src/pyams_media/skin/resources/flowplayer/flowplayer.js
changeset 0 fd39db613f8b
child 30 a84ca4335d44
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_media/skin/resources/flowplayer/flowplayer.js	Wed Sep 02 15:31:55 2015 +0200
@@ -0,0 +1,6697 @@
+/*!
+
+   Flowplayer v6.0.3 (Thursday, 23. July 2015 09:32PM) | flowplayer.org/license
+
+*/
+/*! (C) WebReflection Mit Style License */
+(function(e){function m(e,t,n,r){for(var i,s=n.slice(),o=b(t,e),u=0,a=s.length;u<a;u++){handler=s[u],typeof handler=="object"&&typeof handler.handleEvent=="function"?handler.handleEvent(o):handler.call(e,o);if(o.stoppedImmediatePropagation)break}return i=!o.stoppedPropagation,r&&i&&e.parentNode?e.parentNode.dispatchEvent(o):!o.defaultPrevented}function g(e,t){return{configurable:!0,get:e,set:t}}function y(e,t,n){var r=a(t||e,n);o(e,"textContent",g(function(){return r.get.call(this)},function(e){r.set.call(this,e)}))}function b(e,t){return e.currentTarget=t,e.eventPhase=e.target===e.currentTarget?2:3,e}function w(e,t){var n=e.length;while(n--&&e[n]!==t);return n}function E(){if(this.tagName==="BR")return"\n";var e=this.firstChild,t=[];while(e)e.nodeType!==8&&e.nodeType!==7&&t.push(e.textContent),e=e.nextSibling;return t.join("")}function S(e){return e.nodeType!==9&&document.documentElement.contains(e)}function x(e){var t=document.createEvent("Event");t.initEvent("input",!0,!0),(e.srcElement||e.fromElement||document).dispatchEvent(t)}function T(e){!n&&p.test(document.readyState)&&(n=!n,document.detachEvent(r,T),e=document.createEvent("Event"),e.initEvent(i,!0,!0),document.dispatchEvent(e))}function N(e){var t;while(t=this.lastChild)this.removeChild(t);e!=null&&this.appendChild(document.createTextNode(e))}function C(t,n){return n||(n=e.event),n.target||(n.target=n.srcElement||n.fromElement||document),n.timeStamp||(n.timeStamp=(new Date).getTime()),n}if(document.createEvent)return;var t=!0,n=!1,r="onreadystatechange",i="DOMContentLoaded",s="__IE8__"+Math.random(),o=Object.defineProperty||function(e,t,n){e[t]=n.value},u=Object.defineProperties||function(t,n){for(var r in n)if(f.call(n,r))try{o(t,r,n[r])}catch(i){e.console&&console.log(r+" failed on object:",t,i.message)}},a=Object.getOwnPropertyDescriptor,f=Object.prototype.hasOwnProperty,l=e.Element.prototype,c=e.Text.prototype,h=/^[a-z]+$/,p=/loaded|complete/,d={},v=document.createElement("div");y(e.HTMLCommentElement.prototype,l,"nodeValue"),y(e.HTMLScriptElement.prototype,null,"text"),y(c,null,"nodeValue"),y(e.HTMLTitleElement.prototype,null,"text"),o(e.HTMLStyleElement.prototype,"textContent",function(e){return g(function(){return e.get.call(this.styleSheet)},function(t){e.set.call(this.styleSheet,t)})}(a(e.CSSStyleSheet.prototype,"cssText"))),u(l,{textContent:{get:E,set:N},firstElementChild:{get:function(){for(var e=this.childNodes||[],t=0,n=e.length;t<n;t++)if(e[t].nodeType==1)return e[t]}},lastElementChild:{get:function(){for(var e=this.childNodes||[],t=e.length;t--;)if(e[t].nodeType==1)return e[t]}},oninput:{get:function(){return this._oninput||null},set:function(e){this._oninput&&(this.removeEventListener("input",this._oninput),this._oninput=e,e&&this.addEventListener("input",e))}},previousElementSibling:{get:function(){var e=this.previousSibling;while(e&&e.nodeType!=1)e=e.previousSibling;return e}},nextElementSibling:{get:function(){var e=this.nextSibling;while(e&&e.nodeType!=1)e=e.nextSibling;return e}},childElementCount:{get:function(){for(var e=0,t=this.childNodes||[],n=t.length;n--;e+=t[n].nodeType==1);return e}},addEventListener:{value:function(e,t,n){var r=this,i="on"+e,u=r[s]||o(r,s,{value:{}})[s],a=u[i]||(u[i]={}),l=a.h||(a.h=[]),c;if(!f.call(a,"w")){a.w=function(e){return e[s]||m(r,C(r,e),l,!1)};if(!f.call(d,i))if(h.test(e))try{c=document.createEventObject(),c[s]=!0,r.nodeType!=9&&r.parentNode==null&&v.appendChild(r),r.fireEvent(i,c),d[i]=!0}catch(c){d[i]=!1;while(v.hasChildNodes())v.removeChild(v.firstChild)}else d[i]=!1;(a.n=d[i])&&r.attachEvent(i,a.w)}w(l,t)<0&&l[n?"unshift":"push"](t),e==="input"&&r.attachEvent("onkeyup",x)}},dispatchEvent:{value:function(e){var t=this,n="on"+e.type,r=t[s],i=r&&r[n],o=!!i,u;return e.target||(e.target=t),o?i.n?t.fireEvent(n,e):m(t,e,i.h,!0):(u=t.parentNode)?u.dispatchEvent(e):!0,!e.defaultPrevented}},removeEventListener:{value:function(e,t,n){var r=this,i="on"+e,o=r[s],u=o&&o[i],a=u&&u.h,f=a?w(a,t):-1;-1<f&&a.splice(f,1)}}}),u(c,{addEventListener:{value:l.addEventListener},dispatchEvent:{value:l.dispatchEvent},removeEventListener:{value:l.removeEventListener}}),u(e.XMLHttpRequest.prototype,{addEventListener:{value:function(e,t,n){var r=this,i="on"+e,u=r[s]||o(r,s,{value:{}})[s],a=u[i]||(u[i]={}),f=a.h||(a.h=[]);w(f,t)<0&&(r[i]||(r[i]=function(){var t=document.createEvent("Event");t.initEvent(e,!0,!0),r.dispatchEvent(t)}),f[n?"unshift":"push"](t))}},dispatchEvent:{value:function(e){var t=this,n="on"+e.type,r=t[s],i=r&&r[n],o=!!i;return o&&(i.n?t.fireEvent(n,e):m(t,e,i.h,!0))}},removeEventListener:{value:l.removeEventListener}}),u(e.Event.prototype,{bubbles:{value:!0,writable:!0},cancelable:{value:!0,writable:!0},preventDefault:{value:function(){this.cancelable&&(this.defaultPrevented=!0,this.returnValue=!1)}},stopPropagation:{value:function(){this.stoppedPropagation=!0,this.cancelBubble=!0}},stopImmediatePropagation:{value:function(){this.stoppedImmediatePropagation=!0,this.stopPropagation()}},initEvent:{value:function(e,t,n){this.type=e,this.bubbles=!!t,this.cancelable=!!n,this.bubbles||this.stopPropagation()}}}),u(e.HTMLDocument.prototype,{defaultView:{get:function(){return this.parentWindow}},textContent:{get:function(){return this.nodeType===11?E.call(this):null},set:function(e){this.nodeType===11&&N.call(this,e)}},addEventListener:{value:function(n,s,o){var u=this;l.addEventListener.call(u,n,s,o),t&&n===i&&!p.test(u.readyState)&&(t=!1,u.attachEvent(r,T),e==top&&function a(e){try{u.documentElement.doScroll("left"),T()}catch(t){setTimeout(a,50)}}())}},dispatchEvent:{value:l.dispatchEvent},removeEventListener:{value:l.removeEventListener},createEvent:{value:function(e){var t;if(e!=="Event")throw new Error("unsupported "+e);return t=document.createEventObject(),t.timeStamp=(new Date).getTime(),t}}}),u(e.Window.prototype,{getComputedStyle:{value:function(){function i(e){this._=e}function s(){}var e=/^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/,t=/^(top|right|bottom|left)$/,n=/\-([a-z])/g,r=function(e,t){return t.toUpperCase()};return i.prototype.getPropertyValue=function(i){var s=this._,o=s.style,u=s.currentStyle,a=s.runtimeStyle,f,l,c;return i=(i==="float"?"style-float":i).replace(n,r),f=u?u[i]:o[i],e.test(f)&&!t.test(i)&&(l=o.left,c=a&&a.left,c&&(a.left=u.left),o.left=i==="fontSize"?"1em":f,f=o.pixelLeft+"px",o.left=l,c&&(a.left=c)),f==null?f:f+""||"auto"},s.prototype.getPropertyValue=function(){return null},function(e,t){return t?new s(e):new i(e)}}()},addEventListener:{value:function(t,n,r){var i=e,o="on"+t,u;i[o]||(i[o]=function(e){return m(i,C(i,e),u,!1)}),u=i[o][s]||(i[o][s]=[]),w(u,n)<0&&u[r?"unshift":"push"](n)}},dispatchEvent:{value:function(t){var n=e["on"+t.type];return n?n.call(e,t)!==!1&&!t.defaultPrevented:!0}},removeEventListener:{value:function(t,n,r){var i="on"+t,o=(e[i]||Object)[s],u=o?w(o,n):-1;-1<u&&o.splice(u,1)}}})})(this);
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.flowplayer=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+'use strict';
+var common = module.exports = {},
+    ClassList = _dereq_('class-list'),
+    $ = window.jQuery,
+    punycode = _dereq_('punycode'),
+    computedStyle = _dereq_('computed-style');
+
+common.noop = function() {};
+common.identity = function(i) { return i; };
+
+common.removeNode = function(el) {
+  if (!el || !el.parentNode) return;
+  el.parentNode.removeChild(el);
+};
+
+common.find = function(query, ctx) {
+  if ($) return $(query, ctx).toArray();
+  ctx = ctx || document;
+  return Array.prototype.map.call(ctx.querySelectorAll(query), function(el) { return el; });
+};
+
+common.text = function(el, txt) {
+  el[('innerText' in el) ? 'innerText' : 'textContent'] = txt;
+};
+
+common.findDirect = function(query, ctx) {
+  return common.find(query, ctx).filter(function(node) {
+    return node.parentNode === ctx;
+  });
+};
+
+common.hasClass = function(el, kls) {
+  return ClassList(el).contains(kls);
+};
+
+
+common.css = function(el, property, value) {
+  if (typeof property === 'object') {
+    return Object.keys(property).forEach(function(key) {
+      common.css(el, key, property[key]);
+    });
+  }
+  if (typeof value !== 'undefined') {
+    if (value === '') return el ? el.style.removeProperty(property)  : undefined;
+    return el ? el.style.setProperty(property, value) : undefined;
+  }
+  return el ? computedStyle(el, property) : undefined;
+};
+
+common.createElement = function(tag, attributes, innerHTML) {
+  try {
+    var el = document.createElement(tag);
+    for (var key in attributes) {
+      if (!attributes.hasOwnProperty(key)) continue;
+      if (key === 'css') {
+        common.css(el, attributes[key]);
+      } else {
+        common.attr(el, key, attributes[key]);
+      }
+    }
+    el.innerHTML = innerHTML || '';
+    return el;
+  } catch (e) {
+    if (!$) throw e;
+    return $('<' + tag + '>' + innerHTML + '</' + tag + '>').attr(attributes)[0];
+  }
+};
+
+common.toggleClass = function(el, cls, flag) {
+  if (!el) return;
+  var classes = ClassList(el);
+  if (typeof flag === 'undefined') classes.toggle(cls);
+  else if (flag) classes.add(cls);
+  else if (!flag) classes.remove(cls);
+};
+
+common.addClass = function(el, cls) {
+  return common.toggleClass(el, cls, true);
+};
+
+common.removeClass = function(el, cls) {
+  return common.toggleClass(el, cls, false);
+};
+
+common.append = function(par, child) {
+  par.appendChild(child);
+  return par;
+};
+
+common.appendTo = function(child, par) {
+  common.append(par, child);
+  return child;
+};
+
+common.prepend = function(par, child) {
+  par.insertBefore(child, par.firstChild);
+};
+
+
+// Inserts `el` after `child` that is child of `par`
+common.insertAfter = function(par, child, el) {
+  if (child == common.lastChild(par)) par.appendChild(el);
+  var childIndex = Array.prototype.indexOf.call(par.children, child);
+  par.insertBefore(el, par.children[childIndex + 1]);
+};
+
+common.html = function(elms, val) {
+  elms = elms.length ? elms : [elms];
+  elms.forEach(function(elm) {
+    elm.innerHTML = val;
+  });
+};
+
+
+common.attr = function(el, key, val) {
+  if (key === 'class') key = 'className';
+  if (common.hasOwnOrPrototypeProperty(el, key)) {
+    try {
+      el[key] = val;
+    } catch (e) { // Most likely IE not letting set property
+      if ($) {
+        $(el).attr(key, val);
+      } else {
+        throw e;
+      }
+    }
+  } else {
+    if (val === false) {
+      el.removeAttribute(key);
+    } else {
+      el.setAttribute(key, val);
+    }
+  }
+  return el;
+};
+
+common.prop = function(el, key, val) {
+  if (typeof val === 'undefined') {
+    return el && el[key];
+  }
+  el[key] = val;
+};
+
+common.offset = function(el) {
+  var ret = el.getBoundingClientRect();
+  if (el.offsetWidth / el.offsetHeight > el.clientWidth / el.clientHeight) { // https://github.com/flowplayer/flowplayer/issues/757
+    ret = {
+      left: ret.left * 100,
+      right: ret.right * 100,
+      top: ret.top * 100,
+      bottom: ret.bottom * 100,
+      width: ret.width * 100,
+      height: ret.height * 100
+    };
+  }
+  return ret;
+};
+
+common.width = function(el, val) {
+  /*jshint -W093 */
+  if (val) return el.style.width = (''+val).replace(/px$/, '') + 'px';
+  var ret = common.offset(el).width;
+  return typeof ret === 'undefined' ? el.offsetWidth : ret;
+};
+
+common.height = function(el, val) {
+  /*jshint -W093 */
+  if (val) return el.style.height = (''+val).replace(/px$/, '') + 'px';
+  var ret = common.offset(el).height;
+  return typeof ret === 'undefined' ? el.offsetHeight : ret;
+};
+
+common.lastChild = function(el) {
+  return el.children[el.children.length - 1];
+};
+
+common.hasParent = function(el, parentSelector) {
+  var parent = el.parentElement;
+  while (parent) {
+    if (common.matches(parent, parentSelector)) return true;
+    parent = parent.parentElement;
+  }
+  return false;
+};
+
+common.createAbsoluteUrl = function(url) {
+  return common.createElement('a', {href: url}).href; // This won't work on IE7
+};
+
+common.xhrGet = function(url, successCb, errorCb) {
+  var xhr = new XMLHttpRequest();
+  xhr.onreadystatechange = function() {
+    if (this.readyState !== 4) return;
+    if (this.status >= 400) return errorCb();
+    successCb(this.responseText);
+  };
+  xhr.open('get', url, true);
+  xhr.send();
+};
+
+common.pick = function(obj, props) {
+  var ret = {};
+  props.forEach(function(prop) {
+    if (obj.hasOwnProperty(prop)) ret[prop] = obj[prop];
+  });
+  return ret;
+};
+
+common.hostname = function(host) {
+  return punycode.toUnicode(host || window.location.hostname);
+};
+
+//Hacks
+common.browser = {
+  webkit: 'WebkitAppearance' in document.documentElement.style
+};
+
+common.getPrototype = function(el) {
+  /* jshint proto:true */
+  if (!Object.getPrototypeOf) return el.__proto__;
+  return Object.getPrototypeOf(el);
+};
+
+common.hasOwnOrPrototypeProperty = function(obj, prop) {
+  var o = obj;
+  while (o) {
+    if (Object.prototype.hasOwnProperty.call(o, prop)) return true;
+    o = common.getPrototype(o);
+  }
+  return false;
+};
+
+
+// Polyfill for Element.matches
+// adapted from https://developer.mozilla.org/en/docs/Web/API/Element/matches
+common.matches = function(elem, selector) {
+  var proto = Element.prototype,
+      fn = proto.matches ||
+          proto.matchesSelector ||
+          proto.mozMatchesSelector ||
+          proto.msMatchesSelector ||
+          proto.oMatchesSelector ||
+          proto.webkitMatchesSelector ||
+          function (selector) {
+            var element = this,
+                matches = (element.document || element.ownerDocument).querySelectorAll(selector),
+                i = 0;
+            while (matches[i] && matches[i] !== element) {
+              i++;
+            }
+
+            return matches[i] ? true : false;
+      };
+  return fn.call(elem, selector);
+};
+
+
+// Polyfill for CSSStyleDeclaration
+// from https://github.com/shawnbot/aight
+(function(CSSSDProto) {
+
+  function getAttribute(property) {
+    return property.replace(/-[a-z]/g, function(bit) {
+      return bit[1].toUpperCase();
+    });
+  }
+
+  // patch CSSStyleDeclaration.prototype using IE8's methods
+  if (typeof CSSSDProto.setAttribute !== "undefined") {
+    CSSSDProto.setProperty = function(property, value) {
+      return this.setAttribute(getAttribute(property), String(value) /*, important */ );
+    };
+    CSSSDProto.getPropertyValue = function(property) {
+      return this.getAttribute(getAttribute(property)) || null;
+    };
+    CSSSDProto.removeProperty = function(property) {
+      var value = this.getPropertyValue(property);
+      this.removeAttribute(getAttribute(property));
+      return value;
+    };
+  }
+
+})(window.CSSStyleDeclaration.prototype);
+
+},{"class-list":22,"computed-style":24,"punycode":21}],2:[function(_dereq_,module,exports){
+/* global __flash_unloadHandler:true,__flash_savedUnloadHandler:true */
+'use strict';
+var common = _dereq_('../common');
+
+// movie required in opts
+module.exports = function embed(swf, flashvars, wmode, bgColor) {
+   wmode = wmode || "opaque";
+
+   var id = "obj" + ("" + Math.random()).slice(2, 15),
+       tag = '<object class="fp-engine" id="' + id+ '" name="' + id + '" ',
+       msie = navigator.userAgent.indexOf('MSIE') > -1;
+
+   tag += msie ? 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">' :
+      ' data="' + swf  + '" type="application/x-shockwave-flash">';
+
+   var opts = {
+      width: "100%",
+      height: "100%",
+      allowscriptaccess: "always",
+      wmode: wmode,
+      quality: "high",
+      flashvars: "",
+
+      // https://github.com/flowplayer/flowplayer/issues/13#issuecomment-9369919
+      movie: swf + (msie ? "?" + id : ""),
+      name: id
+   };
+
+   if (wmode !== 'transparent') opts.bgcolor = bgColor || '#333333';
+
+   // flashvars
+   Object.keys(flashvars).forEach(function(key) {
+      opts.flashvars += key + "=" + flashvars[key] + "&";
+   });
+
+   // parameters
+   Object.keys(opts).forEach(function(key) {
+      tag += '<param name="' + key + '" value="'+ opts[key] +'"/>';
+   });
+
+   tag += "</object>";
+   var el = common.createElement('div', {}, tag);
+   return common.find('object', el);
+
+};
+
+
+// Flash is buggy allover
+if (window.attachEvent) {
+   window.attachEvent("onbeforeunload", function() {
+      __flash_savedUnloadHandler = __flash_unloadHandler = function() {};
+   });
+}
+
+
+},{"../common":1}],3:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    common = _dereq_('../common'),
+    embed = _dereq_('./embed'),
+    extend = _dereq_('extend-object'),
+    bean = _dereq_('bean'),
+    engineImpl;
+
+engineImpl = function flashEngine(player, root) {
+
+   var conf = player.conf,
+      video = player.video,
+      loadVideo,
+      callbackId,
+      objectTag,
+      api;
+
+   var win = window;
+
+   var engine = {
+      engineName: engineImpl.engineName,
+
+      pick: function(sources) {
+
+         if (flowplayer.support.flashVideo) {
+            var selectedSource;
+            for (var i = 0, source; i < sources.length; i++) {
+               source = sources[i];
+               if (/mp4|flv|flash/i.test(source.type)) selectedSource = source;
+               if (player.conf.swfHls && /mpegurl/i.test(source.type)) selectedSource = source;
+               if (selectedSource && !/mp4/i.test(selectedSource.type)) return selectedSource;
+               // Did not find any source or source was video/mp4, let's try find more
+            }
+            return selectedSource; // Accept the fact we don't have anything or just an MP4
+         }
+      },
+
+      load: function(video) {
+         loadVideo = video;
+
+         function escapeURL(url) {
+            return url.replace(/&amp;/g, '%26').replace(/&/g, '%26').replace(/=/g, '%3D');
+         }
+
+         var html5Tag = common.findDirect('video', root)[0] || common.find('.fp-player > video', root)[0],
+            url = escapeURL(video.src),
+            is_absolute = /^https?:/.test(url);
+
+         var removeTag = function() {
+            common.removeNode(html5Tag);
+         };
+         var hasSupportedSource = function(sources) {
+            return sources.some(function(src) {
+               return !!html5Tag.canPlayType(src.type);
+            });
+         };
+         if (flowplayer.support.video &&
+            common.prop(html5Tag, 'autoplay') &&
+            hasSupportedSource(video.sources)) bean.one(html5Tag, 'timeupdate', removeTag);
+         else removeTag();
+
+         // convert to absolute
+         var rtmp = video.rtmp || conf.rtmp;
+         if (!is_absolute && !rtmp) url = common.createAbsoluteUrl(url);
+
+         if (api && isHLS(video) && api.data !== conf.swfHls) engine.unload();
+
+         if (api) {
+            ['live', 'preload', 'loop'].forEach(function(prop) {
+              if (!video.hasOwnProperty(prop)) return;
+              api.__set(prop, video[prop]);
+            });
+            Object.keys(video.flashls || {}).forEach(function(key) {
+              api.__set('hls_' + key, video.flashls[key]);
+            });
+            var providerChangeNeeded = false;
+            if (!is_absolute && rtmp) api.__set('rtmp', rtmp.url || rtmp);
+            else {
+              var oldRtmp = api.__get('rtmp');
+              providerChangeNeeded = !!oldRtmp;
+              api.__set('rtmp', null);
+            }
+            api.__play(url, providerChangeNeeded || video.rtmp && video.rtmp !== conf.rtmp);
+
+         } else {
+
+            callbackId = "fpCallback" + ("" + Math.random()).slice(3, 15);
+
+            var opts = {
+               hostname: conf.embedded ? common.hostname(conf.hostname) : common.hostname(location.hostname),
+               url: url,
+               callback: callbackId
+            };
+            if (root.getAttribute('data-origin')) {
+               opts.origin = root.getAttribute('data-origin');
+            }
+
+            // optional conf
+            ['proxy', 'key', 'autoplay', 'preload', 'subscribe', 'live', 'loop', 'debug', 'splash', 'poster', 'rtmpt'].forEach(function(key) {
+              if (conf.hasOwnProperty(key)) opts[key] = conf[key];
+              if (video.hasOwnProperty(key)) opts[key] = video[key];
+              if ((conf.rtmp || {}).hasOwnProperty(key)) opts[key] = (conf.rtmp || {})[key];
+              if ((video.rtmp || {}).hasOwnProperty(key)) opts[key] = (video.rtmp || {})[key];
+            });
+            if (conf.rtmp) opts.rtmp = conf.rtmp.url || conf.rtmp;
+            if (video.rtmp) opts.rtmp = video.rtmp.url || video.rtmp;
+            Object.keys(video.flashls || {}).forEach(function(key) {
+              var val = video.flashls[key];
+              opts['hls_' + key] = val;
+            });
+            // bufferTime might be 0
+            if (conf.bufferTime !== undefined) opts.bufferTime = conf.bufferTime;
+
+            if (is_absolute) delete opts.rtmp;
+
+            // issues #376
+            if (opts.rtmp) {
+               opts.rtmp = escapeURL(opts.rtmp);
+            }
+
+            // issue #733
+            var bgColor = common.css(root, 'background-color') ||'', bg;
+            if (bgColor.indexOf('rgb') === 0) {
+              bg = toHex(bgColor);
+            } else if (bgColor.indexOf('#') === 0) {
+              bg = toLongHex(bgColor);
+            }
+
+            // issues #387
+            opts.initialVolume = player.volumeLevel;
+
+            var swfUrl = isHLS(video) ? conf.swfHls : conf.swf;
+
+            api = embed(swfUrl, opts, conf.wmode, bg)[0];
+
+            var container = common.find('.fp-player', root)[0];
+
+            common.prepend(container, api);
+
+            // throw error if no loading occurs
+            setTimeout(function() {
+               try {
+                  if (!api.PercentLoaded()) {
+                     return player.trigger("error", [player, { code: 7, url: conf.swf }]);
+                  }
+               } catch (e) {}
+            }, 5000);
+
+            // detect disabled flash
+            setTimeout(function() {
+              if (typeof api.PercentLoaded === 'undefined') {
+                player.trigger('flashdisabled', [player]);
+              }
+            }, 1000);
+
+            api.pollInterval = setInterval(function () {
+               if (!api) return;
+               var status = api.__status ? api.__status() : null;
+
+               if (!status) return;
+
+               if (player.playing && status.time && status.time !== player.video.time) player.trigger("progress", [player, status.time]);
+
+               video.buffer = status.buffer / video.bytes * video.duration;
+               player.trigger("buffer", [player, video.buffer]);
+               if (!video.buffered && status.time > 0) {
+                  video.buffered = true;
+                  player.trigger("buffered", [player]);
+               }
+
+            }, 250);
+
+            // listen
+            window[callbackId] = function(type, arg) {
+               var video = loadVideo;
+
+               if (conf.debug) {
+                 if (type.indexOf('debug') === 0 && arg && arg.length) {
+                   console.log.apply(console, ['-- ' + type].concat(arg));
+                 }
+                 else console.log("--", type, arg);
+               }
+
+               var event = {
+                 type: type
+               };
+
+               switch (type) {
+
+                  // RTMP sends a lot of finish events in vain
+                  // case "finish": if (conf.rtmp) return;
+                  case "ready": arg = extend(video, arg); break;
+                  case "click": event.flash = true; break;
+                  case "keydown": event.which = arg; break;
+                  case "seek": video.time = arg; break;
+                  case "status":
+                     player.trigger("progress", [player, arg.time]);
+
+                     if (arg.buffer < video.bytes && !video.buffered) {
+                        video.buffer = arg.buffer / video.bytes * video.duration;
+                        player.trigger("buffer", video.buffer);
+                     } else if (!video.buffered) {
+                        video.buffered = true;
+                        player.trigger("buffered");
+                     }
+
+                     break;
+               }
+               if (type === 'click' || type === 'keydown') {
+                 event.target = root;
+                 bean.fire(root, type, [event]);
+               }
+               else if (type != 'buffered' && type !== 'unload') {
+                  // add some delay so that player is truly ready after an event
+                  setTimeout(function() { player.trigger(event, [player, arg]); }, 1);
+               } else if (type === 'unload') {
+                 player.trigger(event, [player, arg]);
+               }
+
+            };
+
+         }
+
+      },
+
+      // not supported yet
+      speed: common.noop,
+
+
+      unload: function() {
+         if (api && api.__unload) api.__unload();
+         try {
+           if (callbackId && window[callbackId])delete window[callbackId];
+         } catch (e) {}
+         common.find("object", root).forEach(common.removeNode);
+         api = 0;
+         player.off('.flashengine');
+         clearInterval(api.pollInterval);
+      }
+
+   };
+
+   ['pause','resume','seek','volume'].forEach(function(name) {
+
+      engine[name] = function(arg) {
+         try {
+           if (player.ready) {
+
+              if (name == 'seek' && player.video.time && !player.paused) {
+                 player.trigger("beforeseek");
+              }
+
+              if (arg === undefined) {
+                 api["__" + name]();
+
+              } else {
+                 api["__" + name](arg);
+              }
+
+           }
+         } catch (e) {
+           if (typeof api["__" + name] === 'undefined') { //flash lost it's methods
+             return player.trigger('flashdisabled', [player]);
+           }
+           throw e;
+         }
+      };
+
+   });
+
+   function toHex(bg) {
+     function hex(x) {
+       return ("0" + parseInt(x).toString(16)).slice(-2);
+     }
+
+     bg = bg.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
+     if (!bg) return;
+
+     return '#' + hex(bg[1]) + hex(bg[2]) + hex(bg[3]);
+   }
+
+   function toLongHex(bg) {
+     if (bg.length === 7) return bg;
+     var a = bg.split('').slice(1);
+     return '#' + a.map(function(i) {
+       return i + i;
+     }).join('');
+   }
+
+   function isHLS(video) {
+     return /application\/x-mpegurl/i.test(video.type);
+   }
+
+   return engine;
+
+};
+
+
+engineImpl.engineName = 'flash';
+engineImpl.canPlay = function(type, conf) {
+  return flowplayer.support.flashVideo && /video\/(mp4|flash|flv)/i.test(type) || flowplayer.support.flashVideo && conf.swfHls && /mpegurl/i.test(type);
+};
+flowplayer.engines.push(engineImpl);
+
+},{"../common":1,"../flowplayer":18,"./embed":2,"bean":20,"extend-object":26}],4:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    bean = _dereq_('bean'),
+    ClassList = _dereq_('class-list'),
+    extend = _dereq_('extend-object'),
+    common = _dereq_('../common');
+var VIDEO = document.createElement('video');
+
+// HTML5 --> Flowplayer event
+var EVENTS = {
+
+   // fired
+   ended: 'finish',
+   pause: 'pause',
+   play: 'resume',
+   progress: 'buffer',
+   timeupdate: 'progress',
+   volumechange: 'volume',
+   ratechange: 'speed',
+   //seeking: 'beforeseek',
+   seeked: 'seek',
+   // abort: 'resume',
+
+   // not fired
+   loadeddata: 'ready',
+   // loadedmetadata: 0,
+   // canplay: 0,
+
+   // error events
+   // load: 0,
+   // emptied: 0,
+   // empty: 0,
+   error: 'error',
+   dataunavailable: 'error',
+   webkitendfullscreen: !flowplayer.support.inlineVideo && 'unload'
+
+};
+
+function round(val, per) {
+   per = per || 100;
+   return Math.round(val * per) / per;
+}
+
+function getType(type) {
+   return /mpegurl/i.test(type) ? "application/x-mpegurl" : type;
+}
+
+function canPlay(type) {
+   if (!/^(video|application)/i.test(type))
+      type = getType(type);
+   return !!VIDEO.canPlayType(type).replace("no", '');
+}
+
+function findFromSourcesByType(sources, type) {
+   var arr = sources.filter(function(s) {
+      return s.type === type;
+   });
+   return arr.length ? arr[0] : null;
+}
+
+var videoTagCache;
+var createVideoTag = function(video, autoplay, preload, useCache) {
+  if (typeof autoplay === 'undefined') autoplay = true;
+  if (typeof preload === 'undefined') preload = 'none';
+  if (typeof useCache === 'undefined') useCache = true;
+  if (useCache && videoTagCache) {
+    videoTagCache.type = getType(video.type);
+    videoTagCache.src = video.src;
+    return videoTagCache;
+  }
+  var el  = document.createElement('video');
+  el.src = video.src;
+  el.type = getType(video.type);
+  el.className = 'fp-engine';
+  el.autoplay = autoplay ? 'autoplay' : false;
+  el.preload = preload;
+  el.setAttribute('x-webkit-airplay', 'allow');
+  if (useCache) videoTagCache = el;
+  return el;
+};
+
+var engine;
+
+engine = function(player, root) {
+
+  var api = common.findDirect('video', root)[0] || common.find('.fp-player > video', root)[0],
+      support = flowplayer.support,
+      track = common.find("track", api)[0],
+      conf = player.conf,
+      self,
+      timer,
+      volumeLevel;
+   /*jshint -W093 */
+   return self = {
+      engineName: engine.engineName,
+
+      pick: function(sources) {
+         if (support.video) {
+            if (conf.videoTypePreference) {
+               var mp4source = findFromSourcesByType(sources, conf.videoTypePreference);
+               if (mp4source) return mp4source;
+            }
+
+            for (var i = 0, source; i < sources.length; i++) {
+               if (canPlay(sources[i].type)) return sources[i];
+            }
+         }
+      },
+
+      load: function(video) {
+         var created = false, container = common.find('.fp-player', root)[0], reload = false;
+         if (conf.splash && !api) {
+           api = createVideoTag(video);
+           common.prepend(container, api);
+           created = true;
+         } else if (!api) {
+           api = createVideoTag(video, !!video.autoplay || !!conf.autoplay, conf.clip.preload || 'metadata', false);
+           common.prepend(container, api);
+           created = true;
+         } else {
+           ClassList(api).add('fp-engine');
+           common.find('source,track', api).forEach(common.removeNode);
+           if (!player.conf.nativesubtitles) common.attr(api, 'crossorigin', false);
+           reload = api.src === video.src;
+         }
+         if (!support.inlineVideo) {
+           common.css(api, {
+             position: 'absolute',
+             top: '-9999em'
+           });
+         }
+         //TODO subtitles support
+
+         // IE does not fire delegated timeupdate events
+         bean.off(api, 'timeupdate', common.noop);
+         bean.on(api, 'timeupdate', common.noop);
+
+         common.prop(api, 'loop', !!(video.loop || conf.loop));
+
+         if (typeof volumeLevel !== 'undefined') {
+           api.volume = volumeLevel;
+         }
+
+         if (player.video.src && video.src != player.video.src || video.index) common.attr(api, 'autoplay', 'autoplay');
+         api.src = video.src;
+         api.type = video.type;
+
+         listen(api, common.find("source", api).concat(api), video);
+
+         // iPad (+others?) demands load()
+         if (conf.clip.preload != 'none' && video.type != "mpegurl" || !support.zeropreload || !support.dataload) api.load();
+         if (created || reload) api.load();
+         if (api.paused && (video.autoplay || conf.autoplay)) api.play();
+      },
+
+      pause: function() {
+         api.pause();
+      },
+
+      resume: function() {
+         api.play();
+      },
+
+      speed: function(val) {
+         api.playbackRate = val;
+      },
+
+      seek: function(time) {
+         try {
+            var pausedState = player.paused;
+            api.currentTime = time;
+            if (pausedState) api.pause();
+         } catch (ignored) {}
+      },
+
+      volume: function(level) {
+         volumeLevel = level;
+         if (api) {
+            api.volume = level;
+         }
+      },
+
+      unload: function() {
+         common.find('video.fp-engine', root).forEach(common.removeNode);
+         if (!support.cachedVideoTag) videoTagCache = null;
+         timer = clearInterval(timer);
+         api = 0;
+      }
+
+   };
+
+   function listen(api, sources, video) {
+      // listen only once
+      var instanceId = root.getAttribute('data-flowplayer-instance-id');
+
+      if (api.listeners && api.listeners.hasOwnProperty(instanceId)) {
+        api.listeners[instanceId] = video;
+        return;
+      }
+      (api.listeners || (api.listeners = {}))[instanceId] = video;
+
+      bean.on(sources, 'error', function(e) {
+         try {
+            if (canPlay(e.target.getAttribute('type'))) {
+               player.trigger("error", [player, { code: 4, video: extend(video, {src: api.src, url: api.src}) }]);
+            }
+         } catch (er) {
+            // Most likely: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
+         }
+      });
+
+      player.on('shutdown', function() {
+        bean.off(sources);
+      });
+
+      Object.keys(EVENTS).forEach(function(type) {
+        var flow = EVENTS[type];
+        if (!flow) return;
+        root.addEventListener(type, function(e) {
+          video = api.listeners[instanceId];
+          if (!e.target || !ClassList(e.target).contains('fp-engine')) return;
+
+            if (conf.debug && !/progress/.test(flow)) console.log(type, "->", flow, e);
+
+            // no events if player not ready
+            if (!player.ready && !/ready|error/.test(flow) || !flow || !common.find('video', root).length) { return; }
+
+            var arg, vtype;
+
+            if (flow === 'unload') { //Call player unload
+              player.unload();
+              return;
+            }
+
+            var triggerEvent = function() {
+              player.trigger(flow, [player, arg]);
+            };
+
+            switch (flow) {
+
+               case "ready":
+
+                  arg = extend(video, {
+                     duration: api.duration,
+                     width: api.videoWidth,
+                     height: api.videoHeight,
+                     url: api.currentSrc,
+                     src: api.currentSrc
+                  });
+
+                  try {
+                     arg.seekable = !conf.live && /mpegurl/i.test(video ? (video.type || '') : '') && api.duration || api.seekable && api.seekable.end(null);
+
+                  } catch (ignored) {}
+
+                  // buffer
+                  timer = timer || setInterval(function() {
+
+                     try {
+                        arg.buffer = api.buffered.end(null);
+
+                     } catch (ignored) {}
+
+                     if (arg.buffer) {
+                        if (round(arg.buffer, 1000) < round(arg.duration, 1000) && !arg.buffered) {
+                           player.trigger("buffer", e);
+
+                        } else if (!arg.buffered) {
+                           arg.buffered = true;
+                           player.trigger("buffer", e).trigger("buffered", e);
+                           clearInterval(timer);
+                           timer = 0;
+                        }
+                     }
+
+                  }, 250);
+
+                  if (!conf.live && !arg.duration && !support.hlsDuration && type === "loadeddata") {
+                     var durationChanged = function() {
+                        arg.duration = api.duration;
+                        try {
+                           arg.seekable = api.seekable && api.seekable.end(null);
+
+                        } catch (ignored) {}
+                        triggerEvent();
+                        api.removeEventListener('durationchange', durationChanged);
+                        ClassList(root).remove('is-live');
+                     };
+                     api.addEventListener('durationchange', durationChanged);
+
+                     // Ugly hack to handle broken Android devices
+                     var timeUpdated = function() {
+                       if (!player.ready && !api.duration) { // No duration even though the video already plays
+                         arg.duration = 0;
+                         ClassList(root).add('is-live'); // Make UI believe it's live
+                         triggerEvent();
+                       }
+                       api.removeEventListener('timeupdate', timeUpdated);
+                     };
+                     api.addEventListener('timeupdate', timeUpdated);
+                     return;
+                  }
+
+                  break;
+
+               case "progress": case "seek":
+
+                  var dur = player.video.duration;
+
+                  if (api.currentTime > 0 || player.live) {
+                     arg = Math.max(api.currentTime, 0);
+
+                  } else if (flow == 'progress') {
+                     return;
+                  }
+                  break;
+
+
+               case "speed":
+                  arg = round(api.playbackRate);
+                  break;
+
+               case "volume":
+                  arg = round(api.volume);
+                  break;
+
+               case "error":
+                  try {
+                     arg = (e.srcElement || e.originalTarget).error;
+                     arg.video = extend(video, {src: api.src, url: api.src});
+                  } catch (er) {
+                     // Most likely https://bugzilla.mozilla.org/show_bug.cgi?id=208427
+                     return;
+                  }
+            }
+
+            triggerEvent();
+
+
+         }, true);
+
+      });
+
+   }
+
+};
+
+
+engine.canPlay = function(type) {
+  return flowplayer.support.video && canPlay(type);
+};
+
+engine.engineName = 'html5';
+
+flowplayer.engines.push(engine);
+
+},{"../common":1,"../flowplayer":18,"bean":20,"class-list":22,"extend-object":26}],5:[function(_dereq_,module,exports){
+'use strict';
+/* global _gat */
+var flowplayer = _dereq_('../flowplayer'),
+    TYPE_RE = _dereq_('./resolve').TYPE_RE,
+    scriptjs = _dereq_('scriptjs'),
+    bean = _dereq_('bean');
+flowplayer(function(player, root) {
+
+   var id = player.conf.analytics, time = 0, last = 0, timer;
+
+   if (id) {
+
+      // load Analytics script if needed
+      if (typeof _gat == 'undefined') scriptjs("//google-analytics.com/ga.js");
+
+      var getTracker = function() {
+        var tracker = _gat._getTracker(id);
+        tracker._setAllowLinker(true);
+        return tracker;
+      };
+
+      var  track = function track(e, api, video) {
+
+         video = video || player.video;
+
+         if (time && typeof _gat != 'undefined') {
+            var tracker = getTracker();
+
+
+            // http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html
+            tracker._trackEvent(
+               "Video / Seconds played",
+               player.engine.engineName + "/" + video.type,
+               video.title || root.getAttribute("title") || video.src.split("/").slice(-1)[0].replace(TYPE_RE, ''),
+               Math.round(time / 1000)
+            );
+            time = 0;
+            if (timer) {
+              clearTimeout(timer);
+              timer = null;
+            }
+         }
+
+      };
+
+      player.bind("load unload", track).bind("progress", function() {
+
+         if (!player.seeking) {
+            time += last ? (+new Date() - last) : 0;
+            last = +new Date();
+         }
+
+         if (!timer) {
+           timer = setTimeout(function() {
+             timer = null;
+             var tracker = getTracker();
+             tracker._trackEvent('Flowplayer heartbeat', 'Heartbeat', '', 0, true);
+           }, 10*60*1000); // heartbeat every 10 minutes
+         }
+
+      }).bind("pause", function() {
+         last = 0;
+      });
+
+      player.bind('shutdown', function() {
+        bean.off(window, 'unload', track);
+      });
+
+      bean.on(window, 'unload', track);
+
+   }
+
+});
+
+},{"../flowplayer":18,"./resolve":13,"bean":20,"scriptjs":29}],6:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    ClassList = _dereq_('class-list'),
+    common = _dereq_('../common'),
+    bean = _dereq_('bean');
+
+flowplayer(function(player, root) {
+
+   var CUE_RE = / ?cue\d+ ?/;
+
+   var lastTime = 0, cuepointsDisabled = false;
+
+   function setClass(index) {
+      root.className = root.className.replace(CUE_RE, " ");
+      if (index >= 0) ClassList(root).add('cue' + index);
+   }
+
+   var segments = {}, lastFiredSegment = -0.125;
+
+   var fire = function(cue) {
+     var idx = player.cuepoints.indexOf(cue);
+     if (!isNaN(cue)) cue = { time: cue };
+     cue.index = idx;
+     setClass(idx);
+     player.trigger('cuepoint', [player, cue]);
+   };
+
+   player.on("progress", function(e, api, time) {
+     if (cuepointsDisabled) return;
+      var segment = segmentForCue(time);
+      while (lastFiredSegment < segment) {
+        lastFiredSegment += 0.125;
+        if (!segments[lastFiredSegment]) continue;
+        segments[lastFiredSegment].forEach(fire);
+      }
+
+   }).on("unload", setClass)
+   .on('beforeseek', function() {
+     cuepointsDisabled = true;
+   }).on("seek", function(ev, api, time) {
+     setClass();
+     lastFiredSegment = segmentForCue(time || 0) - 0.125;
+     cuepointsDisabled = false;
+     if (!time && segments[0]) segments[0].forEach(fire);
+   }).on('ready', function(e, api, video) {
+     lastFiredSegment = -0.125;
+     var cues = video.cuepoints || player.conf.cuepoints || [];
+     player.setCuepoints(cues);
+   }).on('finish', function() {
+     lastFiredSegment = -0.125;
+   });
+   if (player.conf.generate_cuepoints) {
+
+      player.bind("load", function() {
+
+         // clean up cuepoint elements of previous playlist items
+         common.find('.fp-cuepoint', root).forEach(common.removeNode);
+
+      });
+   }
+
+   /**
+    * API
+    */
+   player.setCuepoints = function(cues) {
+     player.cuepoints = [];
+     segments = {};
+     cues.forEach(player.addCuepoint);
+     return player;
+   };
+   player.addCuepoint = function(cue) {
+     if (!player.cuepoints) player.cuepoints = [];
+     var segment = segmentForCue(cue);
+     if (!segments[segment]) segments[segment] = [];
+     segments[segment].push(cue);
+     player.cuepoints.push(cue);
+
+    if (player.conf.generate_cuepoints && cue.visible !== false) {
+       var duration = player.video.duration,
+           timeline = common.find('.fp-timeline', root)[0];
+        common.css(timeline, "overflow", "visible");
+
+        var time = cue.time || cue;
+        if (time < 0) time = duration + cue;
+
+        var el = common.createElement('a', {className: 'fp-cuepoint fp-cuepoint' + (player.cuepoints.length - 1)});
+        common.css(el, "left", (time / duration * 100) + "%");
+
+        timeline.appendChild(el);
+        bean.on(el, 'mousedown', function(e) {
+          e.preventDefault();
+          player.seek(time);
+
+          // preventDefault() doesn't work
+          return false;
+        });
+    }
+    return player;
+   };
+
+   player.removeCuepoint = function(cue) {
+     var idx = player.cuepoints.indexOf(cue),
+         segment = segmentForCue(cue);
+     if (idx === -1) return;
+     player.cuepoints = player.cuepoints.slice(0, idx).concat(player.cuepoints.slice(idx+1));
+
+     var sIdx = segments[segment].indexOf(cue);
+     if (sIdx === -1) return;
+     segments[segment] = segments[segment].slice(0, sIdx).concat(segments[segment].slice(sIdx+1));
+     return player;
+   };
+
+   function segmentForCue(cue) {
+     var time = cue && !isNaN(cue.time) ? cue.time : cue;
+     if (time < 0) time = player.video.duration + time;
+     return Math.round(time/0.125)*0.125;
+   }
+
+});
+
+},{"../common":1,"../flowplayer":18,"bean":20,"class-list":22}],7:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    bean = _dereq_('bean'),
+    common = _dereq_('../common'),
+    isObject = _dereq_('is-object'),
+    extend = _dereq_('extend-object'),
+    ClassList = _dereq_('class-list');
+
+
+
+flowplayer(function(player, root) {
+
+   // no embedding
+   if (player.conf.embed === false) return;
+
+   var conf = player.conf,
+      ui = common.find('.fp-ui', root)[0],
+      trigger = common.createElement('a', { "class": "fp-embed", title: 'Copy to your site'}),
+      target = common.createElement('div',{ 'class': 'fp-embed-code'}, '<label>Paste this HTML code on your site to embed.</label><textarea></textarea>'),
+      area = common.find("textarea", target)[0];
+
+   ui.appendChild(trigger);
+   ui.appendChild(target);
+
+   player.embedCode = function() {
+     var embedConf = player.conf.embed || {},
+         video = player.video;
+
+     if (embedConf.iframe) {
+       var src = player.conf.embed.iframe,
+           width = embedConf.width || video.width || common.width(root),
+           height = embedConf.height || video.height || common.height(root);
+       return '<iframe src="' + player.conf.embed.iframe + '" frameBorder="0" allowfullscreen width="' + width + '" height="' + height + '"></iframe>';
+     }
+     var props = ['ratio', 'rtmp', 'live', 'bufferTime', 'origin', 'analytics', 'key', 'subscribe', 'swf', 'swfHls', 'embed', 'adaptiveRatio', 'logo'];
+     if (embedConf.playlist) props.push('playlist');
+     var c = common.pick(player.conf, props);
+     if (c.logo) c.logo = common.createElement('img', {src: c.logo}).src;
+     if (!embedConf.playlist || !player.conf.playlist.length) c.clip =  extend({}, player.conf.clip, common.pick(player.video, ['sources']));
+     var script = "var w=window,d=document,e;w._fpes||(w._fpes=[],w.addEventListener(\"load\",function(){var s=d.createElement(\"script\");s.src=\"//embed.flowplayer.org/6.0.3/embed.min.js\",d.body.appendChild(s)})),e=[].slice.call(d.getElementsByTagName(\"script\"),-1)[0].parentNode,w._fpes.push({e:e,l:\"$library\",c:$conf});\n".replace('$conf', JSON.stringify(c)).replace('$library', embedConf.library || '');
+
+     return '<a href="$href">Watch video!\n<script>$script</script></a>'.replace('$href', player.conf.origin || window.location.href).replace('$script', script);
+
+   };
+   fptip(root, ".fp-embed", "is-embedding");
+
+   bean.on(root, 'click', '.fp-embed-code textarea',  function() {
+      area.select();
+   });
+
+   bean.on(root, 'click', '.fp-embed', function() {
+      area.textContent = player.embedCode().replace(/(\r\n|\n|\r)/gm,"");
+      area.focus();
+      area.select();
+   });
+
+});
+
+var fptip = function(root, trigger, active) {
+
+  function close() {
+    rootClasses.remove(active);
+    bean.off(document, '.st');
+  }
+
+  var rootClasses = ClassList(root);
+
+  bean.on(root, 'click', trigger || 'a', function(e) {
+    e.preventDefault();
+
+    rootClasses.toggle(active);
+
+    if (rootClasses.contains(active)) {
+
+      bean.on(document, 'keydown.st', function(e) {
+        if (e.which == 27) close();
+      });
+      bean.on(document, 'click.st', function(e) {
+        if (!common.hasParent(e.target, '.' + active)) close();
+      });
+    }
+  });
+};
+
+
+},{"../common":1,"../flowplayer":18,"bean":20,"class-list":22,"extend-object":26,"is-object":28}],8:[function(_dereq_,module,exports){
+'use strict';
+/* global jQuery */
+/**
+ * Mimimal jQuery-like event emitter implementation
+ */
+module.exports = function(obj, elem) {
+  if (!elem) elem = document.createElement('div'); //In this case we always want to trigger (Custom)Events on dom element
+  var handlers = {}, eventArguments = {};
+
+  var listenEvent = function(type, hndlr, disposable) {
+    var actualEvent = type.split('.')[0]; //Strip namespace
+    var internalHandler = function(ev) {
+      if (disposable) {
+        elem.removeEventListener(actualEvent, internalHandler);
+        handlers[type].splice(handlers[type].indexOf(internalHandler), 1);
+      }
+      var args = [ev].concat(eventArguments[ev.timeStamp + ev.type] || []);
+      if (hndlr) hndlr.apply(undefined, args);
+    };
+    elem.addEventListener(actualEvent, internalHandler);
+
+    //Store handlers for unbinding
+    if (!handlers[type]) handlers[type] = [];
+    handlers[type].push(internalHandler);
+  };
+
+  obj.on = obj.bind = function(typ, hndlr) {
+    var types = typ.split(' ');
+    types.forEach(function(type) {
+      listenEvent(type, hndlr);
+    });
+    return obj; //for chaining
+  };
+
+  obj.one = function(typ, hndlr) {
+    var types = typ.split(' ');
+    types.forEach(function(type) {
+      listenEvent(type, hndlr, true);
+    });
+    return obj;
+  };
+
+  // Function to check if all items in toBeContained array are in the containing array
+  var containsAll = function(containing, toBeContained) {
+    return toBeContained.filter(function(i) {
+      return containing.indexOf(i) === -1;
+    }).length === 0;
+  };
+
+
+  obj.off = obj.unbind = function(typ) {
+    var types = typ.split(' ');
+    types.forEach(function(type) {
+      var typeNameSpaces = type.split('.').slice(1),
+          actualType = type.split('.')[0];
+      Object.keys(handlers).filter(function(t) {
+        var handlerNamespaces = t.split('.').slice(1);
+        return (!actualType || t.indexOf(actualType) === 0) && containsAll(handlerNamespaces, typeNameSpaces);
+      }).forEach(function(t) {
+        var registererHandlers = handlers[t],
+            actualEvent = t.split('.')[0];
+        registererHandlers.forEach(function(hndlr) {
+          elem.removeEventListener(actualEvent, hndlr);
+          registererHandlers.splice(registererHandlers.indexOf(hndlr), 1);
+        });
+      });
+    });
+    return obj;
+  };
+
+  obj.trigger = function(typ, args, returnEvent) {
+    if (!typ) return;
+    args = (args || []).length ? args || [] : [args];
+    var event = document.createEvent('Event'), typStr;
+    typStr = typ.type || typ;
+    event.initEvent(typStr, false, true);
+    eventArguments[event.timeStamp + event.type] = args;
+    elem.dispatchEvent(event);
+    return returnEvent ? event : obj;
+  };
+};
+
+
+module.exports.EVENTS = [
+  'beforeseek',
+  'disable',
+  'error',
+  'finish',
+  'fullscreen',
+  'fullscreen-exit',
+  'load',
+  'mute',
+  'pause',
+  'progress',
+  'ready',
+  'resume',
+  'seek',
+  'speed',
+  'stop',
+  'unload',
+  'volume',
+  'boot',
+  'shutdown'
+];
+
+},{}],9:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    bean = _dereq_('bean'),
+    ClassList = _dereq_('class-list'),
+    extend = _dereq_('extend-object'),
+    common = _dereq_('../common'),
+   VENDOR = flowplayer.support.browser.mozilla ? "moz": "webkit",
+   FS_ENTER = "fullscreen",
+   FS_EXIT = "fullscreen-exit",
+   FULL_PLAYER,
+   FS_SUPPORT = flowplayer.support.fullscreen,
+   FS_NATIVE_SUPPORT = typeof document.exitFullscreen == 'function',
+   ua = navigator.userAgent.toLowerCase(),
+   IS_SAFARI = /(safari)[ \/]([\w.]+)/.exec(ua) && !/(chrome)[ \/]([\w.]+)/.exec(ua);
+
+
+// esc button
+bean.on(document, "fullscreenchange.ffscr webkitfullscreenchange.ffscr mozfullscreenchange.ffscr MSFullscreenChange.ffscr", function(e) {
+  var el = document.webkitCurrentFullScreenElement || document.mozFullScreenElement || document.fullscreenElement || document.msFullscreenElement || e.target;
+  if (!FULL_PLAYER && (!el.parentNode || !el.parentNode.getAttribute('data-flowplayer-instance-id'))) return;
+  var player = FULL_PLAYER || flowplayer(el.parentNode);
+  if (el && !FULL_PLAYER) {
+     FULL_PLAYER = player.trigger(FS_ENTER, [el]);
+  } else {
+     FULL_PLAYER.trigger(FS_EXIT, [FULL_PLAYER]);
+     FULL_PLAYER = null;
+  }
+ });
+
+flowplayer(function(player, root) {
+
+   var wrapper = common.createElement('div', {className: 'fp-player'});
+   Array.prototype.map.call(root.children, common.identity).forEach(function(el) {
+     if (common.matches(el, '.fp-ratio,script')) return;
+     wrapper.appendChild(el);
+   });
+   root.appendChild(wrapper);
+
+   if (!player.conf.fullscreen) return;
+
+   var win = window,
+      scrollY,
+      scrollX,
+      rootClasses = ClassList(root);
+
+   player.isFullscreen = false;
+
+   player.fullscreen = function(flag) {
+
+      if (player.disabled) return;
+
+      if (flag === undefined) flag = !player.isFullscreen;
+
+      if (flag) {
+        scrollY = win.scrollY;
+        scrollX = win.scrollX;
+      }
+
+      if (FS_SUPPORT) {
+
+         if (flag) {
+            ['requestFullScreen', 'webkitRequestFullScreen', 'mozRequestFullScreen', 'msRequestFullscreen'].forEach(function(fName) {
+               if (typeof wrapper[fName] === 'function') {
+                  wrapper[fName](Element.ALLOW_KEYBOARD_INPUT);
+                  if (IS_SAFARI && !document.webkitCurrentFullScreenElement && !document.mozFullScreenElement) { // Element.ALLOW_KEYBOARD_INPUT not allowed
+                     wrapper[fName]();
+                  }
+                  return false;
+               }
+            });
+
+         } else {
+            ['exitFullscreen', 'webkitCancelFullScreen', 'mozCancelFullScreen', 'msExitFullscreen'].forEach(function(fName) {
+              if (typeof document[fName] === 'function') {
+                document[fName]();
+                return false;
+              }
+            });
+         }
+
+      } else {
+         player.trigger(flag ? FS_ENTER : FS_EXIT, [player]);
+      }
+
+      return player;
+   };
+
+   var lastClick;
+
+   player.on("mousedown.fs", function() {
+      if (+new Date() - lastClick < 150 && player.ready) player.fullscreen();
+      lastClick = +new Date();
+   });
+
+   player.on(FS_ENTER, function(e) {
+      rootClasses.add("is-fullscreen");
+      if (!FS_SUPPORT) common.css(root, 'position', 'fixed');
+      player.isFullscreen = true;
+
+   }).on(FS_EXIT, function(e) {
+      var oldOpacity;
+      if (!FS_SUPPORT && player.engine === "html5") {
+        oldOpacity = root.css('opacity') || '';
+        common.css(root, 'opacity', 0);
+      }
+      if (!FS_SUPPORT) common.css(root, 'position', '');
+      rootClasses.remove("is-fullscreen");
+      if (!FS_SUPPORT && player.engine === "html5") setTimeout(function() { root.css('opacity', oldOpacity); });
+      player.isFullscreen = false;
+      win.scrollTo(scrollX, scrollY);
+   }).on('unload', function() {
+     if (player.isFullscreen) player.fullscreen();
+   });
+
+   player.on('shutdown', function() {
+     bean.off(document, '.ffscr');
+     FULL_PLAYER = null;
+   });
+
+});
+
+},{"../common":1,"../flowplayer":18,"bean":20,"class-list":22,"extend-object":26}],10:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    bean = _dereq_('bean'),
+    focused,
+    focusedRoot,
+    IS_HELP = "is-help",
+    common = _dereq_('../common'),
+    ClassList = _dereq_('class-list');
+
+ // keyboard. single global listener
+bean.on(document, "keydown.fp", function(e) {
+
+   var el = focused,
+      metaKeyPressed = e.ctrlKey || e.metaKey || e.altKey,
+      key = e.which,
+      conf = el && el.conf,
+      focusedRootClasses = focusedRoot && ClassList(focusedRoot);
+
+   if (!el || !conf.keyboard || el.disabled) return;
+
+   // help dialog (shift key not truly required)
+   if ([63, 187, 191].indexOf(key) != -1) {
+      focusedRootClasses.toggle(IS_HELP);
+      return false;
+   }
+
+   // close help / unload
+   if (key == 27 && focusedRootClasses.contains(IS_HELP)) {
+      focusedRootClasses.toggle(IS_HELP);
+      return false;
+   }
+
+   if (!metaKeyPressed && el.ready) {
+
+      e.preventDefault();
+
+      // slow motion / fast forward
+      if (e.shiftKey) {
+         if (key == 39) el.speed(true);
+         else if (key == 37) el.speed(false);
+         return;
+      }
+
+      // 1, 2, 3, 4 ..
+      if (key < 58 && key > 47) return el.seekTo(key - 48);
+
+      switch (key) {
+         case 38: case 75: el.volume(el.volumeLevel + 0.15); break;        // volume up
+         case 40: case 74: el.volume(el.volumeLevel - 0.15); break;        // volume down
+         case 39: case 76: el.seeking = true; el.seek(true); break;        // forward
+         case 37: case 72: el.seeking = true; el.seek(false); break;       // backward
+         case 190: el.seekTo(); break;                                     // to last seek position
+         case 32: el.toggle(); break;                                      // spacebar
+         case 70: if(conf.fullscreen) el.fullscreen(); break;               // toggle fullscreen
+         case 77: el.mute(); break;                                        // mute
+         case 81: el.unload(); break;                                      // unload/stop
+      }
+
+   }
+
+});
+
+flowplayer(function(api, root) {
+
+   // no keyboard configured
+   if (!api.conf.keyboard) return;
+
+   // hover
+   bean.on(root, "mouseenter mouseleave", function(e) {
+      focused = !api.disabled && e.type == 'mouseover' ? api : 0;
+      if (focused) focusedRoot = root;
+   });
+
+   var speedhelp = flowplayer.support.video && api.conf.engine !== "flash" &&
+      !!document.createElement('video').playbackRate ?
+      '<p><em>shift</em> + <em>&#8592;</em><em>&#8594;</em>slower / faster</p>' : '';
+
+   // TODO: add to player-layout.html
+   root.appendChild(common.createElement('div', { className: 'fp-help' }, '\
+         <a class="fp-close"></a>\
+         <div class="fp-help-section fp-help-basics">\
+            <p><em>space</em>play / pause</p>\
+            <p><em>q</em>unload | stop</p>\
+            <p><em>f</em>fullscreen</p>' + speedhelp + '\
+         </div>\
+         <div class="fp-help-section">\
+            <p><em>&#8593;</em><em>&#8595;</em>volume</p>\
+            <p><em>m</em>mute</p>\
+         </div>\
+         <div class="fp-help-section">\
+            <p><em>&#8592;</em><em>&#8594;</em>seek</p>\
+            <p><em>&nbsp;. </em>seek to previous\
+            </p><p><em>1</em><em>2</em>&hellip; <em>6</em> seek to 10%, 20% &hellip; 60% </p>\
+         </div>\
+   '));
+
+   if (api.conf.tooltip) {
+     var ui = common.find('.fp-ui', root)[0];
+     ui.setAttribute('title', 'Hit ? for help');
+     bean.one(root, "mouseout.tip", '.fp-ui', function() {
+       ui.removeAttribute('title');
+     });
+   }
+
+   bean.on(root, 'click', '.fp-close', function() {
+     ClassList(root).toggle(IS_HELP);
+   });
+
+   api.bind('shutdown', function() {
+     if (focusedRoot == root) focusedRoot = null;
+   });
+
+});
+
+
+},{"../common":1,"../flowplayer":18,"bean":20,"class-list":22}],11:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    isIeMobile = /IEMobile/.test(window.navigator.userAgent),
+    ClassList = _dereq_('class-list'),
+    common = _dereq_('../common'),
+    bean = _dereq_('bean'),
+    format = _dereq_('./ui').format,
+    UA = window.navigator.userAgent;
+if (flowplayer.support.touch || isIeMobile) {
+
+   flowplayer(function(player, root) {
+      var isAndroid = /Android/.test(UA) && !/Firefox/.test(UA) && !/Opera/.test(UA),
+          isSilk = /Silk/.test(UA),
+          androidVer = isAndroid ? parseFloat(/Android\ (\d\.\d)/.exec(UA)[1], 10) : 0,
+          rootClasses = ClassList(root);
+
+      // custom load for android
+      if (isAndroid && !isIeMobile) {
+         if (!/Chrome/.test(UA) && androidVer < 4) {
+            var originalLoad = player.load;
+            player.load = function(video, callback) {
+               var ret = originalLoad.apply(player, arguments);
+               player.trigger('ready', [player, player.video]);
+               return ret;
+            };
+         }
+         var timer, currentTime = 0;
+         var resumeTimer = function(api) {
+           timer = setInterval(function() {
+             api.video.time = ++currentTime;
+             api.trigger('progress', [api, currentTime]);
+           }, 1000);
+         };
+         player.bind('ready pause unload', function() {
+           if (timer) {
+             clearInterval(timer);
+             timer = null;
+           }
+         });
+         player.bind('ready', function() {
+           currentTime = 0;
+         });
+         player.bind('resume', function(ev, api) {
+           if (!api.live) return;
+           if (currentTime) { return resumeTimer(api); }
+           player.one('progress', function(ev, api, t) {
+             if (t === 0) { // https://github.com/flowplayer/flowplayer/issues/727
+               resumeTimer(api);
+             }
+           });
+         });
+      }
+
+      // hide volume
+      if (!flowplayer.support.volume) {
+         rootClasses.add("no-volume");
+         rootClasses.add("no-mute");
+      }
+      rootClasses.add("is-touch");
+      if (player.sliders && player.sliders.timeline) player.sliders.timeline.disableAnimation();
+
+      if (!flowplayer.support.inlineVideo || player.conf.native_fullscreen) player.conf.nativesubtitles = true;
+
+      // fake mouseover effect with click
+      var hasMoved = false;
+      bean.on(root, 'touchmove', function() {
+        hasMoved = true;
+      });
+      bean.on(root, 'touchend click', function(e) {
+        if (hasMoved) { //not intentional, most likely scrolling
+          hasMoved = false;
+          return;
+        }
+
+        if (player.playing && !rootClasses.contains("is-mouseover")) {
+          rootClasses.add("is-mouseover");
+          rootClasses.remove("is-mouseout");
+          e.preventDefault();
+          e.stopPropagation();
+          return;
+        }
+
+        if (!player.playing && !player.splash && rootClasses.contains('is-mouseout') && !rootClasses.contains('is-mouseover')) {
+          setTimeout(function() {
+            if (!player.playing && !player.splash) {
+              player.resume();
+            }
+          }, 400);
+        }
+
+
+      });
+
+      // native fullscreen
+      if (player.conf.native_fullscreen && typeof document.createElement('video').webkitEnterFullScreen === 'function') {
+         player.fullscreen = function() {
+            var video = common.find('video.fp-engine', root)[0];
+            video.webkitEnterFullScreen();
+            bean.one(video, 'webkitendfullscreen', function() {
+              common.prop(video, 'controls', true);
+              common.prop(video, 'controls', false);
+            });
+         };
+      }
+
+
+      // Android browser gives video.duration == 1 until second 'timeupdate' event
+      if (isAndroid || isSilk) player.bind("ready", function() {
+
+         var video = common.find('video.fp-engine', root)[0];
+         bean.one(video, 'canplay', function() {
+            video.play();
+         });
+         video.play();
+
+         player.bind("progress.dur", function() {
+
+            var duration = video.duration;
+
+            if (duration !== 1) {
+               player.video.duration = duration;
+               common.find(".fp-duration", root)[0].innerHTML = format(duration);
+               player.unbind("progress.dur");
+            }
+         });
+      });
+
+
+   });
+
+}
+
+
+},{"../common":1,"../flowplayer":18,"./ui":17,"bean":20,"class-list":22}],12:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    extend = _dereq_('extend-object'),
+    bean = _dereq_('bean'),
+    ClassList = _dereq_('class-list'),
+    common = _dereq_('../common'),
+    Resolve = _dereq_('./resolve'),
+    resolver = new Resolve(),
+    $ = window.jQuery,
+    externalRe = /^#/;
+flowplayer(function(player, root) {
+
+   var conf = extend({ active: 'is-active', advance: true, query: ".fp-playlist a" }, player.conf),
+      klass = conf.active, rootClasses = ClassList(root);
+
+   // getters
+   function els() {
+     return common.find(conf.query, queryRoot());
+   }
+
+   function queryRoot() {
+     if (externalRe.test(conf.query)) return;
+     return root;
+   }
+
+   function active() {
+      return common.find(conf.query + "." + klass, queryRoot());
+   }
+
+
+   player.play = function(i) {
+      if (i === undefined) return player.resume();
+      if (typeof i === 'number' && !player.conf.playlist[i]) return player;
+      else if (typeof i != 'number') return player.load.apply(null, arguments);
+      var arg = extend({index: i}, player.conf.playlist[i]);
+      if (i === player.video.index) return player.load(arg, function() { player.resume(); });
+      player.off('resume.fromfirst'); // Don't start from beginning if clip explicitely chosen
+      player.load(arg, function() {
+        player.video.index = i;
+      });
+      return player;
+   };
+
+   player.next = function(e) {
+      if (e) e.preventDefault();
+      var current = player.video.index;
+      if (current != -1) {
+         current = current === player.conf.playlist.length - 1 ? 0 : current + 1;
+         player.play(current);
+      }
+      return player;
+   };
+
+   player.prev = function(e) {
+      if (e) e.preventDefault();
+      var current = player.video.index;
+      if (current != -1) {
+         current = current === 0 ? player.conf.playlist.length - 1 : current - 1;
+         player.play(current);
+      }
+      return player;
+   };
+
+   player.setPlaylist = function(items) {
+     player.conf.playlist = items;
+     delete player.video.index;
+     generatePlaylist();
+     return player;
+   };
+
+   player.addPlaylistItem = function(item) {
+     return player.setPlaylist(player.conf.playlist.concat([item]));
+   };
+
+   player.removePlaylistItem = function(idx) {
+     var pl = player.conf.playlist;
+     return player.setPlaylist(pl.slice(0, idx).concat(pl.slice(idx+1)));
+   };
+
+   bean.on(root, 'click', '.fp-next', player.next);
+   bean.on(root, 'click', '.fp-prev', player.prev);
+
+   if (conf.advance) {
+      player.off("finish.pl").on("finish.pl", function(e, player) {
+         // clip looping
+         if (player.video.loop) return player.seek(0, function() { player.resume(); });
+         // next clip is found or loop
+         var next = player.video.index >= 0 ? player.video.index + 1 : undefined;
+         if (next < player.conf.playlist.length || conf.loop) {
+            next = next === player.conf.playlist.length ? 0 : next;
+            rootClasses.remove('is-finished');
+            setTimeout(function() { // Let other finish callbacks fire first
+               player.play(next);
+            });
+
+         // stop to last clip, play button starts from 1:st clip
+         } else {
+
+            // If we have multiple items in playlist, start from first
+            if (player.conf.playlist.length > 1) player.one("resume.fromfirst", function() {
+               player.play(0);
+               return false;
+            });
+         }
+      });
+   }
+
+   function generatePlaylist() {
+      var plEl = common.find('.fp-playlist', root)[0];
+      if (!plEl) {
+         plEl = common.createElement('div', {className: 'fp-playlist'});
+         var cntrls = common.find('.fp-next,.fp-prev', root);
+         if (!cntrls.length) common.insertAfter(root, common.find('video', root)[0], plEl);
+         else cntrls[0].parentElement.insertBefore(plEl, cntrls[0]);
+      }
+      plEl.innerHTML = '';
+      if (player.conf.playlist[0].length) { // FP5 style playlist
+        player.conf.playlist = player.conf.playlist.map(function(itm) {
+          if (typeof itm === 'string') {
+            var type = itm.split(Resolve.TYPE_RE)[1];
+            return {
+              sources: [{
+                type: type.toLowerCase() === 'm3u8' ? 'application/x-mpegurl' : 'video/' + type,
+                src: itm
+              }]
+            };
+          }
+          return {
+            sources: itm.map(function(src) {
+              var s = {};
+              Object.keys(src).forEach(function(k) {
+                s.type = /mpegurl/i.test(k) ? 'application/x-mpegurl' : 'video/' + k;
+                s.src = src[k];
+              });
+              return s;
+            })
+          };
+        });
+      }
+      player.conf.playlist.forEach(function(item, i) {
+         var href = item.sources[0].src;
+         plEl.appendChild(common.createElement('a', {
+            href: href,
+            'data-index': i
+         }));
+      });
+   }
+
+   var playlistInitialized = false;
+   if (player.conf.playlist.length) { // playlist configured by javascript, generate playlist
+      playlistInitialized = true;
+      generatePlaylist();
+      if (!player.conf.clip || !player.conf.clip.sources.length) player.conf.clip = player.conf.playlist[0];
+   }
+
+   if (els().length && !playlistInitialized) { //generate playlist from existing elements
+       player.conf.playlist = [];
+       els().forEach(function(el) {
+          var src = el.href;
+          el.setAttribute('data-index', player.conf.playlist.length);
+          var itm = resolver.resolve(src, player.conf.clip.sources);
+          if ($) {
+            extend(itm, $(el).data());
+          }
+          player.conf.playlist.push(itm);
+       });
+    }
+
+    /* click -> play */
+    bean.on(externalRe.test(conf.query) ? document : root, "click", conf.query, function(e) {
+       e.preventDefault();
+       var el = e.currentTarget;
+       var toPlay = Number(el.getAttribute('data-index'));
+       if (toPlay != -1) {
+          player.play(toPlay);
+       }
+    });
+
+    // highlight
+    player.on("load", function(e, api, video) {
+       if (!player.conf.playlist.length) return;
+       var prev = active()[0],
+          prevIndex = prev && prev.getAttribute('data-index'),
+          index = video.index = video.index || player.video.index || 0,
+          el = common.find(conf.query +'[data-index="' + index + '"]', queryRoot())[0],
+          is_last = index == player.conf.playlist.length - 1;
+       if (prev) ClassList(prev).remove(klass);
+       if (el) ClassList(el).add(klass);
+       // index
+       rootClasses.remove("video" + prevIndex);
+       rootClasses.add("video" + index);
+       common.toggleClass(root, "last-video", is_last);
+
+       // video properties
+       video.index = api.video.index = index;
+       video.is_last = api.video.is_last = is_last;
+
+    // without namespace callback called only once. unknown rason.
+    }).on("unload.pl", function() {
+       if (!player.conf.playlist.length) return;
+       active().forEach(function(el) {
+         ClassList(el).toggle(klass);
+       });
+       player.conf.playlist.forEach(function(itm, i) {
+         rootClasses.remove('video' + i);
+       });
+    });
+
+   if (player.conf.playlist.length) {
+      // disable single clip looping
+      player.conf.loop = false;
+   }
+
+
+});
+
+},{"../common":1,"../flowplayer":18,"./resolve":13,"bean":20,"class-list":22,"extend-object":26}],13:[function(_dereq_,module,exports){
+'use strict';
+var TYPE_RE = /\.(\w{3,4})(\?.*)?$/i,
+    extend = _dereq_('extend-object');
+
+function parseSource(el) {
+
+   var src = el.attr("src"),
+      type = el.attr("type") || "",
+      suffix = src.split(TYPE_RE)[1];
+   type = type.toLowerCase();
+   return extend(el.data(), { src: src, suffix: suffix || type, type: type || suffix });
+}
+
+function getType(typ) {
+  if (/mpegurl/i.test(typ)) return 'application/x-mpegurl';
+  return 'video/' + typ;
+}
+
+/* Resolves video object from initial configuration and from load() method */
+module.exports = function URLResolver() {
+  var self = this;
+
+  self.sourcesFromVideoTag = function(videoTag, $) {
+    /* global $ */
+    var sources = [];
+    // initial sources
+    $("source", videoTag).each(function() {
+      sources.push(parseSource($(this)));
+    });
+
+    if (!sources.length && videoTag.length) sources.push(parseSource(videoTag));
+
+    return sources;
+  };
+
+
+  self.resolve = function(video, sources) {
+    if (!video) return { sources: sources };
+
+    if (typeof video == 'string') {
+      video = { src: video, sources: [] };
+      video.sources = (sources || []).map(function(source) {
+        var suffix = source.src.split(TYPE_RE)[1];
+        return {type: source.type, src: video.src.replace(TYPE_RE, '.' + suffix + "$2")};
+      });
+    }
+
+    if (video instanceof Array) {
+      video = {
+        sources: video.map(function(src) {
+          if (src.type && src.src) return src;
+          return Object.keys(src).reduce(function(m, typ) {
+            return extend(m, {
+              type: getType(typ),
+              src: src[typ]
+            });
+          }, {});
+        })
+      };
+    }
+
+    return video;
+  };
+};
+
+module.exports.TYPE_RE = TYPE_RE;
+
+},{"extend-object":26}],14:[function(_dereq_,module,exports){
+'use strict';
+// skip IE policies
+// document.ondragstart = function () { return false; };
+//
+var ClassList = _dereq_('class-list'),
+    bean = _dereq_('bean'),
+    common = _dereq_('../common');
+
+
+// execute function every <delay> ms
+var throttle = function(fn, delay) {
+   var locked;
+
+   return function () {
+      if (!locked) {
+         fn.apply(this, arguments);
+         locked = 1;
+         setTimeout(function () { locked = 0; }, delay);
+      }
+   };
+};
+
+
+var slider = function(root, rtl) {
+  var IS_IPAD = /iPad/.test(navigator.userAgent) && !/CriOS/.test(navigator.userAgent);
+
+  var progress = common.lastChild(root),
+      rootClasses = ClassList(root),
+      progressClasses = ClassList(progress),
+      disabled,
+      offset,
+      width,
+      height,
+      vertical,
+      size,
+      maxValue,
+      max,
+      skipAnimation = false,
+
+      /* private */
+      calc = function() {
+         offset = common.offset(root);
+         width = common.width(root);
+         height = common.height(root);
+
+         /* exit from fullscreen can mess this up.*/
+         // vertical = height > width;
+
+         size = vertical ? height : width;
+         max = toDelta(maxValue);
+      },
+
+      fire = function(value) {
+         if (!disabled && value != api.value && (!maxValue || value < maxValue)) {
+            bean.fire(root, 'slide', [ value ]);
+            api.value = value;
+         }
+      },
+
+      mousemove = function(e) {
+         var pageX = e.pageX || e.clientX;
+         if (!pageX && e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) {
+            pageX = e.originalEvent.touches[0].pageX;
+         }
+         var delta = vertical ? e.pageY - offset.top : pageX - offset.left;
+         delta = Math.max(0, Math.min(max || size, delta));
+
+         var value = delta / size;
+         if (vertical) value = 1 - value;
+         if (rtl) value = 1 - value;
+         return move(value, 0, true);
+      },
+
+      move = function(value, speed) {
+         if (speed === undefined) { speed = 0; }
+         if (value > 1) value = 1;
+
+         var to = (Math.round(value * 1000) / 10) + "%";
+
+         if (!maxValue || value <= maxValue) {
+           progressClasses.remove('animated');
+            if (skipAnimation) {
+              progressClasses.remove('animated');
+            } else {
+              progressClasses.add('animated');
+              common.css(progress, 'transition-duration', (speed || 0) + 'ms');
+            }
+            common.css(progress, 'width', to);
+         }
+
+         return value;
+      },
+
+      toDelta = function(value) {
+         return Math.max(0, Math.min(size, vertical ? (1 - value) * height : value * width));
+      },
+
+      /* public */
+      api = {
+
+         max: function(value) {
+            maxValue = value;
+         },
+
+         disable: function(flag) {
+            disabled = flag;
+         },
+
+         slide: function(value, speed, fireEvent) {
+            calc();
+            if (fireEvent) fire(value);
+            move(value, speed);
+         },
+
+         // Should animation be handled via css
+         disableAnimation: function(value, alsoCssAnimations) {
+            skipAnimation = value !== false;
+            common.toggleClass(root, 'no-animation', !!alsoCssAnimations);
+         }
+
+      };
+
+  calc();
+
+  // bound dragging into document
+  bean.on(root, 'mousedown.sld touchstart', function(e) {
+    e.preventDefault();
+
+    if (!disabled) {
+      // begin --> recalculate. allows dynamic resizing of the slider
+      var delayedFire = throttle(fire, 100);
+      calc();
+      api.dragging = true;
+      rootClasses.add('is-dragging');
+      fire(mousemove(e));
+
+      bean.on(document, 'mousemove.sld touchmove.sld', function(e) {
+        e.preventDefault();
+        delayedFire(mousemove(e));
+
+      });
+      bean.one(document, 'mouseup touchend', function() {
+         api.dragging = false;
+         rootClasses.remove('is-dragging');
+         bean.off(document, 'mousemove.sld touchmove.sld');
+      });
+
+     }
+
+  });
+  return api;
+};
+
+module.exports = slider;
+
+},{"../common":1,"bean":20,"class-list":22}],15:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    common = _dereq_('../common'),
+    bean = _dereq_('bean'),
+    ClassList = _dereq_('class-list');
+
+flowplayer.defaults.subtitleParser = function(txt) {
+  var TIMECODE_RE = /^(([0-9]{2}:){1,2}[0-9]{2}[,.][0-9]{3}) --\> (([0-9]{2}:){1,2}[0-9]{2}[,.][0-9]{3})(.*)/;
+
+  function seconds(timecode) {
+     var els = timecode.split(':');
+     if (els.length == 2) els.unshift(0);
+     return els[0] * 60 * 60 + els[1] * 60 + parseFloat(els[2].replace(',','.'));
+  }
+
+  var entries = [];
+  for (var i = 0, lines = txt.split("\n"), len = lines.length, entry = {}, title, timecode, text, cue; i < len; i++) {
+    timecode = TIMECODE_RE.exec(lines[i]);
+
+    if (timecode) {
+
+      // title
+      title = lines[i - 1];
+
+      // text
+      text = "<p>" + lines[++i] + "</p><br/>";
+      while (typeof lines[++i] === 'string' && lines[i].trim() && i < lines.length) text +=  "<p>" + lines[i] + "</p><br/>";
+
+      // entry
+      entry = {
+        title: title,
+        startTime: seconds(timecode[1]),
+        endTime: seconds(timecode[3]),
+        text: text
+      };
+      entries.push(entry);
+    }
+  }
+  return entries;
+};
+
+flowplayer(function(p, root) {
+  var wrapClasses, currentPoint, wrap,
+      rootClasses = ClassList(root),
+      subtitleControl;
+
+  var createSubtitleControl = function() {
+    subtitleControl = common.createElement('a', {className: 'fp-menu'});
+    var menu = common.createElement('ul', {className: 'fp-dropdown fp-dropup'});
+    menu.appendChild(common.createElement('li', {'data-subtitle-index': -1}, 'No subtitles'));
+    (p.video.subtitles || []).forEach(function(st, i) {
+      var srcLang = st.srclang || 'en',
+          label = st.label || 'Default (' + srcLang + ')';
+      var item = common.createElement('li', {'data-subtitle-index': i}, label);
+      menu.appendChild(item);
+    });
+    subtitleControl.appendChild(menu);
+    common.find('.fp-controls', root)[0].appendChild(subtitleControl);
+    return subtitleControl;
+  };
+
+  bean.on(root, 'click', '.fp-menu', function(ev) {
+    ClassList(subtitleControl).toggle('dropdown-open');
+  });
+
+  bean.on(root, 'click', '.fp-menu li[data-subtitle-index]', function(ev) {
+    var idx = ev.target.getAttribute('data-subtitle-index');
+    if (idx === '-1') return p.disableSubtitles();
+    p.loadSubtitles(idx);
+  });
+
+  var createUIElements = function() {
+    var playerEl = common.find('.fp-player', root)[0];
+    wrap = common.find('.fp-subtitle', root)[0];
+    wrap = wrap || common.appendTo(common.createElement('div', {'class': 'fp-subtitle'}), playerEl);
+    Array.prototype.forEach.call(wrap.children, common.removeNode);
+    wrapClasses = ClassList(wrap);
+    common.find('.fp-menu', root).forEach(common.removeNode);
+    createSubtitleControl();
+  };
+
+
+  p.on('ready',  function(ev, player, video) {
+    var conf = player.conf;
+    if (flowplayer.support.subtitles && conf.nativesubtitles && player.engine.engineName == 'html5') {
+      var setMode = function(mode) {
+        var tracks = common.find('video', root)[0].textTracks;
+        if (!tracks.length) return;
+        tracks[0].mode = mode;
+      };
+      if (!video.subtitles || !video.subtitles.length) return;
+      var videoTag = common.find('video.fp-engine', root)[0];
+      video.subtitles.forEach(function(st) {
+        videoTag.appendChild(common.createElement('track', {
+          kind: 'subtitles',
+          srclang: st.srclang || 'en',
+          label: st.label || 'en',
+          src: st.src,
+          'default': st['default']
+        }));
+      });
+      setMode('disabled');
+      setMode('showing');
+      return;
+    }
+
+    player.subtitles = [];
+
+    createUIElements();
+
+    rootClasses.remove('has-menu');
+
+    p.disableSubtitles();
+
+    if (!video.subtitles || !video.subtitles.length) return;
+
+    rootClasses.add('has-menu');
+    var defaultSubtitle = video.subtitles.filter(function(one) {
+      return one['default'];
+    })[0];
+    if (defaultSubtitle) player.loadSubtitles(video.subtitles.indexOf(defaultSubtitle));
+  });
+
+  p.bind("cuepoint", function(e, api, cue) {
+    if (cue.subtitle) {
+       currentPoint = cue.index;
+       common.html(wrap, cue.subtitle.text);
+       wrapClasses.add('fp-active');
+    } else if (cue.subtitleEnd) {
+       wrapClasses.remove('fp-active');
+       currentPoint = cue.index;
+    }
+  });
+
+  p.bind("seek", function(e, api, time) {
+    // Clear future subtitles if seeking backwards
+    if (currentPoint && p.cuepoints[currentPoint] && p.cuepoints[currentPoint].time > time) {
+       wrapClasses.remove('fp-active');
+       currentPoint = null;
+    }
+    (p.cuepoints || []).forEach(function(cue) {
+      var entry = cue.subtitle;
+      //Trigger cuepoint if start time before seek position and end time nonexistent or in the future
+      if (entry && currentPoint != cue.index) {
+        if (time >= cue.time && (!entry.endTime || time <= entry.endTime)) p.trigger("cuepoint", [p, cue]);
+      } // Also handle cuepoints that act as the removal trigger
+      else if (cue.subtitleEnd && time >= cue.time && cue.index == currentPoint + 1) p.trigger("cuepoint", [p, cue]);
+    });
+
+  });
+
+  var setActiveSubtitleClass = function(idx) {
+    common.toggleClass(common.find('li.active', root)[0], 'active');
+    common.toggleClass(common.find('li[data-subtitle-index="' + idx + '"]', root)[0], 'active');
+  };
+
+  p.disableSubtitles = function() {
+    p.subtitles = [];
+    (p.cuepoints || []).forEach(function(c) {
+      if (c.subtitle || c.subtitleEnd) p.removeCuepoint(c);
+    });
+    if (wrap) Array.prototype.forEach.call(wrap.children, common.removeNode);
+    setActiveSubtitleClass(-1);
+    return p;
+  };
+
+  p.loadSubtitles = function(i) {
+    //First remove possible old subtitles
+    p.disableSubtitles();
+
+    var st = p.video.subtitles[i];
+
+    var url = st.src;
+    if (!url) return;
+    setActiveSubtitleClass(i);
+    common.xhrGet(url, function(txt) {
+      var entries = p.conf.subtitleParser(txt);
+      entries.forEach(function(entry) {
+        var cue = { time: entry.startTime, subtitle: entry, visible: false };
+        p.subtitles.push(entry);
+        p.addCuepoint(cue);
+        p.addCuepoint({ time: entry.endTime, subtitleEnd: entry.title, visible: false });
+
+        // initial cuepoint
+        if (entry.startTime === 0 && !p.video.time) {
+          p.trigger("cuepoint", [p, cue]);
+        }
+      });
+    }, function() {
+      p.trigger("error", {code: 8, url: url });
+      return false;
+    });
+    return p;
+  };
+});
+
+
+},{"../common":1,"../flowplayer":18,"bean":20,"class-list":22}],16:[function(_dereq_,module,exports){
+'use strict';
+/* global ActiveXObject */
+var flowplayer = _dereq_('../flowplayer'),
+    extend = _dereq_('extend-object');
+(function() {
+
+   var parseIpadVersion = function(UA) {
+      var e = /Version\/(\d\.\d)/.exec(UA);
+      if (e && e.length > 1) {
+         return parseFloat(e[1], 10);
+      }
+      return 0;
+   };
+
+   var createVideoTag = function() {
+     var videoTag = document.createElement('video');
+     videoTag.loop = true;
+     videoTag.autoplay = true;
+     videoTag.preload = true;
+     return videoTag;
+   };
+
+    var b = {},
+      ua = navigator.userAgent.toLowerCase(),
+      match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+      /(safari)[ \/]([\w.]+)/.exec(ua) ||
+      /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+      /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+      /(msie) ([\w.]+)/.exec(ua) ||
+      ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || [];
+
+   if (match[1]) {
+      b[match[1]] = true;
+      b.version = match[2] || "0";
+   }
+
+   var video = createVideoTag(),
+      UA = navigator.userAgent,
+      IS_IE = b.msie || /Trident\/7/.test(UA),
+      IS_IPAD = /iPad|MeeGo/.test(UA) && !/CriOS/.test(UA),
+      IS_IPAD_CHROME = /iPad/.test(UA) && /CriOS/.test(UA),
+      IS_IPHONE = /iP(hone|od)/i.test(UA) && !/iPad/.test(UA) && !/IEMobile/i.test(UA),
+      IS_ANDROID = /Android/.test(UA) && !/Firefox/.test(UA),
+      IS_ANDROID_FIREFOX = /Android/.test(UA) && /Firefox/.test(UA),
+      IS_SILK = /Silk/.test(UA),
+      IS_WP = /IEMobile/.test(UA),
+      WP_VER = IS_WP ? parseFloat(/Windows\ Phone\ (\d+\.\d+)/.exec(UA)[1], 10) : 0,
+      IE_MOBILE_VER = IS_WP ? parseFloat(/IEMobile\/(\d+\.\d+)/.exec(UA)[1], 10) : 0,
+      IPAD_VER = IS_IPAD ? parseIpadVersion(UA) : 0,
+      ANDROID_VER = IS_ANDROID ? parseFloat(/Android\ (\d\.\d)/.exec(UA)[1], 10) : 0,
+      s = extend(flowplayer.support, {
+
+        browser: b,
+        subtitles: !!video.addTextTrack,
+        fullscreen: typeof document.webkitCancelFullScreen == 'function' && !/Mac OS X 10_5.+Version\/5\.0\.\d Safari/.test(UA) ||
+              document.mozFullScreenEnabled ||
+              typeof document.exitFullscreen == 'function' ||
+              typeof document.msExitFullscreen == 'function',
+        inlineBlock: !(IS_IE && b.version < 8),
+        touch: ('ontouchstart' in window),
+        dataload: !IS_IPAD && !IS_IPHONE && !IS_WP,
+        zeropreload: !IS_IE && !IS_ANDROID, // IE supports only preload=metadata
+        volume: !IS_IPAD && !IS_ANDROID && !IS_IPHONE && !IS_SILK && !IS_IPAD_CHROME,
+        cachedVideoTag: !IS_IPAD && !IS_IPHONE && !IS_IPAD_CHROME && !IS_WP,
+        firstframe: !IS_IPHONE && !IS_IPAD && !IS_ANDROID && !IS_SILK && !IS_IPAD_CHROME && !IS_WP && !IS_ANDROID_FIREFOX,
+        inlineVideo: !IS_IPHONE && (!IS_WP || (WP_VER >= 8.1 && IE_MOBILE_VER >= 11)) && (!IS_ANDROID || ANDROID_VER >= 3),
+        hlsDuration: !IS_ANDROID && (!b.safari || IS_IPAD || IS_IPHONE || IS_IPAD_CHROME),
+        seekable: !IS_IPAD && !IS_IPAD_CHROME
+    });
+
+   // flashVideo
+   try {
+      var plugin = navigator.plugins["Shockwave Flash"],
+          ver = IS_IE ? new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable('$version') : plugin.description;
+      if (!IS_IE && !plugin[0].enabledPlugin) s.flashVideo = false;
+      else {
+
+         ver = ver.split(/\D+/);
+         if (ver.length && !ver[0]) ver = ver.slice(1);
+
+         s.flashVideo = ver[0] > 9 || ver[0] == 9 && ver[3] >= 115;
+      }
+
+   } catch (ignored) {}
+   try {
+      s.video = !!video.canPlayType;
+      if (s.video) video.canPlayType('video/mp4');
+   } catch (e) {
+      s.video = false;
+   }
+
+   // animation
+   s.animation = (function() {
+      var vendors = ['','Webkit','Moz','O','ms','Khtml'], el = document.createElement('p');
+
+      for (var i = 0; i < vendors.length; i++) {
+         if (typeof el.style[vendors[i] + 'AnimationName'] !== 'undefined') return true;
+      }
+   })();
+
+
+
+})();
+
+
+},{"../flowplayer":18,"extend-object":26}],17:[function(_dereq_,module,exports){
+'use strict';
+var flowplayer = _dereq_('../flowplayer'),
+    common = _dereq_('../common'),
+    ClassList = _dereq_('class-list'),
+    bean = _dereq_('bean'),
+    slider = _dereq_('./slider');
+
+function zeropad(val) {
+   val = parseInt(val, 10);
+   return val >= 10 ? val : "0" + val;
+}
+
+// display seconds in hh:mm:ss format
+function format(sec) {
+
+   sec = sec || 0;
+
+   var h = Math.floor(sec / 3600),
+       min = Math.floor(sec / 60);
+
+   sec = sec - (min * 60);
+
+   if (h >= 1) {
+      min -= h * 60;
+      return h + ":" + zeropad(min) + ":" + zeropad(sec);
+   }
+
+   return zeropad(min) + ":" + zeropad(sec);
+}
+
+flowplayer(function(api, root) {
+
+   var conf = api.conf,
+      support = flowplayer.support,
+      hovertimer,
+      rootClasses = ClassList(root);
+   common.find('.fp-ratio,.fp-ui', root).forEach(common.removeNode);
+   rootClasses.add('flowplayer');
+   root.appendChild(common.createElement('div', {className: 'fp-ratio'}));
+   var ui = common.createElement('div', {className: 'fp-ui'}, '\
+         <div class="waiting"><em></em><em></em><em></em></div>\
+         <a class="fullscreen"></a>\
+         <a class="unload"></a>\
+         <p class="speed"></p>\
+         <div class="controls">\
+            <a class="play"></a>\
+            <div class="timeline">\
+               <div class="buffer"></div>\
+               <div class="progress"></div>\
+            </div>\
+            <div class="timeline-tooltip fp-tooltip"></div>\
+            <div class="volume">\
+               <a class="mute"></a>\
+               <div class="volumeslider">\
+                  <div class="volumelevel"></div>\
+               </div>\
+            </div>\
+         </div>\
+         <div class="time">\
+            <em class="elapsed">00:00</em>\
+            <em class="remaining"></em>\
+            <em class="duration">00:00</em>\
+         </div>\
+         <div class="message"><h2></h2><p></p></div>'.replace(/class="/g, 'class="fp-'));
+   root.appendChild(ui);
+   function find(klass) {
+     return common.find(".fp-" + klass, root)[0];
+   }
+
+   // widgets
+   var progress = find("progress"),
+      buffer = find("buffer"),
+      elapsed = find("elapsed"),
+      remaining = find("remaining"),
+      waiting = find("waiting"),
+      ratio = find("ratio"),
+      speed = find("speed"),
+      speedClasses = ClassList(speed),
+      durationEl = find("duration"),
+      controls = find('controls'),
+      timelineTooltip = find('timeline-tooltip'),
+      origRatio = common.css(ratio, 'padding-top'),
+
+      // sliders
+      timeline = find("timeline"),
+      timelineApi = slider(timeline, api.rtl),
+
+      volume = find("volume"),
+      fullscreen = find("fullscreen"),
+      volumeSlider = find("volumeslider"),
+      volumeApi = slider(volumeSlider, api.rtl),
+      noToggle = rootClasses.contains('fixed-controls') || rootClasses.contains('no-toggle');
+
+   timelineApi.disableAnimation(rootClasses.contains('is-touch'));
+   api.sliders = api.sliders || {};
+   api.sliders.timeline = timelineApi;
+   api.sliders.volume = volumeApi;
+
+   // aspect ratio
+   function setRatio(val) {
+     common.css(ratio, 'padding-top', val * 100 + "%");
+     if (!support.inlineBlock) common.height(common.find('object', root)[0], common.height(root));
+   }
+
+   function hover(flag) {
+     if (flag) {
+       rootClasses.add('is-mouseover');
+       rootClasses.remove('is-mouseout');
+     } else {
+       rootClasses.add('is-mouseout');
+       rootClasses.remove('is-mouseover');
+     }
+   }
+
+   // loading...
+   if (!support.animation) common.html(waiting, "<p>loading &hellip;</p>");
+
+   if (conf.ratio) setRatio(conf.ratio);
+
+   // no fullscreen in IFRAME
+   try {
+      if (!conf.fullscreen) common.removeNode(fullscreen);
+
+   } catch (e) {
+      common.removeNode(fullscreen);
+   }
+
+   api.on("ready", function(ev, api, video) {
+
+      var duration = api.video.duration;
+
+      timelineApi.disable(api.disabled || !duration);
+
+      if (conf.adaptiveRatio && !isNaN(video.height / video.width)) setRatio(video.height / video.width, true);
+
+      // initial time & volume
+      common.html([durationEl, remaining], format(duration));
+
+      // do we need additional space for showing hour
+      common.toggleClass(root, 'is-long', duration >= 3600);
+      volumeApi.slide(api.volumeLevel);
+
+      if (api.engine.engineName === 'flash') timelineApi.disableAnimation(true, true);
+      else timelineApi.disableAnimation(false);
+      common.find('.fp-title', ui).forEach(common.removeNode);
+      if (video.title) {
+        common.prepend(ui, common.createElement('div', {
+          className: 'fp-title'
+        }, video.title));
+      }
+
+
+   }).on("unload", function() {
+     if (!origRatio) common.css(ratio, "paddingTop", "");
+     timelineApi.slide(0);
+
+   // buffer
+   }).on("buffer", function() {
+      var video = api.video,
+         max = video.buffer / video.duration;
+
+      if (!video.seekable && support.seekable) timelineApi.max(max);
+      if (max < 1) common.css(buffer, "width", (max * 100) + "%");
+      else common.css(buffer, 'width', '100%');
+
+   }).on("speed", function(e, api, val) {
+     common.text(speed, val + "x");
+     speedClasses.add('fp-hilite');
+     setTimeout(function() { speedClasses.remove('fp-hilite'); }, 1000);
+
+   }).on("buffered", function() {
+     common.css(buffer, 'width', '100%');
+      timelineApi.max(1);
+
+   // progress
+   }).on("progress", function() {
+
+      var time = api.video.time,
+         duration = api.video.duration;
+
+      if (!timelineApi.dragging) {
+        timelineApi.slide(time / duration, api.seeking ? 0 : 250);
+      }
+
+      common.html(elapsed, format(time));
+      common.html(remaining, '-' + format(duration - time));
+
+   }).on("finish resume seek", function(e) {
+      common.toggleClass(root, "is-finished", e.type == "finish");
+
+   }).on("stop", function() {
+      common.html(elapsed, format(0));
+      timelineApi.slide(0, 100);
+
+   }).on("finish", function() {
+      common.html(elapsed, format(api.video.duration));
+      timelineApi.slide(1, 100);
+      rootClasses.remove('is-seeking');
+
+   // misc
+   }).on("beforeseek", function() {
+      //TODO FIXME
+      //progress.stop();
+
+   }).on("volume", function() {
+      volumeApi.slide(api.volumeLevel);
+
+
+   }).on("disable", function() {
+      var flag = api.disabled;
+      timelineApi.disable(flag);
+      volumeApi.disable(flag);
+      common.toggleClass(root, 'is-disabled', api.disabled);
+
+   }).on("mute", function(e, api, flag) {
+      common.toggleClass(root, 'is-muted', flag);
+
+   }).on("error", function(e, api, error) {
+      common.removeClass(root, 'is-loading');
+      common.addClass(root, 'is-error');
+      if (error) {
+         error.message = conf.errors[error.code];
+         api.error = true;
+
+         var el = common.find('.fp-message', root)[0],
+             video = error.video || api.video;
+         common.find('h2', el)[0].innerHTML = (api.engine && api.engine.engineName || 'html5') + ": " + error.message;
+         common.find('p', el)[0].innerHTML = error.url || video.url || video.src || conf.errorUrls[error.code];
+         api.off("mouseenter click");
+         rootClasses.remove('is-mouseover');
+      }
+
+
+   // hover
+   });
+   //Interaction events
+   bean.on(root, "mouseenter mouseleave", function(e) {
+     if (noToggle) return;
+
+      var is_over = e.type == "mouseover",
+         lastMove;
+
+      // is-mouseover/out
+      hover(is_over);
+
+      if (is_over) {
+
+         var reg = function() {
+            hover(true);
+            lastMove = new Date();
+         };
+         api.on("pause.x volume.x", reg);
+         bean.on(root, 'mousemove.x', reg);
+
+         hovertimer = setInterval(function() {
+            if (new Date() - lastMove > conf.mouseoutTimeout) {
+               hover(false);
+               lastMove = new Date();
+            }
+         }, 100);
+
+      } else {
+         bean.off(root, 'mousemove.x');
+         api.off("pause.x volume.x");
+         clearInterval(hovertimer);
+      }
+
+
+   // allow dragging over the player edge
+   });
+   bean.on(root, "mouseleave", function() {
+
+     if (timelineApi.dragging || volumeApi.dragging) {
+       rootClasses.add('is-mouseover');
+       rootClasses.remove('is-mouseout');
+     }
+
+   // click
+   });
+   bean.on(root, "click.player", function(e) {
+     if (api.disabled) return;
+     var kls = ClassList(e.target);
+      if (kls.contains('fp-ui') || kls.contains('fp-engine') || e.flash) {
+         if (e.preventDefault) e.preventDefault();
+         return api.toggle();
+      }
+   });
+
+   bean.on(root, 'mousemove', '.fp-timeline', function(ev) {
+     var x = ev.pageX || ev.clientX,
+         delta = x - common.offset(timeline).left,
+         percentage = delta / common.width(timeline),
+         seconds = percentage * api.video.duration;
+     if (percentage < 0) return;
+     common.html(timelineTooltip, format(seconds));
+     common.css(timelineTooltip, 'left', (x - common.offset(controls).left - common.width(timelineTooltip) / 2) + 'px');
+   });
+
+   bean.on(root, 'contextmenu', function(ev) {
+      var o = common.offset(common.find('.fp-player', root)[0]),
+          w = window,
+          left = ev.clientX - o.left,
+          t = ev.clientY - (o.top + w.scrollY);
+      var menu = common.find('.fp-context-menu', root)[0];
+      if (!menu) return;
+      ev.preventDefault();
+      common.css(menu,
+      {left: left + 'px',
+         top: t + 'px',
+         display: 'block'
+      });
+      bean.on(root, 'click', '.fp-context-menu', function(ev) {
+         ev.stopPropagation();
+      });
+      bean.on(document, 'click.outsidemenu', function(ev) {
+         common.css(menu, 'display', 'none');
+         bean.off(document, 'click.outsidemenu');
+      });
+   });
+   api.on('flashdisabled', function() {
+     rootClasses.add('is-flash-disabled');
+     api.one('ready', function() {
+       rootClasses.remove('is-flash-disabled');
+       common.find('.fp-flash-disabled', root).forEach(common.removeNode);
+     });
+     root.appendChild(common.createElement('div', {className: "fp-flash-disabled"}, 'Adobe Flash is disabled for this page, click player area to enable'));
+   });
+
+   // poster -> background image
+   if (conf.poster) common.css(root, 'background-image', "url(" + conf.poster + ")");
+
+   var bc = common.css(root, 'background-color'),
+      has_bg = common.css(root, 'background-image') != "none" || bc && bc != "rgba(0, 0, 0, 0)" && bc != "transparent";
+
+   // is-poster class
+   if (has_bg && !conf.splash && !conf.autoplay) {
+
+      api.on("ready stop", function() {
+         rootClasses.add("is-poster");
+         api.one("progress", function() {
+            rootClasses.remove("is-poster");
+         });
+      });
+
+   }
+
+   if (typeof conf.splash === 'string') {
+     common.css(root, 'background-image', "url('" + conf.splash + "')");
+   }
+
+   // default background color if not present
+   if (!has_bg && api.forcedSplash) {
+      common.css(root, "background-color", "#555");
+   }
+
+   bean.on(root, 'click', '.fp-toggle, .fp-play', function() {
+     if (api.disabled) return;
+     api.toggle();
+   });
+
+   /* controlbar elements */
+   bean.on(root, 'click', '.fp-mute', function() { api.mute(); });
+   bean.on(root, 'click', '.fp-fullscreen', function() { api.fullscreen(); });
+   bean.on(root, 'click', '.fp-unload', function() { api.unload(); });
+
+   bean.on(timeline, 'slide', function(val) {
+     api.seeking = true;
+     api.seek(val * api.video.duration);
+   });
+
+   bean.on(volumeSlider, 'slide', function(val) {
+      api.volume(val);
+   });
+
+   // times
+
+   var time = find('time');
+   bean.on(root, 'click', '.fp-time', function() {
+     ClassList(time).toggle('is-inverted');
+   });
+
+   hover(noToggle);
+
+   api.on('shutdown', function() {
+     bean.off(timeline);
+     bean.off(volumeSlider);
+   });
+
+});
+
+
+module.exports.format = format;
+
+},{"../common":1,"../flowplayer":18,"./slider":14,"bean":20,"class-list":22}],18:[function(_dereq_,module,exports){
+'use strict';
+var extend = _dereq_('extend-object'),
+    isFunction = _dereq_('is-function'),
+    ClassList = _dereq_('class-list'),
+    bean = _dereq_('bean'),
+    common = _dereq_('./common'),
+    events = _dereq_('./ext/events');
+
+var instances = [],
+   extensions = [],
+   UA = window.navigator.userAgent;
+
+
+var oldHandler = window.onbeforeunload;
+window.onbeforeunload = function(ev) {
+  instances.forEach(function(api) {
+    if (api.conf.splash) {
+      api.unload();
+    } else {
+      api.bind("error", function () {
+        common.find('.flowplayer.is-error .fp-message').forEach(common.removeNode);
+      });
+    }
+  });
+  if (oldHandler) return oldHandler(ev);
+};
+
+var supportLocalStorage = false;
+try {
+  if (typeof window.localStorage == "object") {
+    window.localStorage.flowplayerTestStorage = "test";
+    supportLocalStorage = true;
+  }
+} catch (ignored) {}
+
+var isSafari = /Safari/.exec(navigator.userAgent) && !/Chrome/.exec(navigator.userAgent),
+    m = /(\d+\.\d+) Safari/.exec(navigator.userAgent),
+    safariVersion = m ? Number(m[1]) : 100;
+
+/* flowplayer()  */
+var flowplayer = module.exports = function(fn, opts, callback) {
+  if (isFunction(fn)) return extensions.push(fn);
+  if (typeof fn == 'number' || typeof fn === 'undefined') return instances[fn || 0];
+  if (fn.nodeType) { // Is an element
+    if (fn.getAttribute('data-flowplayer-instance-id') !== null) { // Already flowplayer instance
+      return instances[fn.getAttribute('data-flowplayer-instance-id')];
+    }
+    if (!opts) return; // Can't initialize without data
+    return initializePlayer(fn, opts, callback);
+  }
+  if (fn.jquery) return flowplayer(fn[0], opts, callback);
+  if (typeof fn === 'string') {
+    var el = common.find(fn)[0];
+    return el && flowplayer(el, opts, callback);
+  }
+};
+
+extend(flowplayer, {
+
+   version: '6.0.3',
+
+   engines: [],
+
+   conf: {},
+
+   set: function(key, value) {
+      if (typeof key === 'string') flowplayer.conf[key] = value;
+      else extend(flowplayer.conf, key);
+   },
+
+   support: {},
+
+   defaults: {
+
+      debug: supportLocalStorage ? !!localStorage.flowplayerDebug : false,
+
+      // true = forced playback
+      disabled: false,
+
+      fullscreen: window == window.top,
+
+      // keyboard shortcuts
+      keyboard: true,
+
+      // default aspect ratio
+      ratio: 9 / 16,
+
+      adaptiveRatio: false,
+
+      rtmp: 0,
+
+      proxy: 'best',
+
+      splash: false,
+
+      live: false,
+
+      swf: "//releases.flowplayer.org/6.0.3/flowplayer.swf",
+      swfHls: "//releases.flowplayer.org/6.0.3/flowplayerhls.swf",
+
+      speeds: [0.25, 0.5, 1, 1.5, 2],
+
+      tooltip: true,
+
+      mouseoutTimeout: 5000,
+
+      // initial volume level
+      volume: !supportLocalStorage ? 1 : localStorage.muted == "true" ? 0 : !isNaN(localStorage.volume) ? localStorage.volume || 1 : 1,
+
+      // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
+      errors: [
+
+         // video exceptions
+         '',
+         'Video loading aborted',
+         'Network error',
+         'Video not properly encoded',
+         'Video file not found',
+
+         // player exceptions
+         'Unsupported video',
+         'Skin not found',
+         'SWF file not found',
+         'Subtitles not found',
+         'Invalid RTMP URL',
+         'Unsupported video format. Try installing Adobe Flash.'
+      ],
+      errorUrls: ['','','','','','','','','','',
+         'http://get.adobe.com/flashplayer/'
+      ],
+      playlist: [],
+
+      hlsFix: isSafari && safariVersion < 8
+
+   },
+   // Expose utilities for plugins
+   bean: bean,
+   common: common,
+   extend: extend
+
+
+
+});
+
+// keep track of players
+var playerCount = 0;
+
+var URLResolver = _dereq_('./ext/resolve');
+
+
+
+if (typeof window.jQuery !== 'undefined') {
+  var $ = window.jQuery;
+  // auto-install (any video tag with parent .flowplayer)
+  $(function() {
+     if (typeof $.fn.flowplayer == 'function') {
+        $('.flowplayer:has(video,script[type="application/json"])').flowplayer();
+     }
+  });
+
+  // jQuery plugin
+  var videoTagConfig = function(videoTag) {
+    if (!videoTag.length) return {};
+    var clip = videoTag.data() || {}, conf = {};
+    $.each(['autoplay', 'loop', 'preload', 'poster'], function(i, key) {
+      var val = videoTag.attr(key);
+      if (val !== undefined && ['autoplay', 'poster'].indexOf(key) !== -1) conf[key] = val ? val : true;
+      else if (val !== undefined) clip[key] = val ? val : true;
+    });
+    clip.subtitles = videoTag.find('track').map(function() {
+      var tr = $(this);
+      return {
+        src: tr.attr('src'),
+        kind: tr.attr('kind'),
+        label: tr.attr('label'),
+        srclang: tr.attr('srclang'),
+        'default': tr.prop('default')
+      };
+    }).get();
+
+    clip.sources = (new URLResolver()).sourcesFromVideoTag(videoTag, $);
+    return extend(conf, {clip: clip});
+  };
+  $.fn.flowplayer = function(opts, callback) {
+    return this.each(function() {
+      if (typeof opts == 'string') opts = { swf: opts };
+      if (isFunction(opts)) { callback = opts; opts = {}; }
+      var root = $(this),
+          scriptConf = root.find('script[type="application/json"]'),
+          confObject = scriptConf.length ? JSON.parse(scriptConf.text()) : videoTagConfig(root.find('video')),
+          conf = $.extend({}, opts || {}, confObject, root.data());
+      var api = initializePlayer(this, conf, callback);
+      events.EVENTS.forEach(function(evName) {
+        api.on(evName + '.jquery', function(ev) {
+          root.trigger.call(root, ev.type, ev.detail && ev.detail.args);
+        });
+      });
+      root.data('flowplayer', api);
+    });
+  };
+}
+
+function initializePlayer(element, opts, callback) {
+  if (opts && opts.embed) opts.embed = extend({}, flowplayer.defaults.embed, opts.embed);
+
+  var root = element,
+      rootClasses = ClassList(root),
+      conf = extend({}, flowplayer.defaults, flowplayer.conf, opts),
+      storage = {},
+      lastSeekPosition,
+      engine,
+      url,
+      urlResolver = new URLResolver();
+
+      rootClasses.add('is-loading');
+
+      try {
+         storage = supportLocalStorage ? window.localStorage : storage;
+      } catch(e) {}
+
+      var isRTL = (root.currentStyle && root.currentStyle.direction === 'rtl') ||
+        (window.getComputedStyle && window.getComputedStyle(root, null) !== null && window.getComputedStyle(root, null).getPropertyValue('direction') === 'rtl');
+
+      if (isRTL) rootClasses.add('is-rtl');
+
+      /*** API ***/
+      var api = {
+
+         // properties
+         conf: conf,
+         currentSpeed: 1,
+         volumeLevel: conf.muted ? 0 : typeof conf.volume === "undefined" ? storage.volume * 1 : conf.volume,
+         video: {},
+
+         // states
+         disabled: false,
+         finished: false,
+         loading: false,
+         muted: storage.muted == "true" || conf.muted,
+         paused: false,
+         playing: false,
+         ready: false,
+         splash: false,
+         rtl: isRTL,
+
+         // methods
+         load: function(video, callback) {
+
+            if (api.error || api.loading) return;
+            api.video = {};
+
+            api.finished = false;
+
+            video = video || conf.clip;
+
+            // resolve URL
+            video = extend({}, urlResolver.resolve(video, conf.clip.sources));
+            if (api.playing || api.engine) video.autoplay = true;
+            var engineImpl = selectEngine(video);
+            if (!engineImpl) return api.trigger("error", [api, { code: flowplayer.support.flashVideo ? 5 : 10 }]);
+            if (!engineImpl.engineName) throw new Error('engineName property of factory should be exposed');
+            if (!api.engine || engineImpl.engineName !== api.engine.engineName) {
+              api.ready = false;
+              if (api.engine) {
+                api.engine.unload();
+                api.conf.autoplay = true;
+              }
+              engine = api.engine = engineImpl(api, root);
+              api.one('ready', function() {
+                engine.volume(api.volumeLevel);
+              });
+            }
+
+            extend(video, engine.pick(video.sources.filter(function(source) { // Filter out sources explicitely configured for some other engine
+              if (!source.engine) return true;
+              return source.engine === engine.engineName;
+            })));
+
+            if (video.src) {
+               video.src = common.createElement('a', {href: video.src}).href;
+               var e = api.trigger('load', [api, video, engine], true);
+               if (!e.defaultPrevented) {
+                  engine.load(video);
+
+                  // callback
+                  if (isFunction(video)) callback = video;
+                  if (callback) api.one("ready", callback);
+               } else {
+                  api.loading = false;
+               }
+            }
+
+            return api;
+         },
+
+         pause: function(fn) {
+            if (api.ready && !api.seeking && !api.loading) {
+               engine.pause();
+               api.one("pause", fn);
+            }
+            return api;
+         },
+
+         resume: function() {
+
+            if (api.ready && api.paused) {
+               engine.resume();
+
+               // Firefox (+others?) does not fire "resume" after finish
+               if (api.finished) {
+                  api.trigger("resume", [api]);
+                  api.finished = false;
+               }
+            }
+
+            return api;
+         },
+
+         toggle: function() {
+            return api.ready ? api.paused ? api.resume() : api.pause() : api.load();
+         },
+
+         /*
+            seek(1.4)   -> 1.4s time
+            seek(true)  -> 10% forward
+            seek(false) -> 10% backward
+         */
+         seek: function(time, callback) {
+            if (api.ready && !api.live) {
+
+               if (typeof time == "boolean") {
+                  var delta = api.video.duration * 0.1;
+                  time = api.video.time + (time ? delta : -delta);
+               }
+               time = lastSeekPosition = Math.min(Math.max(time, 0), api.video.duration).toFixed(1);
+               var ev = api.trigger('beforeseek', [api, time], true);
+               if (!ev.defaultPrevented) {
+                  engine.seek(time);
+                  if (isFunction(callback)) api.one("seek", callback);
+               } else {
+                  api.seeking = false;
+                  common.toggleClass(root, 'is-seeking', api.seeking); // remove loading indicator
+               }
+            }
+            return api;
+         },
+
+         /*
+            seekTo(1) -> 10%
+            seekTo(2) -> 20%
+            seekTo(3) -> 30%
+            ...
+            seekTo()  -> last position
+         */
+         seekTo: function(position, fn) {
+            var time = position === undefined ? lastSeekPosition : api.video.duration * 0.1 * position;
+            return api.seek(time, fn);
+         },
+
+         mute: function(flag, skipStore) {
+           if (flag === undefined) flag = !api.muted;
+           if (!skipStore) {
+             storage.muted = api.muted = flag;
+             storage.volume = !isNaN(storage.volume) ? storage.volume : conf.volume; // make sure storage has volume
+           }
+           api.volume(flag ? 0 : storage.volume, true);
+           api.trigger("mute", [api, flag]);
+           return api;
+         },
+
+         volume: function(level, skipStore) {
+            if (api.ready) {
+              level = Math.min(Math.max(level, 0), 1);
+              if (!skipStore) storage.volume = level;
+              engine.volume(level);
+            }
+
+            return api;
+         },
+
+         speed: function(val, callback) {
+
+            if (api.ready) {
+
+               // increase / decrease
+               if (typeof val == "boolean") {
+                  val = conf.speeds[conf.speeds.indexOf(api.currentSpeed) + (val ? 1 : -1)] || api.currentSpeed;
+               }
+
+               engine.speed(val);
+               if (callback) root.one("speed", callback);
+            }
+
+            return api;
+         },
+
+
+         stop: function() {
+            if (api.ready) {
+               api.pause();
+               api.seek(0, function() {
+                  api.trigger("stop");
+               });
+            }
+            return api;
+         },
+
+         unload: function() {
+            if (!rootClasses.contains("is-embedding")) {
+
+               if (conf.splash) {
+                  api.trigger("unload", [api]);
+                  if (engine) engine.unload();
+               } else {
+                  api.stop();
+               }
+            }
+            return api;
+         },
+
+         shutdown: function() {
+           api.unload();
+           api.trigger('shutdown', [api]);
+           bean.off(root);
+           delete instances[root.getAttribute('data-flowplayer-instance-id')];
+         },
+
+         disable: function(flag) {
+            if (flag === undefined) flag = !api.disabled;
+
+            if (flag != api.disabled) {
+               api.disabled = flag;
+               api.trigger("disable", flag);
+            }
+            return api;
+         }
+
+      };
+
+      api.conf = extend(api.conf, conf);
+
+      /* event binding / unbinding */
+      events(api);
+
+      var selectEngine = function(clip) {
+        var engine;
+        var engines = flowplayer.engines;
+        if (conf.engine) {
+          var eng = engines.filter(function(e) { return e.engineName === conf.engine; })[0];
+          if (eng && clip.sources.some(function(source) {
+            if (source.engine && source.engine !== eng.engineName) return false;
+            return eng.canPlay(source.type, api.conf);
+          })) return eng;
+        }
+        if (conf.enginePreference) engines = flowplayer.engines.filter(function(one) { return conf.enginePreference.indexOf(one.engineName) > -1; }).sort(function(a, b) {
+          return conf.enginePreference.indexOf(a.engineName) - conf.enginePreference.indexOf(b.engineName);
+        });
+        clip.sources.some(function(source) {
+          var eng = engines.filter(function(engine) {
+            if (source.engine && source.engine !== engine.engineName) return false;
+            return engine.canPlay(source.type, api.conf);
+          }).shift();
+          if (eng) engine = eng;
+          return !!eng;
+        });
+        return engine;
+      };
+
+      /*** Behaviour ***/
+      if (!root.getAttribute('data-flowplayer-instance-id')) { // Only bind once
+         root.setAttribute('data-flowplayer-instance-id', playerCount++);
+
+
+         api.on('boot', function() {
+
+            // splash
+            if (conf.splash || rootClasses.contains("is-splash") || !flowplayer.support.firstframe) {
+               api.forcedSplash = !conf.splash && !rootClasses.contains("is-splash");
+               api.splash = conf.autoplay = true;
+               if (!conf.splash) conf.splash = true;
+               rootClasses.add("is-splash");
+            }
+
+            if (conf.splash) common.find('video', root).forEach(common.removeNode);
+
+            if (conf.live || rootClasses.contains('is-live')) {
+               api.live = conf.live = true;
+               rootClasses.add('is-live');
+            }
+
+            // extensions
+            extensions.forEach(function(e) {
+               e(api, root);
+            });
+
+            // instances
+            instances.push(api);
+
+            // start
+            if (conf.splash) api.unload(); else api.load();
+
+            // disabled
+            if (conf.disabled) api.disable();
+
+            // initial callback
+            api.one("ready", callback);
+
+
+         }).on("load", function(e, api, video) {
+
+            // unload others
+            if (conf.splash) {
+              common.find('.flowplayer.is-ready,.flowplayer.is-loading').forEach(function(el) {
+                var playerId = el.getAttribute('data-flowplayer-instance-id');
+                if (playerId === root.getAttribute('data-flowplayer-instance-id')) return;
+                var a = instances[Number(playerId)];
+                if (a && a.conf.splash) a.unload();
+              });
+
+            }
+
+            // loading
+            rootClasses.add("is-loading");
+            api.loading = true;
+
+            if (typeof video.live !== 'undefined') {
+              common.toggleClass(root, 'is-live', video.live);
+              api.live = video.live;
+            }
+
+
+         }).on("ready", function(e, api, video) {
+            video.time = 0;
+            api.video = video;
+
+            rootClasses.remove("is-loading");
+            api.loading = false;
+
+            // saved state
+            if (api.muted) api.mute(true, true);
+            else api.volume(api.volumeLevel);
+
+            // see https://github.com/flowplayer/flowplayer/issues/479
+
+            var hlsFix = api.conf.hlsFix && /mpegurl/i.exec(video.type);
+            common.toggleClass(root, 'hls-fix', !!hlsFix);
+
+         }).on("unload", function(e) {
+            rootClasses.remove("is-loading");
+            api.loading = false;
+
+
+         }).on("ready unload", function(e) {
+           var is_ready = e.type == "ready";
+           common.toggleClass(root, 'is-splash', !is_ready);
+           common.toggleClass(root, 'is-ready', is_ready);
+           api.ready = is_ready;
+           api.splash = !is_ready;
+
+
+         }).on("progress", function(e, api, time) {
+            api.video.time = time;
+
+
+         }).on("speed", function(e, api, val) {
+            api.currentSpeed = val;
+
+         }).on("volume", function(e, api, level) {
+            api.volumeLevel = Math.round(level * 100) / 100;
+            if (!api.muted) storage.volume = level;
+            else if (level) api.mute(false);
+
+
+         }).on("beforeseek seek", function(e) {
+            api.seeking = e.type == "beforeseek";
+            common.toggleClass(root, 'is-seeking', api.seeking);
+
+         }).on("ready pause resume unload finish stop", function(e, _api, video) {
+
+            // PAUSED: pause / finish
+            api.paused = /pause|finish|unload|stop/.test(e.type);
+            api.paused = api.paused || e.type === 'ready' && !conf.autoplay && !api.playing;
+
+            // the opposite
+            api.playing = !api.paused;
+
+            // CSS classes
+            common.toggleClass(root, 'is-paused', api.paused);
+            common.toggleClass(root, 'is-playing', api.playing);
+
+            // sanity check
+            if (!api.load.ed) api.pause();
+
+         }).on("finish", function(e) {
+            api.finished = true;
+
+         }).on("error", function() {
+         });
+      }
+
+      // boot
+      api.trigger('boot', [api, root]);
+  return api;
+}
+
+},{"./common":1,"./ext/events":8,"./ext/resolve":13,"bean":20,"class-list":22,"extend-object":26,"is-function":27}],19:[function(_dereq_,module,exports){
+//Flowplayer with extensions
+
+_dereq_('es5-shim');
+
+var flowplayer = module.exports = _dereq_('./flowplayer');
+//
+
+//Support needed before engines
+_dereq_('./ext/support');
+
+//Engines
+_dereq_('./engine/embed');
+_dereq_('./engine/html5');
+_dereq_('./engine/flash');
+
+//Extensions
+//require('./ext/slider'); //TODO enable
+_dereq_('./ext/ui');
+_dereq_('./ext/keyboard');
+_dereq_('./ext/playlist');
+_dereq_('./ext/cuepoint');
+_dereq_('./ext/subtitle');
+_dereq_('./ext/analytics');
+_dereq_('./ext/embed');
+//Have to add fullscreen last
+_dereq_('./ext/fullscreen');
+
+_dereq_('./ext/mobile');
+flowplayer(function(e,o){function a(e){var o=document.createElement("a");return o.href=e,t.hostname(o.hostname)}var n=function(e,o){var a=e.className.split(" ");-1===a.indexOf(o)&&(e.className+=" "+o)},r=function(e){return"none"!==window.getComputedStyle(e).display},l=e.conf,t=flowplayer.common,i=t.createElement,d=l.swf.indexOf("flowplayer.org")&&l.e&&o.getAttribute("data-origin"),s=d?a(d):t.hostname(),p=(document,l.key);"file:"==location.protocol&&(s="localhost"),e.load.ed=1,l.hostname=s,l.origin=d||location.href,d&&n(o,"is-embedded"),"string"==typeof p&&(p=p.split(/,\s*/));var f=function(e,a){var n=i("a",{href:a,className:"fp-brand"});n.innerHTML=e,t.find(".fp-controls",o)[0].appendChild(n)};if(p&&"function"==typeof key_check&&key_check(p,s)){if(l.logo){var c=i("a",{href:d,className:"fp-logo"});l.embed&&l.embed.popup&&(c.target="_blank");var h=i("img",{src:l.logo});c.appendChild(h),o.appendChild(c)}l.brand&&d||l.brand&&l.brand.showOnOrigin?f(l.brand.text||l.brand,d||location.href):t.addClass(o,"no-brand")}else{f("flowplayer","http://flowplayer.org");var c=i("a",{href:"http://flowplayer.org"});o.appendChild(c);var y=i("div",{className:"fp-context-menu"},'<ul><li class="copyright">&copy; 2015</li><li><a href="http://flowplayer.org">About Flowplayer</a></li><li><a href="http://flowplayer.org/license">GPL based license</a></li></ul>'),u=window.location.href.indexOf("localhost"),m=t.find(".fp-player",o)[0];7!==u&&(m||o).appendChild(y),e.on("pause resume finish unload ready",function(e,a){t.removeClass(o,"no-brand");var n=-1;if(a.video.src)for(var l=[["org","flowplayer","drive"],["org","flowplayer","my"]],i=0;i<l.length&&(n=a.video.src.indexOf("://"+l[i].reverse().join(".")),-1===n);i++);if((4===n||5===n)&&t.addClass(o,"no-brand"),/pause|resume/.test(e.type)&&"flash"!=a.engine.engineName&&4!=n&&5!=n){var d={display:"block",position:"absolute",left:"16px",bottom:"46px",zIndex:99999,width:"100px",height:"20px",backgroundImage:"url("+[".png","logo","/",".net",".cloudfront","d32wqyuo10o653","//"].reverse().join("")+")"};for(var s in d)d.hasOwnProperty(s)&&(c.style[s]=d[s]);a.load.ed=r(c)&&(7===u||y.parentNode==o||y.parentNode==m)&&!t.hasClass(o,"no-brand"),a.load.ed||a.pause()}else c.style.display="none"})}});
+
+
+},{"./engine/embed":2,"./engine/flash":3,"./engine/html5":4,"./ext/analytics":5,"./ext/cuepoint":6,"./ext/embed":7,"./ext/fullscreen":9,"./ext/keyboard":10,"./ext/mobile":11,"./ext/playlist":12,"./ext/subtitle":15,"./ext/support":16,"./ext/ui":17,"./flowplayer":18,"es5-shim":25}],20:[function(_dereq_,module,exports){
+/*!
+  * Bean - copyright (c) Jacob Thornton 2011-2012
+  * https://github.com/fat/bean
+  * MIT license
+  */
+(function (name, context, definition) {
+  if (typeof module != 'undefined' && module.exports) module.exports = definition()
+  else if (typeof define == 'function' && define.amd) define(definition)
+  else context[name] = definition()
+})('bean', this, function (name, context) {
+  name    = name    || 'bean'
+  context = context || this
+
+  var win            = window
+    , old            = context[name]
+    , namespaceRegex = /[^\.]*(?=\..*)\.|.*/
+    , nameRegex      = /\..*/
+    , addEvent       = 'addEventListener'
+    , removeEvent    = 'removeEventListener'
+    , doc            = document || {}
+    , root           = doc.documentElement || {}
+    , W3C_MODEL      = root[addEvent]
+    , eventSupport   = W3C_MODEL ? addEvent : 'attachEvent'
+    , ONE            = {} // singleton for quick matching making add() do one()
+
+    , slice          = Array.prototype.slice
+    , str2arr        = function (s, d) { return s.split(d || ' ') }
+    , isString       = function (o) { return typeof o == 'string' }
+    , isFunction     = function (o) { return typeof o == 'function' }
+
+      // events that we consider to be 'native', anything not in this list will
+      // be treated as a custom event
+    , standardNativeEvents =
+        'click dblclick mouseup mousedown contextmenu '                  + // mouse buttons
+        'mousewheel mousemultiwheel DOMMouseScroll '                     + // mouse wheel
+        'mouseover mouseout mousemove selectstart selectend '            + // mouse movement
+        'keydown keypress keyup '                                        + // keyboard
+        'orientationchange '                                             + // mobile
+        'focus blur change reset select submit '                         + // form elements
+        'load unload beforeunload resize move DOMContentLoaded '         + // window
+        'readystatechange message '                                      + // window
+        'error abort scroll '                                              // misc
+      // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
+      // that doesn't actually exist, so make sure we only do these on newer browsers
+    , w3cNativeEvents =
+        'show '                                                          + // mouse buttons
+        'input invalid '                                                 + // form elements
+        'touchstart touchmove touchend touchcancel '                     + // touch
+        'gesturestart gesturechange gestureend '                         + // gesture
+        'textinput '                                                     + // TextEvent
+        'readystatechange pageshow pagehide popstate '                   + // window
+        'hashchange offline online '                                     + // window
+        'afterprint beforeprint '                                        + // printing
+        'dragstart dragenter dragover dragleave drag drop dragend '      + // dnd
+        'loadstart progress suspend emptied stalled loadmetadata '       + // media
+        'loadeddata canplay canplaythrough playing waiting seeking '     + // media
+        'seeked ended durationchange timeupdate play pause ratechange '  + // media
+        'volumechange cuechange '                                        + // media
+        'checking noupdate downloading cached updateready obsolete '       // appcache
+
+      // convert to a hash for quick lookups
+    , nativeEvents = (function (hash, events, i) {
+        for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1)
+        return hash
+      }({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : ''))))
+
+      // custom events are events that we *fake*, they are not provided natively but
+      // we can use native events to generate them
+    , customEvents = (function () {
+        var isAncestor = 'compareDocumentPosition' in root
+              ? function (element, container) {
+                  return container.compareDocumentPosition && (container.compareDocumentPosition(element) & 16) === 16
+                }
+              : 'contains' in root
+                ? function (element, container) {
+                    container = container.nodeType === 9 || container === window ? root : container
+                    return container !== element && container.contains(element)
+                  }
+                : function (element, container) {
+                    while (element = element.parentNode) if (element === container) return 1
+                    return 0
+                  }
+          , check = function (event) {
+              var related = event.relatedTarget
+              return !related
+                ? related == null
+                : (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString())
+                    && !isAncestor(related, this))
+            }
+
+        return {
+            mouseenter: { base: 'mouseover', condition: check }
+          , mouseleave: { base: 'mouseout', condition: check }
+          , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
+        }
+      }())
+
+      // we provide a consistent Event object across browsers by taking the actual DOM
+      // event object and generating a new one from its properties.
+    , Event = (function () {
+            // a whitelist of properties (for different event types) tells us what to check for and copy
+        var commonProps  = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' +
+              'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey '  +
+              'srcElement target timeStamp type view which propertyName')
+          , mouseProps   = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer '      +
+              'fromElement offsetX offsetY pageX pageY screenX screenY toElement'))
+          , mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' +
+              'axis')) // 'axis' is FF specific
+          , keyProps     = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier '          +
+              'keyLocation location'))
+          , textProps    = commonProps.concat(str2arr('data'))
+          , touchProps   = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation'))
+          , messageProps = commonProps.concat(str2arr('data origin source'))
+          , stateProps   = commonProps.concat(str2arr('state'))
+          , overOutRegex = /over|out/
+            // some event types need special handling and some need special properties, do that all here
+          , typeFixers   = [
+                { // key events
+                    reg: /key/i
+                  , fix: function (event, newEvent) {
+                      newEvent.keyCode = event.keyCode || event.which
+                      return keyProps
+                    }
+                }
+              , { // mouse events
+                    reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i
+                  , fix: function (event, newEvent, type) {
+                      newEvent.rightClick = event.which === 3 || event.button === 2
+                      newEvent.pos = { x: 0, y: 0 }
+                      if (event.pageX || event.pageY) {
+                        newEvent.clientX = event.pageX
+                        newEvent.clientY = event.pageY
+                      } else if (event.clientX || event.clientY) {
+                        newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
+                        newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
+                      }
+                      if (overOutRegex.test(type)) {
+                        newEvent.relatedTarget = event.relatedTarget
+                          || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']
+                      }
+                      return mouseProps
+                    }
+                }
+              , { // mouse wheel events
+                    reg: /mouse.*(wheel|scroll)/i
+                  , fix: function () { return mouseWheelProps }
+                }
+              , { // TextEvent
+                    reg: /^text/i
+                  , fix: function () { return textProps }
+                }
+              , { // touch and gesture events
+                    reg: /^touch|^gesture/i
+                  , fix: function () { return touchProps }
+                }
+              , { // message events
+                    reg: /^message$/i
+                  , fix: function () { return messageProps }
+                }
+              , { // popstate events
+                    reg: /^popstate$/i
+                  , fix: function () { return stateProps }
+                }
+              , { // everything else
+                    reg: /.*/
+                  , fix: function () { return commonProps }
+                }
+            ]
+          , typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism
+
+          , Event = function (event, element, isNative) {
+              if (!arguments.length) return
+              event = event || ((element.ownerDocument || element.document || element).parentWindow || win).event
+              this.originalEvent = event
+              this.isNative       = isNative
+              this.isBean         = true
+
+              if (!event) return
+
+              var type   = event.type
+                , target = event.target || event.srcElement
+                , i, l, p, props, fixer
+
+              this.target = target && target.nodeType === 3 ? target.parentNode : target
+
+              if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless
+                fixer = typeFixerMap[type]
+                if (!fixer) { // haven't encountered this event type before, map a fixer function for it
+                  for (i = 0, l = typeFixers.length; i < l; i++) {
+                    if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .*
+                      typeFixerMap[type] = fixer = typeFixers[i].fix
+                      break
+                    }
+                  }
+                }
+
+                props = fixer(event, this, type)
+                for (i = props.length; i--;) {
+                  if (!((p = props[i]) in this) && p in event) this[p] = event[p]
+                }
+              }
+            }
+
+        // preventDefault() and stopPropagation() are a consistent interface to those functions
+        // on the DOM, stop() is an alias for both of them together
+        Event.prototype.preventDefault = function () {
+          if (this.originalEvent.preventDefault) this.originalEvent.preventDefault()
+          else this.originalEvent.returnValue = false
+        }
+        Event.prototype.stopPropagation = function () {
+          if (this.originalEvent.stopPropagation) this.originalEvent.stopPropagation()
+          else this.originalEvent.cancelBubble = true
+        }
+        Event.prototype.stop = function () {
+          this.preventDefault()
+          this.stopPropagation()
+          this.stopped = true
+        }
+        // stopImmediatePropagation() has to be handled internally because we manage the event list for
+        // each element
+        // note that originalElement may be a Bean#Event object in some situations
+        Event.prototype.stopImmediatePropagation = function () {
+          if (this.originalEvent.stopImmediatePropagation) this.originalEvent.stopImmediatePropagation()
+          this.isImmediatePropagationStopped = function () { return true }
+        }
+        Event.prototype.isImmediatePropagationStopped = function () {
+          return this.originalEvent.isImmediatePropagationStopped && this.originalEvent.isImmediatePropagationStopped()
+        }
+        Event.prototype.clone = function (currentTarget) {
+          //TODO: this is ripe for optimisation, new events are *expensive*
+          // improving this will speed up delegated events
+          var ne = new Event(this, this.element, this.isNative)
+          ne.currentTarget = currentTarget
+          return ne
+        }
+
+        return Event
+      }())
+
+      // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
+    , targetElement = function (element, isNative) {
+        return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
+      }
+
+      /**
+        * Bean maintains an internal registry for event listeners. We don't touch elements, objects
+        * or functions to identify them, instead we store everything in the registry.
+        * Each event listener has a RegEntry object, we have one 'registry' for the whole instance.
+        */
+    , RegEntry = (function () {
+        // each handler is wrapped so we can handle delegation and custom events
+        var wrappedHandler = function (element, fn, condition, args) {
+            var call = function (event, eargs) {
+                  return fn.apply(element, args ? slice.call(eargs, event ? 0 : 1).concat(args) : eargs)
+                }
+              , findTarget = function (event, eventElement) {
+                  return fn.__beanDel ? fn.__beanDel.ft(event.target, element) : eventElement
+                }
+              , handler = condition
+                  ? function (event) {
+                      var target = findTarget(event, this) // deleated event
+                      if (condition.apply(target, arguments)) {
+                        if (event) event.currentTarget = target
+                        return call(event, arguments)
+                      }
+                    }
+                  : function (event) {
+                      if (fn.__beanDel) event = event.clone(findTarget(event)) // delegated event, fix the fix
+                      return call(event, arguments)
+                    }
+            handler.__beanDel = fn.__beanDel
+            return handler
+          }
+
+        , RegEntry = function (element, type, handler, original, namespaces, args, root) {
+            var customType     = customEvents[type]
+              , isNative
+
+            if (type == 'unload') {
+              // self clean-up
+              handler = once(removeListener, element, type, handler, original)
+            }
+
+            if (customType) {
+              if (customType.condition) {
+                handler = wrappedHandler(element, handler, customType.condition, args)
+              }
+              type = customType.base || type
+            }
+
+            this.isNative      = isNative = nativeEvents[type] && !!element[eventSupport]
+            this.customType    = !W3C_MODEL && !isNative && type
+            this.element       = element
+            this.type          = type
+            this.original      = original
+            this.namespaces    = namespaces
+            this.eventType     = W3C_MODEL || isNative ? type : 'propertychange'
+            this.target        = targetElement(element, isNative)
+            this[eventSupport] = !!this.target[eventSupport]
+            this.root          = root
+            this.handler       = wrappedHandler(element, handler, null, args)
+          }
+
+        // given a list of namespaces, is our entry in any of them?
+        RegEntry.prototype.inNamespaces = function (checkNamespaces) {
+          var i, j, c = 0
+          if (!checkNamespaces) return true
+          if (!this.namespaces) return false
+          for (i = checkNamespaces.length; i--;) {
+            for (j = this.namespaces.length; j--;) {
+              if (checkNamespaces[i] == this.namespaces[j]) c++
+            }
+          }
+          return checkNamespaces.length === c
+        }
+
+        // match by element, original fn (opt), handler fn (opt)
+        RegEntry.prototype.matches = function (checkElement, checkOriginal, checkHandler) {
+          return this.element === checkElement &&
+            (!checkOriginal || this.original === checkOriginal) &&
+            (!checkHandler || this.handler === checkHandler)
+        }
+
+        return RegEntry
+      }())
+
+    , registry = (function () {
+        // our map stores arrays by event type, just because it's better than storing
+        // everything in a single array.
+        // uses '$' as a prefix for the keys for safety and 'r' as a special prefix for
+        // rootListeners so we can look them up fast
+        var map = {}
+
+          // generic functional search of our registry for matching listeners,
+          // `fn` returns false to break out of the loop
+          , forAll = function (element, type, original, handler, root, fn) {
+              var pfx = root ? 'r' : '$'
+              if (!type || type == '*') {
+                // search the whole registry
+                for (var t in map) {
+                  if (t.charAt(0) == pfx) {
+                    forAll(element, t.substr(1), original, handler, root, fn)
+                  }
+                }
+              } else {
+                var i = 0, l, list = map[pfx + type], all = element == '*'
+                if (!list) return
+                for (l = list.length; i < l; i++) {
+                  if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return
+                }
+              }
+            }
+
+          , has = function (element, type, original, root) {
+              // we're not using forAll here simply because it's a bit slower and this
+              // needs to be fast
+              var i, list = map[(root ? 'r' : '$') + type]
+              if (list) {
+                for (i = list.length; i--;) {
+                  if (!list[i].root && list[i].matches(element, original, null)) return true
+                }
+              }
+              return false
+            }
+
+          , get = function (element, type, original, root) {
+              var entries = []
+              forAll(element, type, original, null, root, function (entry) {
+                return entries.push(entry)
+              })
+              return entries
+            }
+
+          , put = function (entry) {
+              var has = !entry.root && !this.has(entry.element, entry.type, null, false)
+                , key = (entry.root ? 'r' : '$') + entry.type
+              ;(map[key] || (map[key] = [])).push(entry)
+              return has
+            }
+
+          , del = function (entry) {
+              forAll(entry.element, entry.type, null, entry.handler, entry.root, function (entry, list, i) {
+                list.splice(i, 1)
+                entry.removed = true
+                if (list.length === 0) delete map[(entry.root ? 'r' : '$') + entry.type]
+                return false
+              })
+            }
+
+            // dump all entries, used for onunload
+          , entries = function () {
+              var t, entries = []
+              for (t in map) {
+                if (t.charAt(0) == '$') entries = entries.concat(map[t])
+              }
+              return entries
+            }
+
+        return { has: has, get: get, put: put, del: del, entries: entries }
+      }())
+
+      // we need a selector engine for delegated events, use querySelectorAll if it exists
+      // but for older browsers we need Qwery, Sizzle or similar
+    , selectorEngine
+    , setSelectorEngine = function (e) {
+        if (!arguments.length) {
+          selectorEngine = doc.querySelectorAll
+            ? function (s, r) {
+                return r.querySelectorAll(s)
+              }
+            : function () {
+                throw new Error('Bean: No selector engine installed') // eeek
+              }
+        } else {
+          selectorEngine = e
+        }
+      }
+
+      // we attach this listener to each DOM event that we need to listen to, only once
+      // per event type per DOM element
+    , rootListener = function (event, type) {
+        if (!W3C_MODEL && type && event && event.propertyName != '_on' + type) return
+
+        var listeners = registry.get(this, type || event.type, null, false)
+          , l = listeners.length
+          , i = 0
+
+        event = new Event(event, this, true)
+        if (type) event.type = type
+
+        // iterate through all handlers registered for this type, calling them unless they have
+        // been removed by a previous handler or stopImmediatePropagation() has been called
+        for (; i < l && !event.isImmediatePropagationStopped(); i++) {
+          if (!listeners[i].removed) listeners[i].handler.call(this, event)
+        }
+      }
+
+      // add and remove listeners to DOM elements
+    , listener = W3C_MODEL
+        ? function (element, type, add) {
+            // new browsers
+            element[add ? addEvent : removeEvent](type, rootListener, false)
+          }
+        : function (element, type, add, custom) {
+            // IE8 and below, use attachEvent/detachEvent and we have to piggy-back propertychange events
+            // to simulate event bubbling etc.
+            var entry
+            if (add) {
+              registry.put(entry = new RegEntry(
+                  element
+                , custom || type
+                , function (event) { // handler
+                    rootListener.call(element, event, custom)
+                  }
+                , rootListener
+                , null
+                , null
+                , true // is root
+              ))
+              if (custom && element['_on' + custom] == null) element['_on' + custom] = 0
+              entry.target.attachEvent('on' + entry.eventType, entry.handler)
+            } else {
+              entry = registry.get(element, custom || type, rootListener, true)[0]
+              if (entry) {
+                entry.target.detachEvent('on' + entry.eventType, entry.handler)
+                registry.del(entry)
+              }
+            }
+          }
+
+    , once = function (rm, element, type, fn, originalFn) {
+        // wrap the handler in a handler that does a remove as well
+        return function () {
+          fn.apply(this, arguments)
+          rm(element, type, originalFn)
+        }
+      }
+
+    , removeListener = function (element, orgType, handler, namespaces) {
+        var type     = orgType && orgType.replace(nameRegex, '')
+          , handlers = registry.get(element, type, null, false)
+          , removed  = {}
+          , i, l
+
+        for (i = 0, l = handlers.length; i < l; i++) {
+          if ((!handler || handlers[i].original === handler) && handlers[i].inNamespaces(namespaces)) {
+            // TODO: this is problematic, we have a registry.get() and registry.del() that
+            // both do registry searches so we waste cycles doing this. Needs to be rolled into
+            // a single registry.forAll(fn) that removes while finding, but the catch is that
+            // we'll be splicing the arrays that we're iterating over. Needs extra tests to
+            // make sure we don't screw it up. @rvagg
+            registry.del(handlers[i])
+            if (!removed[handlers[i].eventType] && handlers[i][eventSupport])
+              removed[handlers[i].eventType] = { t: handlers[i].eventType, c: handlers[i].type }
+          }
+        }
+        // check each type/element for removed listeners and remove the rootListener where it's no longer needed
+        for (i in removed) {
+          if (!registry.has(element, removed[i].t, null, false)) {
+            // last listener of this type, remove the rootListener
+            listener(element, removed[i].t, false, removed[i].c)
+          }
+        }
+      }
+
+      // set up a delegate helper using the given selector, wrap the handler function
+    , delegate = function (selector, fn) {
+        //TODO: findTarget (therefore $) is called twice, once for match and once for
+        // setting e.currentTarget, fix this so it's only needed once
+        var findTarget = function (target, root) {
+              var i, array = isString(selector) ? selectorEngine(selector, root) : selector
+              for (; target && target !== root; target = target.parentNode) {
+                for (i = array.length; i--;) {
+                  if (array[i] === target) return target
+                }
+              }
+            }
+          , handler = function (e) {
+              var match = findTarget(e.target, this)
+              if (match) fn.apply(match, arguments)
+            }
+
+        // __beanDel isn't pleasant but it's a private function, not exposed outside of Bean
+        handler.__beanDel = {
+            ft       : findTarget // attach it here for customEvents to use too
+          , selector : selector
+        }
+        return handler
+      }
+
+    , fireListener = W3C_MODEL ? function (isNative, type, element) {
+        // modern browsers, do a proper dispatchEvent()
+        var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
+        evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
+        element.dispatchEvent(evt)
+      } : function (isNative, type, element) {
+        // old browser use onpropertychange, just increment a custom property to trigger the event
+        element = targetElement(element, isNative)
+        isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
+      }
+
+      /**
+        * Public API: off(), on(), add(), (remove()), one(), fire(), clone()
+        */
+
+      /**
+        * off(element[, eventType(s)[, handler ]])
+        */
+    , off = function (element, typeSpec, fn) {
+        var isTypeStr = isString(typeSpec)
+          , k, type, namespaces, i
+
+        if (isTypeStr && typeSpec.indexOf(' ') > 0) {
+          // off(el, 't1 t2 t3', fn) or off(el, 't1 t2 t3')
+          typeSpec = str2arr(typeSpec)
+          for (i = typeSpec.length; i--;)
+            off(element, typeSpec[i], fn)
+          return element
+        }
+
+        type = isTypeStr && typeSpec.replace(nameRegex, '')
+        if (type && customEvents[type]) type = customEvents[type].base
+
+        if (!typeSpec || isTypeStr) {
+          // off(el) or off(el, t1.ns) or off(el, .ns) or off(el, .ns1.ns2.ns3)
+          if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.')
+          removeListener(element, type, fn, namespaces)
+        } else if (isFunction(typeSpec)) {
+          // off(el, fn)
+          removeListener(element, null, typeSpec)
+        } else {
+          // off(el, { t1: fn1, t2, fn2 })
+          for (k in typeSpec) {
+            if (typeSpec.hasOwnProperty(k)) off(element, k, typeSpec[k])
+          }
+        }
+
+        return element
+      }
+
+      /**
+        * on(element, eventType(s)[, selector], handler[, args ])
+        */
+    , on = function(element, events, selector, fn) {
+        var originalFn, type, types, i, args, entry, first
+
+        //TODO: the undefined check means you can't pass an 'args' argument, fix this perhaps?
+        if (selector === undefined && typeof events == 'object') {
+          //TODO: this can't handle delegated events
+          for (type in events) {
+            if (events.hasOwnProperty(type)) {
+              on.call(this, element, type, events[type])
+            }
+          }
+          return
+        }
+
+        if (!isFunction(selector)) {
+          // delegated event
+          originalFn = fn
+          args       = slice.call(arguments, 4)
+          fn         = delegate(selector, originalFn, selectorEngine)
+        } else {
+          args       = slice.call(arguments, 3)
+          fn         = originalFn = selector
+        }
+
+        types = str2arr(events)
+
+        // special case for one(), wrap in a self-removing handler
+        if (this === ONE) {
+          fn = once(off, element, events, fn, originalFn)
+        }
+
+        for (i = types.length; i--;) {
+          // add new handler to the registry and check if it's the first for this element/type
+          first = registry.put(entry = new RegEntry(
+              element
+            , types[i].replace(nameRegex, '') // event type
+            , fn
+            , originalFn
+            , str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces
+            , args
+            , false // not root
+          ))
+          if (entry[eventSupport] && first) {
+            // first event of this type on this element, add root listener
+            listener(element, entry.eventType, true, entry.customType)
+          }
+        }
+
+        return element
+      }
+
+      /**
+        * add(element[, selector], eventType(s), handler[, args ])
+        *
+        * Deprecated: kept (for now) for backward-compatibility
+        */
+    , add = function (element, events, fn, delfn) {
+        return on.apply(
+            null
+          , !isString(fn)
+              ? slice.call(arguments)
+              : [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : [])
+        )
+      }
+
+      /**
+        * one(element, eventType(s)[, selector], handler[, args ])
+        */
+    , one = function () {
+        return on.apply(ONE, arguments)
+      }
+
+      /**
+        * fire(element, eventType(s)[, args ])
+        *
+        * The optional 'args' argument must be an array, if no 'args' argument is provided
+        * then we can use the browser's DOM event system, otherwise we trigger handlers manually
+        */
+    , fire = function (element, type, args) {
+        var types = str2arr(type)
+          , i, j, l, names, handlers
+
+        for (i = types.length; i--;) {
+          type = types[i].replace(nameRegex, '')
+          if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.')
+          if (!names && !args && element[eventSupport]) {
+            fireListener(nativeEvents[type], type, element)
+          } else {
+            // non-native event, either because of a namespace, arguments or a non DOM element
+            // iterate over all listeners and manually 'fire'
+            handlers = registry.get(element, type, null, false)
+            args = [false].concat(args)
+            for (j = 0, l = handlers.length; j < l; j++) {
+              if (handlers[j].inNamespaces(names)) {
+                handlers[j].handler.apply(element, args)
+              }
+            }
+          }
+        }
+        return element
+      }
+
+      /**
+        * clone(dstElement, srcElement[, eventType ])
+        *
+        * TODO: perhaps for consistency we should allow the same flexibility in type specifiers?
+        */
+    , clone = function (element, from, type) {
+        var handlers = registry.get(from, type, null, false)
+          , l = handlers.length
+          , i = 0
+          , args, beanDel
+
+        for (; i < l; i++) {
+          if (handlers[i].original) {
+            args = [ element, handlers[i].type ]
+            if (beanDel = handlers[i].handler.__beanDel) args.push(beanDel.selector)
+            args.push(handlers[i].original)
+            on.apply(null, args)
+          }
+        }
+        return element
+      }
+
+    , bean = {
+          'on'                : on
+        , 'add'               : add
+        , 'one'               : one
+        , 'off'               : off
+        , 'remove'            : off
+        , 'clone'             : clone
+        , 'fire'              : fire
+        , 'Event'             : Event
+        , 'setSelectorEngine' : setSelectorEngine
+        , 'noConflict'        : function () {
+            context[name] = old
+            return this
+          }
+      }
+
+  // for IE, clean up on unload to avoid leaks
+  if (win.attachEvent) {
+    var cleanup = function () {
+      var i, entries = registry.entries()
+      for (i in entries) {
+        if (entries[i].type && entries[i].type !== 'unload') off(entries[i].element, entries[i].type)
+      }
+      win.detachEvent('onunload', cleanup)
+      win.CollectGarbage && win.CollectGarbage()
+    }
+    win.attachEvent('onunload', cleanup)
+  }
+
+  // initialize selector engine to internal default (qSA or throw Error)
+  setSelectorEngine()
+
+  return bean
+});
+
+},{}],21:[function(_dereq_,module,exports){
+(function (global){
+/*! http://mths.be/punycode v1.2.4 by @mathias */
+;(function(root) {
+
+	/** Detect free variables */
+	var freeExports = typeof exports == 'object' && exports;
+	var freeModule = typeof module == 'object' && module &&
+		module.exports == freeExports && module;
+	var freeGlobal = typeof global == 'object' && global;
+	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+		root = freeGlobal;
+	}
+
+	/**
+	 * The `punycode` object.
+	 * @name punycode
+	 * @type Object
+	 */
+	var punycode,
+
+	/** Highest positive signed 32-bit float value */
+	maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+	/** Bootstring parameters */
+	base = 36,
+	tMin = 1,
+	tMax = 26,
+	skew = 38,
+	damp = 700,
+	initialBias = 72,
+	initialN = 128, // 0x80
+	delimiter = '-', // '\x2D'
+
+	/** Regular expressions */
+	regexPunycode = /^xn--/,
+	regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
+	regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
+
+	/** Error messages */
+	errors = {
+		'overflow': 'Overflow: input needs wider integers to process',
+		'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+		'invalid-input': 'Invalid input'
+	},
+
+	/** Convenience shortcuts */
+	baseMinusTMin = base - tMin,
+	floor = Math.floor,
+	stringFromCharCode = String.fromCharCode,
+
+	/** Temporary variable */
+	key;
+
+	/*--------------------------------------------------------------------------*/
+
+	/**
+	 * A generic error utility function.
+	 * @private
+	 * @param {String} type The error type.
+	 * @returns {Error} Throws a `RangeError` with the applicable error message.
+	 */
+	function error(type) {
+		throw RangeError(errors[type]);
+	}
+
+	/**
+	 * A generic `Array#map` utility function.
+	 * @private
+	 * @param {Array} array The array to iterate over.
+	 * @param {Function} callback The function that gets called for every array
+	 * item.
+	 * @returns {Array} A new array of values returned by the callback function.
+	 */
+	function map(array, fn) {
+		var length = array.length;
+		while (length--) {
+			array[length] = fn(array[length]);
+		}
+		return array;
+	}
+
+	/**
+	 * A simple `Array#map`-like wrapper to work with domain name strings.
+	 * @private
+	 * @param {String} domain The domain name.
+	 * @param {Function} callback The function that gets called for every
+	 * character.
+	 * @returns {Array} A new string of characters returned by the callback
+	 * function.
+	 */
+	function mapDomain(string, fn) {
+		return map(string.split(regexSeparators), fn).join('.');
+	}
+
+	/**
+	 * Creates an array containing the numeric code points of each Unicode
+	 * character in the string. While JavaScript uses UCS-2 internally,
+	 * this function will convert a pair of surrogate halves (each of which
+	 * UCS-2 exposes as separate characters) into a single code point,
+	 * matching UTF-16.
+	 * @see `punycode.ucs2.encode`
+	 * @see <http://mathiasbynens.be/notes/javascript-encoding>
+	 * @memberOf punycode.ucs2
+	 * @name decode
+	 * @param {String} string The Unicode input string (UCS-2).
+	 * @returns {Array} The new array of code points.
+	 */
+	function ucs2decode(string) {
+		var output = [],
+		    counter = 0,
+		    length = string.length,
+		    value,
+		    extra;
+		while (counter < length) {
+			value = string.charCodeAt(counter++);
+			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+				// high surrogate, and there is a next character
+				extra = string.charCodeAt(counter++);
+				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+				} else {
+					// unmatched surrogate; only append this code unit, in case the next
+					// code unit is the high surrogate of a surrogate pair
+					output.push(value);
+					counter--;
+				}
+			} else {
+				output.push(value);
+			}
+		}
+		return output;
+	}
+
+	/**
+	 * Creates a string based on an array of numeric code points.
+	 * @see `punycode.ucs2.decode`
+	 * @memberOf punycode.ucs2
+	 * @name encode
+	 * @param {Array} codePoints The array of numeric code points.
+	 * @returns {String} The new Unicode string (UCS-2).
+	 */
+	function ucs2encode(array) {
+		return map(array, function(value) {
+			var output = '';
+			if (value > 0xFFFF) {
+				value -= 0x10000;
+				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+				value = 0xDC00 | value & 0x3FF;
+			}
+			output += stringFromCharCode(value);
+			return output;
+		}).join('');
+	}
+
+	/**
+	 * Converts a basic code point into a digit/integer.
+	 * @see `digitToBasic()`
+	 * @private
+	 * @param {Number} codePoint The basic numeric code point value.
+	 * @returns {Number} The numeric value of a basic code point (for use in
+	 * representing integers) in the range `0` to `base - 1`, or `base` if
+	 * the code point does not represent a value.
+	 */
+	function basicToDigit(codePoint) {
+		if (codePoint - 48 < 10) {
+			return codePoint - 22;
+		}
+		if (codePoint - 65 < 26) {
+			return codePoint - 65;
+		}
+		if (codePoint - 97 < 26) {
+			return codePoint - 97;
+		}
+		return base;
+	}
+
+	/**
+	 * Converts a digit/integer into a basic code point.
+	 * @see `basicToDigit()`
+	 * @private
+	 * @param {Number} digit The numeric value of a basic code point.
+	 * @returns {Number} The basic code point whose value (when used for
+	 * representing integers) is `digit`, which needs to be in the range
+	 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+	 * used; else, the lowercase form is used. The behavior is undefined
+	 * if `flag` is non-zero and `digit` has no uppercase form.
+	 */
+	function digitToBasic(digit, flag) {
+		//  0..25 map to ASCII a..z or A..Z
+		// 26..35 map to ASCII 0..9
+		return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+	}
+
+	/**
+	 * Bias adaptation function as per section 3.4 of RFC 3492.
+	 * http://tools.ietf.org/html/rfc3492#section-3.4
+	 * @private
+	 */
+	function adapt(delta, numPoints, firstTime) {
+		var k = 0;
+		delta = firstTime ? floor(delta / damp) : delta >> 1;
+		delta += floor(delta / numPoints);
+		for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+			delta = floor(delta / baseMinusTMin);
+		}
+		return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+	}
+
+	/**
+	 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
+	 * symbols.
+	 * @memberOf punycode
+	 * @param {String} input The Punycode string of ASCII-only symbols.
+	 * @returns {String} The resulting string of Unicode symbols.
+	 */
+	function decode(input) {
+		// Don't use UCS-2
+		var output = [],
+		    inputLength = input.length,
+		    out,
+		    i = 0,
+		    n = initialN,
+		    bias = initialBias,
+		    basic,
+		    j,
+		    index,
+		    oldi,
+		    w,
+		    k,
+		    digit,
+		    t,
+		    /** Cached calculation results */
+		    baseMinusT;
+
+		// Handle the basic code points: let `basic` be the number of input code
+		// points before the last delimiter, or `0` if there is none, then copy
+		// the first basic code points to the output.
+
+		basic = input.lastIndexOf(delimiter);
+		if (basic < 0) {
+			basic = 0;
+		}
+
+		for (j = 0; j < basic; ++j) {
+			// if it's not a basic code point
+			if (input.charCodeAt(j) >= 0x80) {
+				error('not-basic');
+			}
+			output.push(input.charCodeAt(j));
+		}
+
+		// Main decoding loop: start just after the last delimiter if any basic code
+		// points were copied; start at the beginning otherwise.
+
+		for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+			// `index` is the index of the next character to be consumed.
+			// Decode a generalized variable-length integer into `delta`,
+			// which gets added to `i`. The overflow checking is easier
+			// if we increase `i` as we go, then subtract off its starting
+			// value at the end to obtain `delta`.
+			for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+				if (index >= inputLength) {
+					error('invalid-input');
+				}
+
+				digit = basicToDigit(input.charCodeAt(index++));
+
+				if (digit >= base || digit > floor((maxInt - i) / w)) {
+					error('overflow');
+				}
+
+				i += digit * w;
+				t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+				if (digit < t) {
+					break;
+				}
+
+				baseMinusT = base - t;
+				if (w > floor(maxInt / baseMinusT)) {
+					error('overflow');
+				}
+
+				w *= baseMinusT;
+
+			}
+
+			out = output.length + 1;
+			bias = adapt(i - oldi, out, oldi == 0);
+
+			// `i` was supposed to wrap around from `out` to `0`,
+			// incrementing `n` each time, so we'll fix that now:
+			if (floor(i / out) > maxInt - n) {
+				error('overflow');
+			}
+
+			n += floor(i / out);
+			i %= out;
+
+			// Insert `n` at position `i` of the output
+			output.splice(i++, 0, n);
+
+		}
+
+		return ucs2encode(output);
+	}
+
+	/**
+	 * Converts a string of Unicode symbols to a Punycode string of ASCII-only
+	 * symbols.
+	 * @memberOf punycode
+	 * @param {String} input The string of Unicode symbols.
+	 * @returns {String} The resulting Punycode string of ASCII-only symbols.
+	 */
+	function encode(input) {
+		var n,
+		    delta,
+		    handledCPCount,
+		    basicLength,
+		    bias,
+		    j,
+		    m,
+		    q,
+		    k,
+		    t,
+		    currentValue,
+		    output = [],
+		    /** `inputLength` will hold the number of code points in `input`. */
+		    inputLength,
+		    /** Cached calculation results */
+		    handledCPCountPlusOne,
+		    baseMinusT,
+		    qMinusT;
+
+		// Convert the input in UCS-2 to Unicode
+		input = ucs2decode(input);
+
+		// Cache the length
+		inputLength = input.length;
+
+		// Initialize the state
+		n = initialN;
+		delta = 0;
+		bias = initialBias;
+
+		// Handle the basic code points
+		for (j = 0; j < inputLength; ++j) {
+			currentValue = input[j];
+			if (currentValue < 0x80) {
+				output.push(stringFromCharCode(currentValue));
+			}
+		}
+
+		handledCPCount = basicLength = output.length;
+
+		// `handledCPCount` is the number of code points that have been handled;
+		// `basicLength` is the number of basic code points.
+
+		// Finish the basic string - if it is not empty - with a delimiter
+		if (basicLength) {
+			output.push(delimiter);
+		}
+
+		// Main encoding loop:
+		while (handledCPCount < inputLength) {
+
+			// All non-basic code points < n have been handled already. Find the next
+			// larger one:
+			for (m = maxInt, j = 0; j < inputLength; ++j) {
+				currentValue = input[j];
+				if (currentValue >= n && currentValue < m) {
+					m = currentValue;
+				}
+			}
+
+			// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
+			// but guard against overflow
+			handledCPCountPlusOne = handledCPCount + 1;
+			if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+				error('overflow');
+			}
+
+			delta += (m - n) * handledCPCountPlusOne;
+			n = m;
+
+			for (j = 0; j < inputLength; ++j) {
+				currentValue = input[j];
+
+				if (currentValue < n && ++delta > maxInt) {
+					error('overflow');
+				}
+
+				if (currentValue == n) {
+					// Represent delta as a generalized variable-length integer
+					for (q = delta, k = base; /* no condition */; k += base) {
+						t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+						if (q < t) {
+							break;
+						}
+						qMinusT = q - t;
+						baseMinusT = base - t;
+						output.push(
+							stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+						);
+						q = floor(qMinusT / baseMinusT);
+					}
+
+					output.push(stringFromCharCode(digitToBasic(q, 0)));
+					bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+					delta = 0;
+					++handledCPCount;
+				}
+			}
+
+			++delta;
+			++n;
+
+		}
+		return output.join('');
+	}
+
+	/**
+	 * Converts a Punycode string representing a domain name to Unicode. Only the
+	 * Punycoded parts of the domain name will be converted, i.e. it doesn't
+	 * matter if you call it on a string that has already been converted to
+	 * Unicode.
+	 * @memberOf punycode
+	 * @param {String} domain The Punycode domain name to convert to Unicode.
+	 * @returns {String} The Unicode representation of the given Punycode
+	 * string.
+	 */
+	function toUnicode(domain) {
+		return mapDomain(domain, function(string) {
+			return regexPunycode.test(string)
+				? decode(string.slice(4).toLowerCase())
+				: string;
+		});
+	}
+
+	/**
+	 * Converts a Unicode string representing a domain name to Punycode. Only the
+	 * non-ASCII parts of the domain name will be converted, i.e. it doesn't
+	 * matter if you call it with a domain that's already in ASCII.
+	 * @memberOf punycode
+	 * @param {String} domain The domain name to convert, as a Unicode string.
+	 * @returns {String} The Punycode representation of the given domain name.
+	 */
+	function toASCII(domain) {
+		return mapDomain(domain, function(string) {
+			return regexNonASCII.test(string)
+				? 'xn--' + encode(string)
+				: string;
+		});
+	}
+
+	/*--------------------------------------------------------------------------*/
+
+	/** Define the public API */
+	punycode = {
+		/**
+		 * A string representing the current Punycode.js version number.
+		 * @memberOf punycode
+		 * @type String
+		 */
+		'version': '1.2.4',
+		/**
+		 * An object of methods to convert from JavaScript's internal character
+		 * representation (UCS-2) to Unicode code points, and back.
+		 * @see <http://mathiasbynens.be/notes/javascript-encoding>
+		 * @memberOf punycode
+		 * @type Object
+		 */
+		'ucs2': {
+			'decode': ucs2decode,
+			'encode': ucs2encode
+		},
+		'decode': decode,
+		'encode': encode,
+		'toASCII': toASCII,
+		'toUnicode': toUnicode
+	};
+
+	/** Expose `punycode` */
+	// Some AMD build optimizers, like r.js, check for specific condition patterns
+	// like the following:
+	if (
+		typeof define == 'function' &&
+		typeof define.amd == 'object' &&
+		define.amd
+	) {
+		define('punycode', function() {
+			return punycode;
+		});
+	} else if (freeExports && !freeExports.nodeType) {
+		if (freeModule) { // in Node.js or RingoJS v0.8.0+
+			freeModule.exports = punycode;
+		} else { // in Narwhal or RingoJS v0.7.0-
+			for (key in punycode) {
+				punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+			}
+		}
+	} else { // in Rhino or a web browser
+		root.punycode = punycode;
+	}
+
+}(this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],22:[function(_dereq_,module,exports){
+// contains, add, remove, toggle
+var indexof = _dereq_('indexof')
+
+module.exports = ClassList
+
+function ClassList(elem) {
+    var cl = elem.classList
+
+    if (cl) {
+        return cl
+    }
+
+    var classList = {
+        add: add
+        , remove: remove
+        , contains: contains
+        , toggle: toggle
+        , toString: $toString
+        , length: 0
+        , item: item
+    }
+
+    return classList
+
+    function add(token) {
+        var list = getTokens()
+        if (indexof(list, token) > -1) {
+            return
+        }
+        list.push(token)
+        setTokens(list)
+    }
+
+    function remove(token) {
+        var list = getTokens()
+            , index = indexof(list, token)
+
+        if (index === -1) {
+            return
+        }
+
+        list.splice(index, 1)
+        setTokens(list)
+    }
+
+    function contains(token) {
+        return indexof(getTokens(), token) > -1
+    }
+
+    function toggle(token) {
+        if (contains(token)) {
+            remove(token)
+            return false
+        } else {
+            add(token)
+            return true
+        }
+    }
+
+    function $toString() {
+        return elem.className
+    }
+
+    function item(index) {
+        var tokens = getTokens()
+        return tokens[index] || null
+    }
+
+    function getTokens() {
+        var className = elem.className
+
+        return filter(className.split(" "), isTruthy)
+    }
+
+    function setTokens(list) {
+        var length = list.length
+
+        elem.className = list.join(" ")
+        classList.length = length
+
+        for (var i = 0; i < list.length; i++) {
+            classList[i] = list[i]
+        }
+
+        delete list[length]
+    }
+}
+
+function filter (arr, fn) {
+    var ret = []
+    for (var i = 0; i < arr.length; i++) {
+        if (fn(arr[i])) ret.push(arr[i])
+    }
+    return ret
+}
+
+function isTruthy(value) {
+    return !!value
+}
+
+},{"indexof":23}],23:[function(_dereq_,module,exports){
+
+var indexOf = [].indexOf;
+
+module.exports = function(arr, obj){
+  if (indexOf) return arr.indexOf(obj);
+  for (var i = 0; i < arr.length; ++i) {
+    if (arr[i] === obj) return i;
+  }
+  return -1;
+};
+},{}],24:[function(_dereq_,module,exports){
+// DEV: We don't use var but favor parameters since these play nicer with minification
+function computedStyle(el, prop, getComputedStyle, style) {
+  getComputedStyle = window.getComputedStyle;
+  style =
+      // If we have getComputedStyle
+      getComputedStyle ?
+        // Query it
+        // TODO: From CSS-Query notes, we might need (node, null) for FF
+        getComputedStyle(el) :
+
+      // Otherwise, we are in IE and use currentStyle
+        el.currentStyle;
+  if (style) {
+    return style
+    [
+      // Switch to camelCase for CSSOM
+      // DEV: Grabbed from jQuery
+      // https://github.com/jquery/jquery/blob/1.9-stable/src/css.js#L191-L194
+      // https://github.com/jquery/jquery/blob/1.9-stable/src/core.js#L593-L597
+      prop.replace(/-(\w)/gi, function (word, letter) {
+        return letter.toUpperCase();
+      })
+    ];
+  }
+}
+
+module.exports = computedStyle;
+
+},{}],25:[function(_dereq_,module,exports){
+/*!
+ * https://github.com/es-shims/es5-shim
+ * @license es5-shim Copyright 2009-2015 by contributors, MIT License
+ * see https://github.com/es-shims/es5-shim/blob/master/LICENSE
+ */
+
+// vim: ts=4 sts=4 sw=4 expandtab
+
+// Add semicolon to prevent IIFE from being passed as argument to concatenated code.
+;
+
+// UMD (Universal Module Definition)
+// see https://github.com/umdjs/umd/blob/master/returnExports.js
+(function (root, factory) {
+    'use strict';
+
+    /* global define, exports, module */
+    if (typeof define === 'function' && define.amd) {
+        // AMD. Register as an anonymous module.
+        define(factory);
+    } else if (typeof exports === 'object') {
+        // Node. Does not work with strict CommonJS, but
+        // only CommonJS-like enviroments that support module.exports,
+        // like Node.
+        module.exports = factory();
+    } else {
+        // Browser globals (root is window)
+        root.returnExports = factory();
+    }
+}(this, 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/
+ */
+
+// Shortcut to an often accessed properties, in order to avoid multiple
+// dereference that costs universally. This also holds a reference to known-good
+// functions.
+var $Array = Array;
+var ArrayPrototype = $Array.prototype;
+var $Object = Object;
+var ObjectPrototype = $Object.prototype;
+var FunctionPrototype = Function.prototype;
+var $String = String;
+var StringPrototype = $String.prototype;
+var $Number = Number;
+var NumberPrototype = $Number.prototype;
+var array_slice = ArrayPrototype.slice;
+var array_splice = ArrayPrototype.splice;
+var array_push = ArrayPrototype.push;
+var array_unshift = ArrayPrototype.unshift;
+var array_concat = ArrayPrototype.concat;
+var call = FunctionPrototype.call;
+var max = Math.max;
+var min = Math.min;
+
+// Having a toString local variable name breaks in Opera so use to_string.
+var to_string = ObjectPrototype.toString;
+
+var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
+var isCallable; /* inlined from https://npmjs.com/is-callable */ var fnToStr = Function.prototype.toString, tryFunctionObject = function tryFunctionObject(value) { try { fnToStr.call(value); return true; } catch (e) { return false; } }, fnClass = '[object Function]', genClass = '[object GeneratorFunction]'; isCallable = function isCallable(value) { if (typeof value !== 'function') { return false; } if (hasToStringTag) { return tryFunctionObject(value); } var strClass = to_string.call(value); return strClass === fnClass || strClass === genClass; };
+var isRegex; /* inlined from https://npmjs.com/is-regex */ var regexExec = RegExp.prototype.exec, tryRegexExec = function tryRegexExec(value) { try { regexExec.call(value); return true; } catch (e) { return false; } }, regexClass = '[object RegExp]'; isRegex = function isRegex(value) { if (typeof value !== 'object') { return false; } return hasToStringTag ? tryRegexExec(value) : to_string.call(value) === regexClass; };
+var isString; /* inlined from https://npmjs.com/is-string */ var strValue = String.prototype.valueOf, tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }, stringClass = '[object String]'; isString = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : to_string.call(value) === stringClass; };
+
+/* inlined from http://npmjs.com/define-properties */
+var defineProperties = (function (has) {
+  var supportsDescriptors = $Object.defineProperty && (function () {
+      try {
+          var obj = {};
+          $Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
+          for (var _ in obj) { return false; }
+          return obj.x === obj;
+      } catch (e) { /* this is ES3 */
+          return false;
+      }
+  }());
+
+  // Define configurable, writable and non-enumerable props
+  // if they don't exist.
+  var defineProperty;
+  if (supportsDescriptors) {
+      defineProperty = function (object, name, method, forceAssign) {
+          if (!forceAssign && (name in object)) { return; }
+          $Object.defineProperty(object, name, {
+              configurable: true,
+              enumerable: false,
+              writable: true,
+              value: method
+          });
+      };
+  } else {
+      defineProperty = function (object, name, method, forceAssign) {
+          if (!forceAssign && (name in object)) { return; }
+          object[name] = method;
+      };
+  }
+  return function defineProperties(object, map, forceAssign) {
+      for (var name in map) {
+          if (has.call(map, name)) {
+            defineProperty(object, name, map[name], forceAssign);
+          }
+      }
+  };
+}(ObjectPrototype.hasOwnProperty));
+
+//
+// Util
+// ======
+//
+
+/* replaceable with https://npmjs.com/package/es-abstract /helpers/isPrimitive */
+var isPrimitive = function isPrimitive(input) {
+    var type = typeof input;
+    return input === null || (type !== 'object' && type !== 'function');
+};
+
+var ES = {
+    // ES5 9.4
+    // http://es5.github.com/#x9.4
+    // http://jsperf.com/to-integer
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToInteger */
+    ToInteger: function ToInteger(num) {
+        var n = +num;
+        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;
+    },
+
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToPrimitive */
+    ToPrimitive: function ToPrimitive(input) {
+        var val, valueOf, toStr;
+        if (isPrimitive(input)) {
+            return input;
+        }
+        valueOf = input.valueOf;
+        if (isCallable(valueOf)) {
+            val = valueOf.call(input);
+            if (isPrimitive(val)) {
+                return val;
+            }
+        }
+        toStr = input.toString;
+        if (isCallable(toStr)) {
+            val = toStr.call(input);
+            if (isPrimitive(val)) {
+                return val;
+            }
+        }
+        throw new TypeError();
+    },
+
+    // ES5 9.9
+    // http://es5.github.com/#x9.9
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */
+    ToObject: function (o) {
+        /* jshint eqnull: true */
+        if (o == null) { // this matches both null and undefined
+            throw new TypeError("can't convert " + o + ' to object');
+        }
+        return $Object(o);
+    },
+
+    /* replaceable with https://npmjs.com/package/es-abstract ES5.ToUint32 */
+    ToUint32: function ToUint32(x) {
+        return x >>> 0;
+    }
+};
+
+//
+// Function
+// ========
+//
+
+// ES-5 15.3.4.5
+// http://es5.github.com/#x15.3.4.5
+
+var Empty = function Empty() {};
+
+defineProperties(FunctionPrototype, {
+    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 (!isCallable(target)) {
+            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 bound;
+        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,
+                    array_concat.call(args, 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,
+                    array_concat.call(args, 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 = 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++) {
+            array_push.call(boundArgs, '$' + 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.
+        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;
+    }
+});
+
+// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
+// us it in defining shortcuts.
+var owns = call.bind(ObjectPrototype.hasOwnProperty);
+var toStr = call.bind(ObjectPrototype.toString);
+var strSlice = call.bind(StringPrototype.slice);
+var strSplit = call.bind(StringPrototype.split);
+
+//
+// Array
+// =====
+//
+
+var isArray = $Array.isArray || function isArray(obj) {
+    return toStr(obj) === '[object Array]';
+};
+
+// 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"
+var hasUnshiftReturnValueBug = [].unshift(0) !== 1;
+defineProperties(ArrayPrototype, {
+    unshift: function () {
+        array_unshift.apply(this, arguments);
+        return this.length;
+    }
+}, hasUnshiftReturnValueBug);
+
+// ES5 15.4.3.2
+// http://es5.github.com/#x15.4.3.2
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
+defineProperties($Array, { isArray: isArray });
+
+// 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');
+var splitString = boxedString[0] !== 'a' || !(0 in boxedString);
+
+var properlyBoxesContext = function properlyBoxed(method) {
+    // Check node 0.6.21 bug where third parameter is not boxed
+    var properlyBoxesNonStrict = true;
+    var properlyBoxesStrict = true;
+    if (method) {
+        method.call('foo', function (_, __, context) {
+            if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
+        });
+
+        method.call([1], function () {
+            'use strict';
+
+            properlyBoxesStrict = typeof this === 'string';
+        }, 'x');
+    }
+    return !!method && properlyBoxesNonStrict && properlyBoxesStrict;
+};
+
+defineProperties(ArrayPrototype, {
+    forEach: function forEach(callbackfn /*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var i = -1;
+        var length = self.length >>> 0;
+        var T;
+        if (arguments.length > 1) {
+          T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.forEach callback must be a function');
+        }
+
+        while (++i < length) {
+            if (i in self) {
+                // Invoke the callback function with call, passing arguments:
+                // context, property value, property key, thisArg object
+                if (typeof T !== 'undefined') {
+                    callbackfn.call(T, self[i], i, object);
+                } else {
+                    callbackfn(self[i], i, object);
+                }
+            }
+        }
+    }
+}, !properlyBoxesContext(ArrayPrototype.forEach));
+
+// 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
+defineProperties(ArrayPrototype, {
+    map: function map(callbackfn/*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = self.length >>> 0;
+        var result = $Array(length);
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.map callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self) {
+                if (typeof T !== 'undefined') {
+                    result[i] = callbackfn.call(T, self[i], i, object);
+                } else {
+                    result[i] = callbackfn(self[i], i, object);
+                }
+            }
+        }
+        return result;
+    }
+}, !properlyBoxesContext(ArrayPrototype.map));
+
+// 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
+defineProperties(ArrayPrototype, {
+    filter: function filter(callbackfn /*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = self.length >>> 0;
+        var result = [];
+        var value;
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.filter callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self) {
+                value = self[i];
+                if (typeof T === 'undefined' ? callbackfn(value, i, object) : callbackfn.call(T, value, i, object)) {
+                    array_push.call(result, value);
+                }
+            }
+        }
+        return result;
+    }
+}, !properlyBoxesContext(ArrayPrototype.filter));
+
+// ES5 15.4.4.16
+// http://es5.github.com/#x15.4.4.16
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
+defineProperties(ArrayPrototype, {
+    every: function every(callbackfn /*, thisArg*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = self.length >>> 0;
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.every callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self && !(typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}, !properlyBoxesContext(ArrayPrototype.every));
+
+// ES5 15.4.4.17
+// http://es5.github.com/#x15.4.4.17
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
+defineProperties(ArrayPrototype, {
+    some: function some(callbackfn/*, thisArg */) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = self.length >>> 0;
+        var T;
+        if (arguments.length > 1) {
+            T = arguments[1];
+        }
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.some callback must be a function');
+        }
+
+        for (var i = 0; i < length; i++) {
+            if (i in self && (typeof T === 'undefined' ? callbackfn(self[i], i, object) : callbackfn.call(T, self[i], i, object))) {
+                return true;
+            }
+        }
+        return false;
+    }
+}, !properlyBoxesContext(ArrayPrototype.some));
+
+// 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
+var reduceCoercesToObject = false;
+if (ArrayPrototype.reduce) {
+    reduceCoercesToObject = typeof ArrayPrototype.reduce.call('es5', function (_, __, ___, list) { return list; }) === 'object';
+}
+defineProperties(ArrayPrototype, {
+    reduce: function reduce(callbackfn /*, initialValue*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = self.length >>> 0;
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.reduce callback must be a function');
+        }
+
+        // no value to return if no initial value and an empty array
+        if (length === 0 && 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 = callbackfn(result, self[i], i, object);
+            }
+        }
+
+        return result;
+    }
+}, !reduceCoercesToObject);
+
+// 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
+var reduceRightCoercesToObject = false;
+if (ArrayPrototype.reduceRight) {
+    reduceRightCoercesToObject = typeof ArrayPrototype.reduceRight.call('es5', function (_, __, ___, list) { return list; }) === 'object';
+}
+defineProperties(ArrayPrototype, {
+    reduceRight: function reduceRight(callbackfn/*, initial*/) {
+        var object = ES.ToObject(this);
+        var self = splitString && isString(this) ? strSplit(this, '') : object;
+        var length = self.length >>> 0;
+
+        // If no callback function or if callback is not a callable function
+        if (!isCallable(callbackfn)) {
+            throw new TypeError('Array.prototype.reduceRight callback must be a function');
+        }
+
+        // no value to return if no initial value, empty array
+        if (length === 0 && arguments.length === 1) {
+            throw new TypeError('reduceRight of empty array with no initial value');
+        }
+
+        var result;
+        var 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 self) {
+                result = callbackfn(result, self[i], i, object);
+            }
+        } while (i--);
+
+        return result;
+    }
+}, !reduceRightCoercesToObject);
+
+// ES5 15.4.4.14
+// http://es5.github.com/#x15.4.4.14
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
+var hasFirefox2IndexOfBug = ArrayPrototype.indexOf && [0, 1].indexOf(1, 2) !== -1;
+defineProperties(ArrayPrototype, {
+    indexOf: function indexOf(searchElement /*, fromIndex */) {
+        var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);
+        var length = self.length >>> 0;
+
+        if (length === 0) {
+            return -1;
+        }
+
+        var i = 0;
+        if (arguments.length > 1) {
+            i = ES.ToInteger(arguments[1]);
+        }
+
+        // handle negative indices
+        i = i >= 0 ? i : max(0, length + i);
+        for (; i < length; i++) {
+            if (i in self && self[i] === searchElement) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}, hasFirefox2IndexOfBug);
+
+// ES5 15.4.4.15
+// http://es5.github.com/#x15.4.4.15
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
+var hasFirefox2LastIndexOfBug = ArrayPrototype.lastIndexOf && [0, 1].lastIndexOf(0, -3) !== -1;
+defineProperties(ArrayPrototype, {
+    lastIndexOf: function lastIndexOf(searchElement /*, fromIndex */) {
+        var self = splitString && isString(this) ? strSplit(this, '') : ES.ToObject(this);
+        var length = self.length >>> 0;
+
+        if (length === 0) {
+            return -1;
+        }
+        var i = length - 1;
+        if (arguments.length > 1) {
+            i = min(i, ES.ToInteger(arguments[1]));
+        }
+        // handle negative indices
+        i = i >= 0 ? i : length - Math.abs(i);
+        for (; i >= 0; i--) {
+            if (i in self && searchElement === self[i]) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}, hasFirefox2LastIndexOfBug);
+
+// ES5 15.4.4.12
+// http://es5.github.com/#x15.4.4.12
+var spliceNoopReturnsEmptyArray = (function () {
+    var a = [1, 2];
+    var result = a.splice();
+    return a.length === 2 && isArray(result) && result.length === 0;
+}());
+defineProperties(ArrayPrototype, {
+    // Safari 5.0 bug where .splice() returns undefined
+    splice: function splice(start, deleteCount) {
+        if (arguments.length === 0) {
+            return [];
+        } else {
+            return array_splice.apply(this, arguments);
+        }
+    }
+}, !spliceNoopReturnsEmptyArray);
+
+var spliceWorksWithEmptyObject = (function () {
+    var obj = {};
+    ArrayPrototype.splice.call(obj, 0, 0, 1);
+    return obj.length === 1;
+}());
+defineProperties(ArrayPrototype, {
+    splice: function splice(start, deleteCount) {
+        if (arguments.length === 0) { return []; }
+        var args = arguments;
+        this.length = max(ES.ToInteger(this.length), 0);
+        if (arguments.length > 0 && typeof deleteCount !== 'number') {
+            args = array_slice.call(arguments);
+            if (args.length < 2) {
+                array_push.call(args, this.length - start);
+            } else {
+                args[1] = ES.ToInteger(deleteCount);
+            }
+        }
+        return array_splice.apply(this, args);
+    }
+}, !spliceWorksWithEmptyObject);
+var spliceWorksWithLargeSparseArrays = (function () {
+    // Per https://github.com/es-shims/es5-shim/issues/295
+    // Safari 7/8 breaks with sparse arrays of size 1e5 or greater
+    var arr = new $Array(1e5);
+    // note: the index MUST be 8 or larger or the test will false pass
+    arr[8] = 'x';
+    arr.splice(1, 1);
+    // note: this test must be defined *after* the indexOf shim
+    // per https://github.com/es-shims/es5-shim/issues/313
+    return arr.indexOf('x') === 7;
+}());
+var spliceWorksWithSmallSparseArrays = (function () {
+    // Per https://github.com/es-shims/es5-shim/issues/295
+    // Opera 12.15 breaks on this, no idea why.
+    var n = 256;
+    var arr = [];
+    arr[n] = 'a';
+    arr.splice(n + 1, 0, 'b');
+    return arr[n] === 'a';
+}());
+defineProperties(ArrayPrototype, {
+    splice: function splice(start, deleteCount) {
+        var O = ES.ToObject(this);
+        var A = [];
+        var len = ES.ToUint32(O.length);
+        var relativeStart = ES.ToInteger(start);
+        var actualStart = relativeStart < 0 ? max((len + relativeStart), 0) : min(relativeStart, len);
+        var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart);
+
+        var k = 0;
+        var from;
+        while (k < actualDeleteCount) {
+            from = $String(actualStart + k);
+            if (owns(O, from)) {
+                A[k] = O[from];
+            }
+            k += 1;
+        }
+
+        var items = array_slice.call(arguments, 2);
+        var itemCount = items.length;
+        var to;
+        if (itemCount < actualDeleteCount) {
+            k = actualStart;
+            while (k < (len - actualDeleteCount)) {
+                from = $String(k + actualDeleteCount);
+                to = $String(k + itemCount);
+                if (owns(O, from)) {
+                    O[to] = O[from];
+                } else {
+                    delete O[to];
+                }
+                k += 1;
+            }
+            k = len;
+            while (k > (len - actualDeleteCount + itemCount)) {
+                delete O[k - 1];
+                k -= 1;
+            }
+        } else if (itemCount > actualDeleteCount) {
+            k = len - actualDeleteCount;
+            while (k > actualStart) {
+                from = $String(k + actualDeleteCount - 1);
+                to = $String(k + itemCount - 1);
+                if (owns(O, from)) {
+                    O[to] = O[from];
+                } else {
+                    delete O[to];
+                }
+                k -= 1;
+            }
+        }
+        k = actualStart;
+        for (var i = 0; i < items.length; ++i) {
+            O[k] = items[i];
+            k += 1;
+        }
+        O.length = len - actualDeleteCount + itemCount;
+
+        return A;
+    }
+}, !spliceWorksWithLargeSparseArrays || !spliceWorksWithSmallSparseArrays);
+
+//
+// Object
+// ======
+//
+
+// ES5 15.2.3.14
+// http://es5.github.com/#x15.2.3.14
+
+// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
+var hasDontEnumBug = !({ 'toString': null }).propertyIsEnumerable('toString');
+var hasProtoEnumBug = function () {}.propertyIsEnumerable('prototype');
+var hasStringEnumBug = !owns('x', '0');
+var equalsConstructorPrototype = function (o) {
+    var ctor = o.constructor;
+    return ctor && ctor.prototype === o;
+};
+var blacklistedKeys = {
+    $window: true,
+    $console: true,
+    $parent: true,
+    $self: true,
+    $frames: true,
+    $frameElement: true,
+    $webkitIndexedDB: true,
+    $webkitStorageInfo: true
+};
+var hasAutomationEqualityBug = (function () {
+    /* globals window */
+    if (typeof window === 'undefined') { return false; }
+    for (var k in window) {
+        if (!blacklistedKeys['$' + k] && owns(window, k) && window[k] !== null && typeof window[k] === 'object') {
+            try {
+                equalsConstructorPrototype(window[k]);
+            } catch (e) {
+                return true;
+            }
+        }
+    }
+    return false;
+}());
+var equalsConstructorPrototypeIfNotBuggy = function (object) {
+    if (typeof window === 'undefined' || !hasAutomationEqualityBug) { return equalsConstructorPrototype(object); }
+    try {
+        return equalsConstructorPrototype(object);
+    } catch (e) {
+        return false;
+    }
+};
+var dontEnums = [
+    'toString',
+    'toLocaleString',
+    'valueOf',
+    'hasOwnProperty',
+    'isPrototypeOf',
+    'propertyIsEnumerable',
+    'constructor'
+];
+var dontEnumsLength = dontEnums.length;
+
+var isArguments = function isArguments(value) {
+    var str = toStr(value);
+    var isArgs = str === '[object Arguments]';
+    if (!isArgs) {
+        isArgs = !isArray(value) &&
+          value !== null &&
+          typeof value === 'object' &&
+          typeof value.length === 'number' &&
+          value.length >= 0 &&
+          isCallable(value.callee);
+    }
+    return isArgs;
+};
+
+defineProperties($Object, {
+    keys: function keys(object) {
+        var isFn = isCallable(object);
+        var isArgs = isArguments(object);
+        var isObject = object !== null && typeof object === 'object';
+        var isStr = isObject && isString(object);
+
+        if (!isObject && !isFn && !isArgs) {
+            throw new TypeError('Object.keys called on a non-object');
+        }
+
+        var theKeys = [];
+        var skipProto = hasProtoEnumBug && isFn;
+        if ((isStr && hasStringEnumBug) || isArgs) {
+            for (var i = 0; i < object.length; ++i) {
+                array_push.call(theKeys, $String(i));
+            }
+        }
+
+        if (!isArgs) {
+            for (var name in object) {
+                if (!(skipProto && name === 'prototype') && owns(object, name)) {
+                    array_push.call(theKeys, $String(name));
+                }
+            }
+        }
+
+        if (hasDontEnumBug) {
+            var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);
+            for (var j = 0; j < dontEnumsLength; j++) {
+                var dontEnum = dontEnums[j];
+                if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) {
+                    array_push.call(theKeys, dontEnum);
+                }
+            }
+        }
+        return theKeys;
+    }
+});
+
+var keysWorksWithArguments = $Object.keys && (function () {
+    // Safari 5.0 bug
+    return $Object.keys(arguments).length === 2;
+}(1, 2));
+var originalKeys = $Object.keys;
+defineProperties($Object, {
+    keys: function keys(object) {
+        if (isArguments(object)) {
+            return originalKeys(array_slice.call(object));
+        } else {
+            return originalKeys(object);
+        }
+    }
+}, !keysWorksWithArguments);
+
+//
+// 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;
+var negativeYearString = '-000001';
+var hasNegativeDateBug = Date.prototype.toISOString && new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1;
+var hasSafari51DateBug = Date.prototype.toISOString && new Date(-1).toISOString() !== '1969-12-31T23:59:59.999Z';
+
+defineProperties(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 ? '+' : '')) +
+            strSlice('00000' + Math.abs(year), (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 + '-' + array_slice.call(result, 0, 2).join('-') +
+            'T' + array_slice.call(result, 2).join(':') + '.' +
+            strSlice('000' + this.getUTCMilliseconds(), -3) + 'Z'
+        );
+    }
+}, hasNegativeDateBug || hasSafari51DateBug);
+
+// 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 = (function () {
+    try {
+        return 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) {
+        return false;
+    }
+}());
+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 ES.ToPrimitive(O, hint Number).
+        var O = $Object(this);
+        var tv = ES.ToPrimitive(O);
+        // 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".
+        var toISO = O.toISOString;
+        // 5. If IsCallable(toISO) is false, throw a TypeError exception.
+        if (!isCallable(toISO)) {
+            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
+var supportsExtendedYears = Date.parse('+033658-09-27T01:46:40.000Z') === 1e15;
+var acceptsInvalidDates = !isNaN(Date.parse('2012-04-04T24:00:00.500Z')) || !isNaN(Date.parse('2012-11-31T23:59:59.000Z')) || !isNaN(Date.parse('2012-12-31T23:59:60.000Z'));
+var doesNotParseY2KNewYear = isNaN(Date.parse('2000-01-01T00:00:00.000Z'));
+if (!Date.parse || doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) {
+    // XXX global assignment won't work in embeddings that use
+    // an alternate object for the context.
+    /* global Date: true */
+    /* eslint-disable no-undef */
+    Date = (function (NativeDate) {
+    /* eslint-enable no-undef */
+        // Date.length === 7
+        var DateShim = function Date(Y, M, D, h, m, s, ms) {
+            var length = arguments.length;
+            var date;
+            if (this instanceof NativeDate) {
+                date = length === 1 && $String(Y) === Y ? // isString(Y)
+                    // We explicitly pass it through parse:
+                    new NativeDate(DateShim.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();
+            } else {
+                date = NativeDate.apply(this, arguments);
+            }
+            // Prevent mixups with unfixed Date object
+            defineProperties(date, { constructor: DateShim }, true);
+            return date;
+        };
+
+        // 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];
+
+        var dayFromMonth = 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)
+            );
+        };
+
+        var toUTC = 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) {
+            if (owns(NativeDate, key)) {
+                DateShim[key] = NativeDate[key];
+            }
+        }
+
+        // Copy "native" methods explicitly; they may be non-enumerable
+        defineProperties(DateShim, {
+            now: NativeDate.now,
+            UTC: NativeDate.UTC
+        }, true);
+        DateShim.prototype = NativeDate.prototype;
+        defineProperties(DateShim.prototype, {
+            constructor: DateShim
+        }, true);
+
+        // Upgrade Date.parse to handle simplified ISO 8601 strings
+        var parseShim = 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);
+        };
+        defineProperties(DateShim, { parse: parseShim });
+
+        return DateShim;
+    }(Date));
+    /* global Date: false */
+}
+
+// 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
+var hasToFixedBugs = NumberPrototype.toFixed && (
+  (0.00008).toFixed(3) !== '0.000' ||
+  (0.9).toFixed(0) !== '1' ||
+  (1.255).toFixed(2) !== '1.25' ||
+  (1000000000000000128).toFixed(0) !== '1000000000000000128'
+);
+
+var toFixedHelpers = {
+  base: 1e7,
+  size: 6,
+  data: [0, 0, 0, 0, 0, 0],
+  multiply: function multiply(n, c) {
+      var i = -1;
+      var c2 = c;
+      while (++i < toFixedHelpers.size) {
+          c2 += n * toFixedHelpers.data[i];
+          toFixedHelpers.data[i] = c2 % toFixedHelpers.base;
+          c2 = Math.floor(c2 / toFixedHelpers.base);
+      }
+  },
+  divide: function divide(n) {
+      var i = toFixedHelpers.size, c = 0;
+      while (--i >= 0) {
+          c += toFixedHelpers.data[i];
+          toFixedHelpers.data[i] = Math.floor(c / n);
+          c = (c % n) * toFixedHelpers.base;
+      }
+  },
+  numToString: function numToString() {
+      var i = toFixedHelpers.size;
+      var s = '';
+      while (--i >= 0) {
+          if (s !== '' || i === 0 || toFixedHelpers.data[i] !== 0) {
+              var t = $String(toFixedHelpers.data[i]);
+              if (s === '') {
+                  s = t;
+              } else {
+                  s += strSlice('0000000', 0, 7 - t.length) + t;
+              }
+          }
+      }
+      return s;
+  },
+  pow: function pow(x, n, acc) {
+      return (n === 0 ? acc : (n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc)));
+  },
+  log: function log(x) {
+      var n = 0;
+      var x2 = x;
+      while (x2 >= 4096) {
+          n += 12;
+          x2 /= 4096;
+      }
+      while (x2 >= 2) {
+          n += 1;
+          x2 /= 2;
+      }
+      return n;
+  }
+};
+
+defineProperties(NumberPrototype, {
+    toFixed: function toFixed(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 = toFixedHelpers.log(x * toFixedHelpers.pow(2, 69, 1)) - 69;
+            z = (e < 0 ? x * toFixedHelpers.pow(2, -e, 1) : x / toFixedHelpers.pow(2, e, 1));
+            z *= 0x10000000000000; // Math.pow(2, 52);
+            e = 52 - e;
+
+            // -18 < e < 122
+            // x = z / 2 ^ e
+            if (e > 0) {
+                toFixedHelpers.multiply(0, z);
+                j = f;
+
+                while (j >= 7) {
+                    toFixedHelpers.multiply(1e7, 0);
+                    j -= 7;
+                }
+
+                toFixedHelpers.multiply(toFixedHelpers.pow(10, j, 1), 0);
+                j = e - 1;
+
+                while (j >= 23) {
+                    toFixedHelpers.divide(1 << 23);
+                    j -= 23;
+                }
+
+                toFixedHelpers.divide(1 << j);
+                toFixedHelpers.multiply(1, 1);
+                toFixedHelpers.divide(2);
+                m = toFixedHelpers.numToString();
+            } else {
+                toFixedHelpers.multiply(0, z);
+                toFixedHelpers.multiply(1 << (-e), 0);
+                m = toFixedHelpers.numToString() + strSlice('0.00000000000000000000', 2, 2 + f);
+            }
+        }
+
+        if (f > 0) {
+            k = m.length;
+
+            if (k <= f) {
+                m = s + strSlice('0.0000000000000000000', 0, f - k + 2) + m;
+            } else {
+                m = s + strSlice(m, 0, k - f) + '.' + strSlice(m, k - f);
+            }
+        } else {
+            m = s + m;
+        }
+
+        return m;
+    }
+}, hasToFixedBugs);
+
+//
+// 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 ["", "", "."]
+
+if (
+    'ab'.split(/(?:ab)*/).length !== 2 ||
+    '.'.split(/(.?)(.?)/).length !== 4 ||
+    'tesst'.split(/(s)*/)[1] === 't' ||
+    'test'.split(/(?:)/, -1).length !== 4 ||
+    ''.split(/.?/).length ||
+    '.'.split(/()()/).length > 1
+) {
+    (function () {
+        var compliantExecNpcg = typeof (/()??/).exec('')[1] === 'undefined'; // NPCG: nonparticipating capturing group
+
+        StringPrototype.split = function (separator, limit) {
+            var string = this;
+            if (typeof separator === 'undefined' && limit === 0) {
+                return [];
+            }
+
+            // If `separator` is not a regex, use native split
+            if (!isRegex(separator)) {
+                return strSplit(this, separator, limit);
+            }
+
+            var output = [];
+            var flags = (separator.ignoreCase ? 'i' : '') +
+                        (separator.multiline ? 'm' : '') +
+                        (separator.unicode ? 'u' : '') + // in ES6
+                        (separator.sticky ? 'y' : ''), // Firefox 3+ and ES6
+                lastLastIndex = 0,
+                // Make `global` and avoid `lastIndex` issues by working with a copy
+                separator2, match, lastIndex, lastLength;
+            var separatorCopy = new RegExp(separator.source, flags + 'g');
+            string += ''; // Type-convert
+            if (!compliantExecNpcg) {
+                // Doesn't need flags gy, but they don't hurt
+                separator2 = new RegExp('^' + separatorCopy.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
+             */
+            var splitLimit = typeof limit === 'undefined' ?
+                -1 >>> 0 : // Math.pow(2, 32) - 1
+                ES.ToUint32(limit);
+            match = separatorCopy.exec(string);
+            while (match) {
+                // `separatorCopy.lastIndex` is not reliable cross-browser
+                lastIndex = match.index + match[0].length;
+                if (lastIndex > lastLastIndex) {
+                    array_push.call(output, strSlice(string, lastLastIndex, match.index));
+                    // Fix browsers whose `exec` methods don't consistently return `undefined` for
+                    // nonparticipating capturing groups
+                    if (!compliantExecNpcg && match.length > 1) {
+                        /* eslint-disable no-loop-func */
+                        match[0].replace(separator2, function () {
+                            for (var i = 1; i < arguments.length - 2; i++) {
+                                if (typeof arguments[i] === 'undefined') {
+                                    match[i] = void 0;
+                                }
+                            }
+                        });
+                        /* eslint-enable no-loop-func */
+                    }
+                    if (match.length > 1 && match.index < string.length) {
+                        array_push.apply(output, array_slice.call(match, 1));
+                    }
+                    lastLength = match[0].length;
+                    lastLastIndex = lastIndex;
+                    if (output.length >= splitLimit) {
+                        break;
+                    }
+                }
+                if (separatorCopy.lastIndex === match.index) {
+                    separatorCopy.lastIndex++; // Avoid an infinite loop
+                }
+                match = separatorCopy.exec(string);
+            }
+            if (lastLastIndex === string.length) {
+                if (lastLength || !separatorCopy.test('')) {
+                    array_push.call(output, '');
+                }
+            } else {
+                array_push.call(output, strSlice(string, lastLastIndex));
+            }
+            return output.length > splitLimit ? strSlice(output, 0, splitLimit) : 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) {
+    StringPrototype.split = function split(separator, limit) {
+        if (typeof separator === 'undefined' && limit === 0) { return []; }
+        return strSplit(this, separator, limit);
+    };
+}
+
+var str_replace = StringPrototype.replace;
+var replaceReportsGroupsCorrectly = (function () {
+    var groups = [];
+    'x'.replace(/x(.)?/g, function (match, group) {
+        array_push.call(groups, group);
+    });
+    return groups.length === 1 && typeof groups[0] === 'undefined';
+}());
+
+if (!replaceReportsGroupsCorrectly) {
+    StringPrototype.replace = function replace(searchValue, replaceValue) {
+        var isFn = isCallable(replaceValue);
+        var hasCapturingGroups = isRegex(searchValue) && (/\)[*?]/).test(searchValue.source);
+        if (!isFn || !hasCapturingGroups) {
+            return str_replace.call(this, searchValue, replaceValue);
+        } else {
+            var wrappedReplaceValue = function (match) {
+                var length = arguments.length;
+                var originalLastIndex = searchValue.lastIndex;
+                searchValue.lastIndex = 0;
+                var args = searchValue.exec(match) || [];
+                searchValue.lastIndex = originalLastIndex;
+                array_push.call(args, arguments[length - 2], arguments[length - 1]);
+                return replaceValue.apply(this, args);
+            };
+            return str_replace.call(this, searchValue, wrappedReplaceValue);
+        }
+    };
+}
+
+// ECMA-262, 3rd B.2.3
+// Not an ECMAScript standard, 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
+var string_substr = StringPrototype.substr;
+var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';
+defineProperties(StringPrototype, {
+    substr: function substr(start, length) {
+        var normalizedStart = start;
+        if (start < 0) {
+            normalizedStart = max(this.length + start, 0);
+        }
+        return string_substr.call(this, normalizedStart, length);
+    }
+}, hasNegativeSubstrBug);
+
+// ES5 15.5.4.20
+// whitespace from: http://es5.github.io/#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';
+var zeroWidth = '\u200b';
+var wsRegexChars = '[' + ws + ']';
+var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*');
+var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$');
+var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim());
+defineProperties(StringPrototype, {
+    // http://blog.stevenlevithan.com/archives/faster-trim-javascript
+    // http://perfectionkills.com/whitespace-deviations/
+    trim: function trim() {
+        if (typeof this === 'undefined' || this === null) {
+            throw new TypeError("can't convert " + this + ' to object');
+        }
+        return $String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');
+    }
+}, hasTrimWhitespaceBug);
+
+// ES-5 15.1.2.2
+if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) {
+    /* global parseInt: true */
+    parseInt = (function (origParseInt) {
+        var hexRegex = /^0[xX]/;
+        return function parseInt(str, radix) {
+            var string = $String(str).trim();
+            var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10);
+            return origParseInt(string, defaultedRadix);
+        };
+    }(parseInt));
+}
+
+}));
+
+},{}],26:[function(_dereq_,module,exports){
+var arr = [];
+var each = arr.forEach;
+var slice = arr.slice;
+
+
+module.exports = function(obj) {
+    each.call(slice.call(arguments, 1), function(source) {
+        if (source) {
+            for (var prop in source) {
+                obj[prop] = source[prop];
+            }
+        }
+    });
+    return obj;
+};
+
+},{}],27:[function(_dereq_,module,exports){
+module.exports = isFunction
+
+var toString = Object.prototype.toString
+
+function isFunction (fn) {
+  var string = toString.call(fn)
+  return string === '[object Function]' ||
+    (typeof fn === 'function' && string !== '[object RegExp]') ||
+    (typeof window !== 'undefined' &&
+     // IE8 and below
+     (fn === window.setTimeout ||
+      fn === window.alert ||
+      fn === window.confirm ||
+      fn === window.prompt))
+};
+
+},{}],28:[function(_dereq_,module,exports){
+"use strict";
+
+module.exports = function isObject(x) {
+	return typeof x === "object" && x !== null;
+};
+
+},{}],29:[function(_dereq_,module,exports){
+/*!
+  * $script.js JS loader & dependency manager
+  * https://github.com/ded/script.js
+  * (c) Dustin Diaz 2014 | License MIT
+  */
+
+(function (name, definition) {
+  if (typeof module != 'undefined' && module.exports) module.exports = definition()
+  else if (typeof define == 'function' && define.amd) define(definition)
+  else this[name] = definition()
+})('$script', function () {
+  var doc = document
+    , head = doc.getElementsByTagName('head')[0]
+    , s = 'string'
+    , f = false
+    , push = 'push'
+    , readyState = 'readyState'
+    , onreadystatechange = 'onreadystatechange'
+    , list = {}
+    , ids = {}
+    , delay = {}
+    , scripts = {}
+    , scriptpath
+    , urlArgs
+
+  function every(ar, fn) {
+    for (var i = 0, j = ar.length; i < j; ++i) if (!fn(ar[i])) return f
+    return 1
+  }
+  function each(ar, fn) {
+    every(ar, function (el) {
+      return !fn(el)
+    })
+  }
+
+  function $script(paths, idOrDone, optDone) {
+    paths = paths[push] ? paths : [paths]
+    var idOrDoneIsDone = idOrDone && idOrDone.call
+      , done = idOrDoneIsDone ? idOrDone : optDone
+      , id = idOrDoneIsDone ? paths.join('') : idOrDone
+      , queue = paths.length
+    function loopFn(item) {
+      return item.call ? item() : list[item]
+    }
+    function callback() {
+      if (!--queue) {
+        list[id] = 1
+        done && done()
+        for (var dset in delay) {
+          every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = [])
+        }
+      }
+    }
+    setTimeout(function () {
+      each(paths, function loading(path, force) {
+        if (path === null) return callback()
+        path = !force && path.indexOf('.js') === -1 && !/^https?:\/\//.test(path) && scriptpath ? scriptpath + path + '.js' : path
+        if (scripts[path]) {
+          if (id) ids[id] = 1
+          return (scripts[path] == 2) ? callback() : setTimeout(function () { loading(path, true) }, 0)
+        }
+
+        scripts[path] = 1
+        if (id) ids[id] = 1
+        create(path, callback)
+      })
+    }, 0)
+    return $script
+  }
+
+  function create(path, fn) {
+    var el = doc.createElement('script'), loaded
+    el.onload = el.onerror = el[onreadystatechange] = function () {
+      if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) return;
+      el.onload = el[onreadystatechange] = null
+      loaded = 1
+      scripts[path] = 2
+      fn()
+    }
+    el.async = 1
+    el.src = urlArgs ? path + (path.indexOf('?') === -1 ? '?' : '&') + urlArgs : path;
+    head.insertBefore(el, head.lastChild)
+  }
+
+  $script.get = create
+
+  $script.order = function (scripts, id, done) {
+    (function callback(s) {
+      s = scripts.shift()
+      !scripts.length ? $script(s, id, done) : $script(s, callback)
+    }())
+  }
+
+  $script.path = function (p) {
+    scriptpath = p
+  }
+  $script.urlArgs = function (str) {
+    urlArgs = str;
+  }
+  $script.ready = function (deps, ready, req) {
+    deps = deps[push] ? deps : [deps]
+    var missing = [];
+    !each(deps, function (dep) {
+      list[dep] || missing[push](dep);
+    }) && every(deps, function (dep) {return list[dep]}) ?
+      ready() : !function (key) {
+      delay[key] = delay[key] || []
+      delay[key][push](ready)
+      req && req(missing)
+    }(deps.join('|'))
+    return $script
+  }
+
+  $script.done = function (idOrDone) {
+    $script([null], idOrDone)
+  }
+
+  return $script
+});
+
+},{}]},{},[19])(19)
+});