Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/mdl-ext/es/accordion/accordion.js b/node_modules/mdl-ext/es/accordion/accordion.js
new file mode 100644
index 0000000..2d518ca
--- /dev/null
+++ b/node_modules/mdl-ext/es/accordion/accordion.js
@@ -0,0 +1,546 @@
+'use strict';
+
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
+var _constants = require('../utils/constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+(function () {
+  'use strict';
+
+  var ACCORDION = 'mdlext-accordion';
+  var ACCORDION_VERTICAL = 'mdlext-accordion--vertical';
+  var ACCORDION_HORIZONTAL = 'mdlext-accordion--horizontal';
+  var PANEL = 'mdlext-accordion__panel';
+  var PANEL_ROLE = 'presentation';
+  var TAB = 'mdlext-accordion__tab';
+  var TAB_CAPTION = 'mdlext-accordion__tab__caption';
+  var TAB_ROLE = 'tab';
+  var TABPANEL = 'mdlext-accordion__tabpanel';
+  var TABPANEL_ROLE = 'tabpanel';
+  var RIPPLE_EFFECT = 'mdlext-js-ripple-effect';
+  var RIPPLE = 'mdlext-accordion__tab--ripple';
+  var ANIMATION_EFFECT = 'mdlext-js-animation-effect';
+  var ANIMATION = 'mdlext-accordion__tabpanel--animation';
+
+  /**
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  var MaterialExtAccordion = function MaterialExtAccordion(element) {
+
+    // Stores the Accordion HTML element.
+    this.element_ = element;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtAccordion'] = MaterialExtAccordion;
+
+  // Helpers
+  var accordionPanelElements = function accordionPanelElements(element) {
+    if (!element) {
+      return {
+        panel: null,
+        tab: null,
+        tabpanel: null
+      };
+    } else if (element.classList.contains(PANEL)) {
+      return {
+        panel: element,
+        tab: element.querySelector('.' + TAB),
+        tabpanel: element.querySelector('.' + TABPANEL)
+      };
+    } else {
+      return {
+        panel: element.parentNode,
+        tab: element.parentNode.querySelector('.' + TAB),
+        tabpanel: element.parentNode.querySelector('.' + TABPANEL)
+      };
+    }
+  };
+
+  // Private methods.
+
+  /**
+   * Handles custom command event, 'open', 'close', 'toggle' or upgrade
+   * @param event. A custom event
+   * @private
+   */
+  MaterialExtAccordion.prototype.commandHandler_ = function (event) {
+    event.preventDefault();
+    event.stopPropagation();
+
+    if (event && event.detail) {
+      this.command(event.detail);
+    }
+  };
+
+  /**
+   * Dispatch toggle event
+   * @param {string} state
+   * @param {Element} tab
+   * @param {Element} tabpanel
+   * @private
+   */
+  MaterialExtAccordion.prototype.dispatchToggleEvent_ = function (state, tab, tabpanel) {
+    var ce = new CustomEvent('toggle', {
+      bubbles: true,
+      cancelable: true,
+      detail: { state: state, tab: tab, tabpanel: tabpanel }
+    });
+    this.element_.dispatchEvent(ce);
+  };
+
+  /**
+   * Open tab
+   * @param {Element} panel
+   * @param {Element} tab
+   * @param {Element} tabpanel
+   * @private
+   */
+  MaterialExtAccordion.prototype.openTab_ = function (panel, tab, tabpanel) {
+    panel.classList.add(_constants.IS_EXPANDED);
+    tab.setAttribute(_constants.ARIA_EXPANDED, 'true');
+    tabpanel.removeAttribute('hidden');
+    tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'false');
+    this.dispatchToggleEvent_('open', tab, tabpanel);
+  };
+
+  /**
+   * Close tab
+   * @param {Element} panel
+   * @param {Element} tab
+   * @param {Element} tabpanel
+   * @private
+   */
+  MaterialExtAccordion.prototype.closeTab_ = function (panel, tab, tabpanel) {
+    panel.classList.remove(_constants.IS_EXPANDED);
+    tab.setAttribute(_constants.ARIA_EXPANDED, 'false');
+    tabpanel.setAttribute('hidden', '');
+    tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'true');
+    this.dispatchToggleEvent_('close', tab, tabpanel);
+  };
+
+  /**
+   * Toggle tab
+   * @param {Element} panel
+   * @param {Element} tab
+   * @param {Element} tabpanel
+   * @private
+   */
+  MaterialExtAccordion.prototype.toggleTab_ = function (panel, tab, tabpanel) {
+    if (!(this.element_.hasAttribute('disabled') || tab.hasAttribute('disabled'))) {
+      if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() === 'true') {
+        this.closeTab_(panel, tab, tabpanel);
+      } else {
+        if (this.element_.getAttribute(_constants.ARIA_MULTISELECTABLE).toLowerCase() !== 'true') {
+          this.closeTabs_();
+        }
+        this.openTab_(panel, tab, tabpanel);
+      }
+    }
+  };
+
+  /**
+   * Open tabs
+   * @private
+   */
+  MaterialExtAccordion.prototype.openTabs_ = function () {
+    var _this = this;
+
+    if (this.element_.getAttribute(_constants.ARIA_MULTISELECTABLE).toLowerCase() === 'true') {
+      [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + ACCORDION + ' > .' + PANEL))).filter(function (panel) {
+        return !panel.classList.contains(_constants.IS_EXPANDED);
+      }).forEach(function (closedItem) {
+        var tab = closedItem.querySelector('.' + TAB);
+        if (!tab.hasAttribute('disabled')) {
+          _this.openTab_(closedItem, tab, closedItem.querySelector('.' + TABPANEL));
+        }
+      });
+    }
+  };
+
+  /**
+   * Close tabs
+   * @private
+   */
+  MaterialExtAccordion.prototype.closeTabs_ = function () {
+    var _this2 = this;
+
+    [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + ACCORDION + ' > .' + PANEL + '.' + _constants.IS_EXPANDED))).forEach(function (panel) {
+      var tab = panel.querySelector('.' + TAB);
+      if (!tab.hasAttribute('disabled')) {
+        _this2.closeTab_(panel, tab, panel.querySelector('.' + TABPANEL));
+      }
+    });
+  };
+
+  // Public methods.
+
+  /**
+   * Upgrade an individual accordion tab
+   * @public
+   * @param {Element} tabElement The HTML element for the accordion panel.
+   */
+  MaterialExtAccordion.prototype.upgradeTab = function (tabElement) {
+    var _this3 = this;
+
+    var _accordionPanelElemen = accordionPanelElements(tabElement),
+        panel = _accordionPanelElemen.panel,
+        tab = _accordionPanelElemen.tab,
+        tabpanel = _accordionPanelElemen.tabpanel;
+
+    var disableTab = function disableTab() {
+      panel.classList.remove(_constants.IS_EXPANDED);
+      tab.setAttribute('tabindex', '-1');
+      tab.setAttribute(_constants.ARIA_EXPANDED, 'false');
+      tabpanel.setAttribute('hidden', '');
+      tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'true');
+    };
+
+    var enableTab = function enableTab() {
+      if (!tab.hasAttribute(_constants.ARIA_EXPANDED)) {
+        tab.setAttribute(_constants.ARIA_EXPANDED, 'false');
+      }
+
+      tab.setAttribute('tabindex', '0');
+
+      if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() === 'true') {
+        panel.classList.add(_constants.IS_EXPANDED);
+        tabpanel.removeAttribute('hidden');
+        tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'false');
+      } else {
+        panel.classList.remove(_constants.IS_EXPANDED);
+        tabpanel.setAttribute('hidden', '');
+        tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'true');
+      }
+    };
+
+    // In horizontal layout, caption must have a max-width defined to prevent pushing elements to the right of the caption out of view.
+    // In JsDom, offsetWidth and offsetHeight properties do not work, so this function is not testable.
+    /* istanbul ignore next */
+    var calcMaxTabCaptionWidth = function calcMaxTabCaptionWidth() {
+
+      var tabCaption = tab.querySelector('.' + TAB_CAPTION);
+      if (tabCaption !== null) {
+        var w = [].concat((0, _toConsumableArray3.default)(tab.children)).filter(function (el) {
+          return el.classList && !el.classList.contains(TAB_CAPTION);
+        }).reduce(function (v, el) {
+          return v + el.offsetWidth;
+        }, 0);
+
+        var maxWidth = tab.clientHeight - w;
+        if (maxWidth > 0) {
+          tabCaption.style['max-width'] = maxWidth + 'px';
+        }
+      }
+    };
+
+    var selectTab = function selectTab() {
+      if (!tab.hasAttribute(_constants.ARIA_SELECTED)) {
+        [].concat((0, _toConsumableArray3.default)(_this3.element_.querySelectorAll('.' + TAB + '[aria-selected="true"]'))).forEach(function (selectedTab) {
+          return selectedTab.removeAttribute(_constants.ARIA_SELECTED);
+        });
+        tab.setAttribute(_constants.ARIA_SELECTED, 'true');
+      }
+    };
+
+    var tabClickHandler = function tabClickHandler() {
+      _this3.toggleTab_(panel, tab, tabpanel);
+      selectTab();
+    };
+
+    var tabFocusHandler = function tabFocusHandler() {
+      selectTab();
+    };
+
+    var tabpanelClickHandler = function tabpanelClickHandler() {
+      selectTab();
+    };
+
+    var tabpanelFocusHandler = function tabpanelFocusHandler() {
+      selectTab();
+    };
+
+    var tabKeydownHandler = function tabKeydownHandler(e) {
+
+      if (_this3.element_.hasAttribute('disabled')) {
+        return;
+      }
+
+      if (e.keyCode === _constants.VK_END || e.keyCode === _constants.VK_HOME || e.keyCode === _constants.VK_ARROW_UP || e.keyCode === _constants.VK_ARROW_LEFT || e.keyCode === _constants.VK_ARROW_DOWN || e.keyCode === _constants.VK_ARROW_RIGHT) {
+
+        var nextTab = null;
+        var keyCode = e.keyCode;
+
+        if (keyCode === _constants.VK_HOME) {
+          nextTab = _this3.element_.querySelector('.' + PANEL + ':first-child > .' + TAB);
+          if (nextTab && nextTab.hasAttribute('disabled')) {
+            nextTab = null;
+            keyCode = _constants.VK_ARROW_DOWN;
+          }
+        } else if (keyCode === _constants.VK_END) {
+          nextTab = _this3.element_.querySelector('.' + PANEL + ':last-child > .' + TAB);
+          if (nextTab && nextTab.hasAttribute('disabled')) {
+            nextTab = null;
+            keyCode = _constants.VK_ARROW_UP;
+          }
+        }
+
+        if (!nextTab) {
+          var nextPanel = panel;
+
+          do {
+            if (keyCode === _constants.VK_ARROW_UP || keyCode === _constants.VK_ARROW_LEFT) {
+              nextPanel = nextPanel.previousElementSibling;
+              if (!nextPanel) {
+                nextPanel = _this3.element_.querySelector('.' + PANEL + ':last-child');
+              }
+              if (nextPanel) {
+                nextTab = nextPanel.querySelector('.' + PANEL + ' > .' + TAB);
+              }
+            } else if (keyCode === _constants.VK_ARROW_DOWN || keyCode === _constants.VK_ARROW_RIGHT) {
+              nextPanel = nextPanel.nextElementSibling;
+              if (!nextPanel) {
+                nextPanel = _this3.element_.querySelector('.' + PANEL + ':first-child');
+              }
+              if (nextPanel) {
+                nextTab = nextPanel.querySelector('.' + PANEL + ' > .' + TAB);
+              }
+            }
+
+            if (nextTab && nextTab.hasAttribute('disabled')) {
+              nextTab = null;
+            } else {
+              break;
+            }
+          } while (nextPanel !== panel);
+        }
+
+        if (nextTab) {
+          e.preventDefault();
+          e.stopPropagation();
+          nextTab.focus();
+
+          // Workaround for JSDom testing:
+          // In JsDom 'element.focus()' does not trigger any focus event
+          if (!nextTab.hasAttribute(_constants.ARIA_SELECTED)) {
+
+            [].concat((0, _toConsumableArray3.default)(_this3.element_.querySelectorAll('.' + TAB + '[aria-selected="true"]'))).forEach(function (selectedTab) {
+              return selectedTab.removeAttribute(_constants.ARIA_SELECTED);
+            });
+
+            nextTab.setAttribute(_constants.ARIA_SELECTED, 'true');
+          }
+        }
+      } else if (e.keyCode === _constants.VK_ENTER || e.keyCode === _constants.VK_SPACE) {
+        e.preventDefault();
+        e.stopPropagation();
+        _this3.toggleTab_(panel, tab, tabpanel);
+      }
+    };
+
+    if (tab === null) {
+      throw new Error('There must be a tab element for each accordion panel.');
+    }
+
+    if (tabpanel === null) {
+      throw new Error('There must be a tabpanel element for each accordion panel.');
+    }
+
+    panel.setAttribute('role', PANEL_ROLE);
+    tab.setAttribute('role', TAB_ROLE);
+    tabpanel.setAttribute('role', TABPANEL_ROLE);
+
+    if (tab.hasAttribute('disabled')) {
+      disableTab();
+    } else {
+      enableTab();
+    }
+
+    if (this.element_.classList.contains(ACCORDION_HORIZONTAL)) {
+      calcMaxTabCaptionWidth();
+    }
+
+    if (this.element_.classList.contains(RIPPLE_EFFECT)) {
+      tab.classList.add(RIPPLE);
+    }
+
+    if (this.element_.classList.contains(ANIMATION_EFFECT)) {
+      tabpanel.classList.add(ANIMATION);
+    }
+
+    // Remove listeners, just in case ...
+    tab.removeEventListener('click', tabClickHandler);
+    tab.removeEventListener('focus', tabFocusHandler);
+    tab.removeEventListener('keydown', tabKeydownHandler);
+    tabpanel.removeEventListener('click', tabpanelClickHandler);
+    tabpanel.removeEventListener('focus', tabpanelFocusHandler);
+
+    tab.addEventListener('click', tabClickHandler);
+    tab.addEventListener('focus', tabFocusHandler);
+    tab.addEventListener('keydown', tabKeydownHandler);
+    tabpanel.addEventListener('click', tabpanelClickHandler, true);
+    tabpanel.addEventListener('focus', tabpanelFocusHandler, true);
+  };
+  MaterialExtAccordion.prototype['upgradeTab'] = MaterialExtAccordion.prototype.upgradeTab;
+
+  /**
+   * Execute command
+   * @param detail
+   */
+  MaterialExtAccordion.prototype.command = function (detail) {
+    var _this4 = this;
+
+    var openTab = function openTab(tabElement) {
+
+      if (tabElement === undefined) {
+        _this4.openTabs_();
+      } else if (tabElement !== null) {
+        var _accordionPanelElemen2 = accordionPanelElements(tabElement),
+            panel = _accordionPanelElemen2.panel,
+            tab = _accordionPanelElemen2.tab,
+            tabpanel = _accordionPanelElemen2.tabpanel;
+
+        if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() !== 'true') {
+          _this4.toggleTab_(panel, tab, tabpanel);
+        }
+      }
+    };
+
+    var closeTab = function closeTab(tabElement) {
+      if (tabElement === undefined) {
+        _this4.closeTabs_();
+      } else if (tabElement !== null) {
+        var _accordionPanelElemen3 = accordionPanelElements(tabElement),
+            panel = _accordionPanelElemen3.panel,
+            tab = _accordionPanelElemen3.tab,
+            tabpanel = _accordionPanelElemen3.tabpanel;
+
+        if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() === 'true') {
+          _this4.toggleTab_(panel, tab, tabpanel);
+        }
+      }
+    };
+
+    var toggleTab = function toggleTab(tabElement) {
+      if (tabElement) {
+        var _accordionPanelElemen4 = accordionPanelElements(tabElement),
+            panel = _accordionPanelElemen4.panel,
+            tab = _accordionPanelElemen4.tab,
+            tabpanel = _accordionPanelElemen4.tabpanel;
+
+        _this4.toggleTab_(panel, tab, tabpanel);
+      }
+    };
+
+    if (detail && detail.action) {
+      var action = detail.action,
+          target = detail.target;
+
+
+      switch (action.toLowerCase()) {
+        case 'open':
+          openTab(target);
+          break;
+        case 'close':
+          closeTab(target);
+          break;
+        case 'toggle':
+          toggleTab(target);
+          break;
+        case 'upgrade':
+          if (target) {
+            this.upgradeTab(target);
+          }
+          break;
+        default:
+          throw new Error('Unknown action "' + action + '". Action must be one of "open", "close", "toggle" or "upgrade"');
+      }
+    }
+  };
+  MaterialExtAccordion.prototype['command'] = MaterialExtAccordion.prototype.command;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtAccordion.prototype.init = function () {
+    var _this5 = this;
+
+    if (this.element_) {
+      // Do the init required for this component to work
+      if (!(this.element_.classList.contains(ACCORDION_HORIZONTAL) || this.element_.classList.contains(ACCORDION_VERTICAL))) {
+        throw new Error('Accordion must have one of the classes "' + ACCORDION_HORIZONTAL + '" or "' + ACCORDION_VERTICAL + '"');
+      }
+
+      this.element_.setAttribute('role', 'tablist');
+
+      if (!this.element_.hasAttribute(_constants.ARIA_MULTISELECTABLE)) {
+        this.element_.setAttribute(_constants.ARIA_MULTISELECTABLE, 'false');
+      }
+
+      this.element_.removeEventListener('command', this.commandHandler_);
+      this.element_.addEventListener('command', this.commandHandler_.bind(this), false);
+
+      [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + ACCORDION + ' > .' + PANEL))).forEach(function (panel) {
+        return _this5.upgradeTab(panel);
+      });
+
+      // Set upgraded flag
+      this.element_.classList.add(_constants.IS_UPGRADED);
+    }
+  };
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   *
+   * Nothing to downgrade
+   *
+   MaterialExtAccordion.prototype.mdlDowngrade_ = function() {
+     'use strict';
+     console.log('***** MaterialExtAccordion.mdlDowngrade');
+   };
+   */
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtAccordion,
+    classAsString: 'MaterialExtAccordion',
+    cssClass: 'mdlext-js-accordion',
+    widget: true
+  });
+})(); /**
+       * @license
+       * Copyright 2016 Leif Olsen. All Rights Reserved.
+       *
+       * Licensed under the Apache License, Version 2.0 (the "License");
+       * you may not use this file except in compliance with the License.
+       * You may obtain a copy of the License at
+       *
+       *      http://www.apache.org/licenses/LICENSE-2.0
+       *
+       * Unless required by applicable law or agreed to in writing, software
+       * distributed under the License is distributed on an "AS IS" BASIS,
+       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+       * See the License for the specific language governing permissions and
+       * limitations under the License.
+       *
+       * This code is built with Google Material Design Lite,
+       * which is Licensed under the Apache License, Version 2.0
+       */
+
+/**
+ * A WAI-ARIA friendly accordion component.
+ * An accordion is a collection of expandable panels associated with a common outer container. Panels consist
+ * of a header and an associated content region or tabpanel. The primary use of an Accordion is to present multiple sections
+ * of content on a single page without scrolling, where all of the sections are peers in the application or object hierarchy.
+ * The general look is similar to a tree where each root tree node is an expandable accordion header. The user navigates
+ * and makes the contents of each panel visible (or not) by interacting with the Accordion Header
+ */
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/carousel/carousel.js b/node_modules/mdl-ext/es/carousel/carousel.js
new file mode 100644
index 0000000..51b9451
--- /dev/null
+++ b/node_modules/mdl-ext/es/carousel/carousel.js
@@ -0,0 +1,718 @@
+'use strict';
+
+var _isInteger = require('babel-runtime/core-js/number/is-integer');
+
+var _isInteger2 = _interopRequireDefault(_isInteger);
+
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
+var _assign = require('babel-runtime/core-js/object/assign');
+
+var _assign2 = _interopRequireDefault(_assign);
+
+var _intervalFunction = require('../utils/interval-function');
+
+var _intervalFunction2 = _interopRequireDefault(_intervalFunction);
+
+var _easing = require('../utils/easing');
+
+var _jsonUtils = require('../utils/json-utils');
+
+var _constants = require('../utils/constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * @license
+ * Copyright 2016 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+/**
+ * Image carousel
+ */
+
+var MDL_RIPPLE_CONTAINER = 'mdlext-carousel__slide__ripple-container';
+
+(function () {
+  'use strict';
+
+  //const CAROUSEL = 'mdlext-carousel';
+
+  var SLIDE = 'mdlext-carousel__slide';
+  var ROLE = 'list';
+  var SLIDE_ROLE = 'listitem';
+
+  /**
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  var MaterialExtCarousel = function MaterialExtCarousel(element) {
+    // Stores the element.
+    this.element_ = element;
+
+    // Default config
+    this.config_ = {
+      interactive: true,
+      autostart: false,
+      type: 'slide',
+      interval: 1000,
+      animationLoop: (0, _intervalFunction2.default)(1000)
+    };
+
+    this.scrollAnimation_ = (0, _intervalFunction2.default)(33);
+
+    // Initialize instance.
+    this.init();
+  };
+
+  window['MaterialExtCarousel'] = MaterialExtCarousel;
+
+  /**
+   * Start slideshow animation
+   * @private
+   */
+  MaterialExtCarousel.prototype.startSlideShow_ = function () {
+    var _this = this;
+
+    var nextSlide = function nextSlide() {
+      var slide = _this.element_.querySelector('.' + SLIDE + '[aria-selected]');
+      if (slide) {
+        slide.removeAttribute('aria-selected');
+        slide = slide.nextElementSibling;
+      }
+      if (!slide) {
+        slide = _this.element_.querySelector('.' + SLIDE + ':first-child');
+        _this.animateScroll_(0);
+      }
+      if (slide) {
+        _this.moveSlideIntoViewport_(slide);
+        slide.setAttribute('aria-selected', '');
+        _this.emitSelectEvent_('next', null, slide);
+        return true;
+      }
+      return false;
+    };
+
+    var nextScroll = function nextScroll(direction) {
+      var nextDirection = direction;
+
+      if ('next' === direction && _this.element_.scrollLeft === _this.element_.scrollWidth - _this.element_.clientWidth) {
+        nextDirection = 'prev';
+      } else if (_this.element_.scrollLeft === 0) {
+        nextDirection = 'next';
+      }
+      var x = 'next' === nextDirection ? Math.min(_this.element_.scrollLeft + _this.element_.clientWidth, _this.element_.scrollWidth - _this.element_.clientWidth) : Math.max(_this.element_.scrollLeft - _this.element_.clientWidth, 0);
+
+      _this.animateScroll_(x, 1000);
+      return nextDirection;
+    };
+
+    if (!this.config_.animationLoop.started) {
+      (function () {
+        _this.config_.animationLoop.interval = _this.config_.interval;
+        var direction = 'next';
+
+        if ('scroll' === _this.config_.type) {
+          _this.config_.animationLoop.start(function () {
+            direction = nextScroll(direction);
+            return true; // It runs until cancelSlideShow_ is triggered
+          });
+        } else {
+          nextSlide();
+          _this.config_.animationLoop.start(function () {
+            return nextSlide(); // It runs until cancelSlideShow_ is triggered
+          });
+        }
+      })();
+    }
+
+    // TODO: Pause animation when carousel is not in browser viewport or user changes tab
+  };
+
+  /**
+   * Cancel slideshow if running. Emmits a 'pause' event
+   * @private
+   */
+  MaterialExtCarousel.prototype.cancelSlideShow_ = function () {
+    if (this.config_.animationLoop.started) {
+      this.config_.animationLoop.stop();
+      this.emitSelectEvent_('pause', _constants.VK_ESC, this.element_.querySelector('.' + SLIDE + '[aria-selected]'));
+    }
+  };
+
+  /**
+   * Animate scroll
+   * @param newPosition
+   * @param newDuration
+   * @param completedCallback
+   * @private
+   */
+  MaterialExtCarousel.prototype.animateScroll_ = function (newPosition, newDuration, completedCallback) {
+    var _this2 = this;
+
+    var start = this.element_.scrollLeft;
+    var distance = newPosition - start;
+
+    if (distance !== 0) {
+      (function () {
+        var duration = Math.max(Math.min(Math.abs(distance), newDuration || 400), 100); // duration is between 100 and newDuration||400ms||distance
+        var t = 0;
+        _this2.scrollAnimation_.stop();
+        _this2.scrollAnimation_.start(function (timeElapsed) {
+          t += timeElapsed;
+          if (t < duration) {
+            _this2.element_.scrollLeft = (0, _easing.inOutQuintic)(t, start, distance, duration);
+            return true;
+          } else {
+            _this2.element_.scrollLeft = newPosition;
+            if (completedCallback) {
+              completedCallback();
+            }
+            return false;
+          }
+        });
+      })();
+    } else {
+      if (completedCallback) {
+        completedCallback();
+      }
+    }
+  };
+
+  /**
+   * Execute commend
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.command_ = function (event) {
+    var _this3 = this;
+
+    var x = 0;
+    var slide = null;
+    var a = event.detail.action.toLowerCase();
+
+    // Cancel slideshow if running
+    this.cancelSlideShow_();
+
+    switch (a) {
+      case 'first':
+        slide = this.element_.querySelector('.' + SLIDE + ':first-child');
+        break;
+
+      case 'last':
+        x = this.element_.scrollWidth - this.element_.clientWidth;
+        slide = this.element_.querySelector('.' + SLIDE + ':last-child');
+        break;
+
+      case 'scroll-prev':
+        x = Math.max(this.element_.scrollLeft - this.element_.clientWidth, 0);
+        break;
+
+      case 'scroll-next':
+        x = Math.min(this.element_.scrollLeft + this.element_.clientWidth, this.element_.scrollWidth - this.element_.clientWidth);
+        break;
+
+      case 'next':
+      case 'prev':
+        slide = this.element_.querySelector('.' + SLIDE + '[aria-selected]');
+        if (slide) {
+          slide = a === 'next' ? slide.nextElementSibling : slide.previousElementSibling;
+          this.setAriaSelected_(slide);
+          this.emitSelectEvent_(a, null, slide);
+        }
+        return;
+
+      case 'play':
+        (0, _assign2.default)(this.config_, event.detail);
+        this.startSlideShow_();
+        return;
+
+      case 'pause':
+        return;
+
+      default:
+        return;
+    }
+
+    this.animateScroll_(x, undefined, function () {
+      if ('scroll-next' === a || 'scroll-prev' === a) {
+        var slides = _this3.getSlidesInViewport_();
+        if (slides.length > 0) {
+          slide = 'scroll-next' === a ? slides[0] : slides[slides.length - 1];
+        }
+      }
+      _this3.setAriaSelected_(slide);
+      _this3.emitSelectEvent_(a, null, slide);
+    });
+  };
+
+  /**
+   * Handles custom command event, 'scroll-prev', 'scroll-next', 'first', 'last', next, prev, play, pause
+   * @param event. A custom event
+   * @private
+   */
+  MaterialExtCarousel.prototype.commandHandler_ = function (event) {
+    event.preventDefault();
+    event.stopPropagation();
+    if (event.detail && event.detail.action) {
+      this.command_(event);
+    }
+  };
+
+  /**
+   * Handle keypress
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.keyDownHandler_ = function (event) {
+
+    if (event && event.target && event.target !== this.element_) {
+
+      var action = 'first';
+
+      if (event.keyCode === _constants.VK_HOME || event.keyCode === _constants.VK_END || event.keyCode === _constants.VK_PAGE_UP || event.keyCode === _constants.VK_PAGE_DOWN) {
+
+        event.preventDefault();
+        if (event.keyCode === _constants.VK_END) {
+          action = 'last';
+        } else if (event.keyCode === _constants.VK_PAGE_UP) {
+          action = 'scroll-prev';
+        } else if (event.keyCode === _constants.VK_PAGE_DOWN) {
+          action = 'scroll-next';
+        }
+
+        var cmd = new CustomEvent('select', {
+          detail: {
+            action: action
+          }
+        });
+        this.command_(cmd);
+      } else if (event.keyCode === _constants.VK_TAB || event.keyCode === _constants.VK_ENTER || event.keyCode === _constants.VK_SPACE || event.keyCode === _constants.VK_ARROW_UP || event.keyCode === _constants.VK_ARROW_LEFT || event.keyCode === _constants.VK_ARROW_DOWN || event.keyCode === _constants.VK_ARROW_RIGHT) {
+
+        var slide = getSlide_(event.target);
+
+        if (!slide) {
+          return;
+        }
+
+        // Cancel slideshow if running
+        this.cancelSlideShow_();
+
+        switch (event.keyCode) {
+          case _constants.VK_ARROW_UP:
+          case _constants.VK_ARROW_LEFT:
+            action = 'prev';
+            slide = slide.previousElementSibling;
+            break;
+
+          case _constants.VK_ARROW_DOWN:
+          case _constants.VK_ARROW_RIGHT:
+            action = 'next';
+            slide = slide.nextElementSibling;
+            break;
+
+          case _constants.VK_TAB:
+            if (event.shiftKey) {
+              action = 'prev';
+              slide = slide.previousElementSibling;
+            } else {
+              action = 'next';
+              slide = slide.nextElementSibling;
+            }
+            break;
+
+          case _constants.VK_SPACE:
+          case _constants.VK_ENTER:
+            action = 'select';
+            break;
+        }
+
+        if (slide) {
+          event.preventDefault();
+          setFocus_(slide);
+          this.emitSelectEvent_(action, event.keyCode, slide);
+        }
+      }
+    }
+  };
+
+  /**
+   * Handle dragging
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.dragHandler_ = function (event) {
+    var _this4 = this;
+
+    event.preventDefault();
+
+    // Cancel slideshow if running
+    this.cancelSlideShow_();
+
+    var updating = false;
+    var rAFDragId = 0;
+
+    var startX = event.clientX || (event.touches !== undefined ? event.touches[0].clientX : 0);
+    var prevX = startX;
+    var targetElement = event.target;
+
+    var update = function update(e) {
+      var currentX = e.clientX || (e.touches !== undefined ? e.touches[0].clientX : 0);
+      var dx = prevX - currentX;
+
+      if (dx < 0) {
+        _this4.element_.scrollLeft = Math.max(_this4.element_.scrollLeft + dx, 0);
+      } else if (dx > 0) {
+        _this4.element_.scrollLeft = Math.min(_this4.element_.scrollLeft + dx, _this4.element_.scrollWidth - _this4.element_.clientWidth);
+      }
+
+      prevX = currentX;
+      updating = false;
+    };
+
+    // drag handler
+    var drag = function drag(e) {
+      e.preventDefault();
+
+      if (!updating) {
+        rAFDragId = window.requestAnimationFrame(function () {
+          return update(e);
+        });
+        updating = true;
+      }
+    };
+
+    // end drag handler
+    var endDrag = function endDrag(e) {
+      e.preventDefault();
+
+      _this4.element_.removeEventListener('mousemove', drag);
+      _this4.element_.removeEventListener('touchmove', drag);
+      window.removeEventListener('mouseup', endDrag);
+      window.removeEventListener('touchend', endDrag);
+
+      // cancel any existing drag rAF, see: http://www.html5rocks.com/en/tutorials/speed/animations/
+      window.cancelAnimationFrame(rAFDragId);
+
+      var slide = getSlide_(targetElement);
+      setFocus_(slide);
+      _this4.emitSelectEvent_('click', null, slide);
+    };
+
+    this.element_.addEventListener('mousemove', drag);
+    this.element_.addEventListener('touchmove', drag);
+    window.addEventListener('mouseup', endDrag);
+    window.addEventListener('touchend', endDrag);
+  };
+
+  /**
+   * Handle click
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.clickHandler_ = function (event) {
+    // Click is handled by drag
+    event.preventDefault();
+  };
+
+  /**
+   * Handle focus
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.focusHandler_ = function (event) {
+    var slide = getSlide_(event.target);
+    if (slide) {
+      // The last focused/selected slide has 'aria-selected', even if focus is lost
+      this.setAriaSelected_(slide);
+      slide.classList.add(_constants.IS_FOCUSED);
+    }
+  };
+
+  /**
+   * Handle blur
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.blurHandler_ = function (event) {
+    var slide = getSlide_(event.target);
+    if (slide) {
+      slide.classList.remove(_constants.IS_FOCUSED);
+    }
+  };
+
+  /**
+   * Emits a custeom 'select' event
+   * @param command
+   * @param keyCode
+   * @param slide
+   * @private
+   */
+  MaterialExtCarousel.prototype.emitSelectEvent_ = function (command, keyCode, slide) {
+
+    if (slide) {
+      this.moveSlideIntoViewport_(slide);
+
+      var evt = new CustomEvent('select', {
+        bubbles: true,
+        cancelable: true,
+        detail: {
+          command: command,
+          keyCode: keyCode,
+          source: slide
+        }
+      });
+      this.element_.dispatchEvent(evt);
+    }
+  };
+
+  /**
+   * Get the first visible slide in component viewport
+   * @private
+   */
+  MaterialExtCarousel.prototype.getSlidesInViewport_ = function () {
+    var carouselRect = this.element_.getBoundingClientRect();
+
+    var slidesInViewport = [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE))).filter(function (slide) {
+      var slideRect = slide.getBoundingClientRect();
+      return slideRect.left >= carouselRect.left && slideRect.right <= carouselRect.right;
+    });
+    return slidesInViewport;
+  };
+
+  /**
+   * Move slide into component viewport - if needed
+   * @param slide
+   * @private
+   */
+  MaterialExtCarousel.prototype.moveSlideIntoViewport_ = function (slide) {
+    var carouselRect = this.element_.getBoundingClientRect();
+    var slideRect = slide.getBoundingClientRect();
+
+    if (slideRect.left < carouselRect.left) {
+      var x = this.element_.scrollLeft - (carouselRect.left - slideRect.left);
+      this.animateScroll_(x);
+    } else if (slideRect.right > carouselRect.right) {
+      var _x = this.element_.scrollLeft - (carouselRect.right - slideRect.right);
+      this.animateScroll_(_x);
+    }
+  };
+
+  /**
+   * Removes 'aria-selected' from all slides in carousel
+   * @private
+   */
+  MaterialExtCarousel.prototype.setAriaSelected_ = function (slide) {
+    if (slide) {
+      [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE + '[aria-selected]'))).forEach(function (slide) {
+        return slide.removeAttribute('aria-selected');
+      });
+      slide.setAttribute('aria-selected', '');
+    }
+  };
+
+  /**
+   * Removes event listeners
+   * @private
+   */
+  MaterialExtCarousel.prototype.removeListeners_ = function () {
+    this.element_.removeEventListener('focus', this.focusHandler_);
+    this.element_.removeEventListener('blur', this.blurHandler_);
+    this.element_.removeEventListener('keydown', this.keyDownHandler_);
+    this.element_.removeEventListener('mousedown', this.dragHandler_);
+    this.element_.removeEventListener('touchstart', this.dragHandler_);
+    this.element_.removeEventListener('click', this.clickHandler_, false);
+    this.element_.removeEventListener('command', this.commandHandler_);
+    this.element_.removeEventListener('mdl-componentdowngraded', this.mdlDowngrade_);
+  };
+
+  // Helpers
+  var getSlide_ = function getSlide_(element) {
+    return element.closest('.' + SLIDE);
+  };
+
+  var setFocus_ = function setFocus_(slide) {
+    if (slide) {
+      slide.focus();
+    }
+  };
+
+  var addRipple_ = function addRipple_(slide) {
+    if (!slide.querySelector('.' + MDL_RIPPLE_CONTAINER)) {
+      var rippleContainer = document.createElement('span');
+      rippleContainer.classList.add(MDL_RIPPLE_CONTAINER);
+      rippleContainer.classList.add(_constants.MDL_RIPPLE_EFFECT);
+      var ripple = document.createElement('span');
+      ripple.classList.add(_constants.MDL_RIPPLE);
+      rippleContainer.appendChild(ripple);
+
+      var img = slide.querySelector('img');
+      if (img) {
+        // rippleContainer blocks image title
+        rippleContainer.title = img.title;
+      }
+      slide.appendChild(rippleContainer);
+      componentHandler.upgradeElement(rippleContainer, _constants.MDL_RIPPLE_COMPONENT);
+    }
+  };
+  // End helpers
+
+
+  // Public methods.
+
+  /**
+   * Cancel animation - if running.
+   *
+   * @public
+   */
+  MaterialExtCarousel.prototype.stopAnimation = function () {
+    this.config_.animationLoop.stop();
+  };
+  MaterialExtCarousel.prototype['stopAnimation'] = MaterialExtCarousel.prototype.stopAnimation;
+
+  /**
+   * Upgrade slides
+   * Use if more list elements are added later (dynamically)
+   *
+   * @public
+   */
+  MaterialExtCarousel.prototype.upgradeSlides = function () {
+    var _this5 = this;
+
+    var hasRippleEffect = this.element_.classList.contains(_constants.MDL_RIPPLE_EFFECT);
+
+    [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE))).forEach(function (slide) {
+
+      slide.setAttribute('role', SLIDE_ROLE);
+
+      if (_this5.config_.interactive) {
+        if (!slide.getAttribute('tabindex')) {
+          slide.setAttribute('tabindex', '0');
+        }
+        if (hasRippleEffect) {
+          addRipple_(slide);
+        }
+      } else {
+        slide.setAttribute('tabindex', '-1');
+      }
+    });
+  };
+  MaterialExtCarousel.prototype['upgradeSlides'] = MaterialExtCarousel.prototype.upgradeSlides;
+
+  /**
+   * Get config object
+   *
+   * @public
+   */
+  MaterialExtCarousel.prototype.getConfig = function () {
+    return this.config_;
+  };
+  MaterialExtCarousel.prototype['getConfig'] = MaterialExtCarousel.prototype.getConfig;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtCarousel.prototype.init = function () {
+
+    if (this.element_) {
+      // Config
+      if (this.element_.hasAttribute('data-config')) {
+        this.config_ = (0, _jsonUtils.jsonStringToObject)(this.element_.getAttribute('data-config'), this.config_);
+      }
+
+      // Wai-Aria
+      this.element_.setAttribute('role', ROLE);
+
+      // Prefer tabindex -1
+      if (!(0, _isInteger2.default)(this.element_.getAttribute('tabindex'))) {
+        this.element_.setAttribute('tabindex', -1);
+      }
+
+      // Remove listeners, just in case ...
+      this.removeListeners_();
+
+      if (this.config_.interactive) {
+
+        // Ripple
+        var hasRippleEffect = this.element_.classList.contains(_constants.MDL_RIPPLE_EFFECT);
+        if (hasRippleEffect) {
+          this.element_.classList.add(_constants.MDL_RIPPLE_EFFECT_IGNORE_EVENTS);
+        }
+
+        // Listen to focus/blur events
+        this.element_.addEventListener('focus', this.focusHandler_.bind(this), true);
+        this.element_.addEventListener('blur', this.blurHandler_.bind(this), true);
+
+        // Listen to keyboard events
+        this.element_.addEventListener('keydown', this.keyDownHandler_.bind(this), false);
+
+        // Listen to drag events
+        this.element_.addEventListener('mousedown', this.dragHandler_.bind(this), false);
+        this.element_.addEventListener('touchstart', this.dragHandler_.bind(this), false);
+
+        // Listen to click events
+        this.element_.addEventListener('click', this.clickHandler_.bind(this), false);
+      }
+
+      // Listen to custom 'command' event
+      this.element_.addEventListener('command', this.commandHandler_.bind(this), false);
+
+      // Listen to 'mdl-componentdowngraded' event
+      this.element_.addEventListener('mdl-componentdowngraded', this.mdlDowngrade_.bind(this));
+
+      // Slides collection
+      this.upgradeSlides();
+
+      // Set upgraded flag
+      this.element_.classList.add(_constants.IS_UPGRADED);
+
+      if (this.config_.autostart) {
+        // Start slideshow
+        this.startSlideShow_();
+      }
+    }
+  };
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   */
+  MaterialExtCarousel.prototype.mdlDowngrade_ = function () {
+    'use strict';
+    //console.log('***** MaterialExtCarousel.mdlDowngrade_');
+
+    // Stop animation - if any
+
+    this.stopAnimation();
+
+    // Remove listeners
+    this.removeListeners_();
+  };
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtCarousel,
+    classAsString: 'MaterialExtCarousel',
+    cssClass: 'mdlext-js-carousel',
+    widget: true
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/collapsible/collapsible.js b/node_modules/mdl-ext/es/collapsible/collapsible.js
new file mode 100644
index 0000000..80c2db0
--- /dev/null
+++ b/node_modules/mdl-ext/es/collapsible/collapsible.js
@@ -0,0 +1,481 @@
+'use strict';
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _constants = require('../utils/constants');
+
+var _stringUtils = require('../utils/string-utils');
+
+var _domUtils = require('../utils/dom-utils');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var JS_COLLAPSIBLE = 'mdlext-js-collapsible'; /**
+                                               * @license
+                                               * Copyright 2016-2017 Leif Olsen. All Rights Reserved.
+                                               *
+                                               * Licensed under the Apache License, Version 2.0 (the "License");
+                                               * you may not use this file except in compliance with the License.
+                                               * You may obtain a copy of the License at
+                                               *
+                                               *      http://www.apache.org/licenses/LICENSE-2.0
+                                               *
+                                               * Unless required by applicable law or agreed to in writing, software
+                                               * distributed under the License is distributed on an "AS IS" BASIS,
+                                               * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+                                               * See the License for the specific language governing permissions and
+                                               * limitations under the License.
+                                               *
+                                               * This code is built with Google Material Design Lite,
+                                               * which is Licensed under the Apache License, Version 2.0
+                                               */
+
+/**
+ * A collapsible is a component to mark expandable and collapsible regions.
+ * The component use the aria-expanded state to indicate whether regions of
+ * the content are collapsible, and to expose whether a region is currently
+ * expanded or collapsed.
+ * @see https://www.w3.org/WAI/GL/wiki/Using_the_WAI-ARIA_aria-expanded_state_to_mark_expandable_and_collapsible_regions
+ */
+
+var COLLAPSIBLE_CONTROL_CLASS = 'mdlext-collapsible';
+var COLLAPSIBLE_GROUP_CLASS = 'mdlext-collapsible-group';
+var COLLAPSIBLE_REGION_CLASS = 'mdlext-collapsible-region';
+
+/**
+ * The collapsible component
+ */
+
+var Collapsible = function () {
+
+  /**
+   * @constructor
+   * @param {HTMLElement} element The element that this component is connected to.
+   */
+  function Collapsible(element) {
+    var _this = this;
+
+    (0, _classCallCheck3.default)(this, Collapsible);
+    this.element_ = null;
+    this.controlElement_ = null;
+
+    this.keyDownHandler = function (event) {
+      if (event.keyCode === _constants.VK_ENTER || event.keyCode === _constants.VK_SPACE) {
+        event.preventDefault();
+
+        // Trigger click
+        (event.target || _this.controlElement).dispatchEvent(new MouseEvent('click', {
+          bubbles: true,
+          cancelable: true,
+          view: window
+        }));
+      }
+    };
+
+    this.clickHandler = function (event) {
+      if (!_this.isDisabled) {
+        if (event.target !== _this.controlElement) {
+          // Do not toggle if a focusable element inside the control element triggered the event
+          var p = (0, _domUtils.getParentElements)(event.target, _this.controlElement);
+          p.push(event.target);
+          if (p.find(function (el) {
+            return (0, _domUtils.isFocusable)(el);
+          })) {
+            return;
+          }
+        }
+        _this.toggle();
+      }
+    };
+
+    this.element_ = element;
+    this.init();
+  }
+
+  (0, _createClass3.default)(Collapsible, [{
+    key: 'collapse',
+    value: function collapse() {
+      if (!this.isDisabled && this.isExpanded) {
+        if (this.dispatchToggleEvent('collapse')) {
+          this.controlElement.setAttribute('aria-expanded', 'false');
+          var regions = this.regionElements.slice(0);
+          for (var i = regions.length - 1; i >= 0; --i) {
+            regions[i].setAttribute('hidden', '');
+          }
+        }
+      }
+    }
+  }, {
+    key: 'expand',
+    value: function expand() {
+      if (!this.isDisabled && !this.isExpanded) {
+        if (this.dispatchToggleEvent('expand')) {
+          this.controlElement.setAttribute('aria-expanded', 'true');
+          this.regionElements.forEach(function (region) {
+            return region.removeAttribute('hidden');
+          });
+        }
+      }
+    }
+  }, {
+    key: 'toggle',
+    value: function toggle() {
+      if (this.isExpanded) {
+        this.collapse();
+      } else {
+        this.expand();
+      }
+    }
+  }, {
+    key: 'dispatchToggleEvent',
+    value: function dispatchToggleEvent(action) {
+      return this.element.dispatchEvent(new CustomEvent('toggle', {
+        bubbles: true,
+        cancelable: true,
+        detail: {
+          action: action
+        }
+      }));
+    }
+  }, {
+    key: 'disableToggle',
+    value: function disableToggle() {
+      this.controlElement.setAttribute('aria-disabled', true);
+    }
+  }, {
+    key: 'enableToggle',
+    value: function enableToggle() {
+      this.controlElement.removeAttribute('aria-disabled');
+    }
+  }, {
+    key: 'addRegionId',
+    value: function addRegionId(regionId) {
+      var ids = this.regionIds;
+      if (!ids.find(function (id) {
+        return regionId === id;
+      })) {
+        ids.push(regionId);
+        this.controlElement.setAttribute('aria-controls', ids.join(' '));
+      }
+    }
+  }, {
+    key: 'addRegionElement',
+    value: function addRegionElement(region) {
+      if (!(region.classList.contains(COLLAPSIBLE_GROUP_CLASS) || region.classList.contains(COLLAPSIBLE_REGION_CLASS))) {
+        region.classList.add(COLLAPSIBLE_GROUP_CLASS);
+      }
+
+      if (!region.hasAttribute('role')) {
+        var role = region.classList.contains(COLLAPSIBLE_GROUP_CLASS) ? 'group' : 'region';
+        region.setAttribute('role', role);
+      }
+
+      if (!region.hasAttribute('id')) {
+        region.id = region.getAttribute('role') + '-' + (0, _stringUtils.randomString)();
+      }
+
+      if (this.isExpanded) {
+        region.removeAttribute('hidden');
+      } else {
+        region.setAttribute('hidden', '');
+      }
+      this.addRegionId(region.id);
+    }
+  }, {
+    key: 'removeRegionElement',
+    value: function removeRegionElement(region) {
+      if (region && region.id) {
+        var ids = this.regionIds.filter(function (id) {
+          return id === region.id;
+        });
+        this.controlElement.setAttribute('aria-controls', ids.join(' '));
+      }
+    }
+  }, {
+    key: 'removeListeners',
+    value: function removeListeners() {
+      this.controlElement.removeEventListener('keydown', this.keyDownHandler);
+      this.controlElement.removeEventListener('click', this.clickHandler);
+    }
+  }, {
+    key: 'init',
+    value: function init() {
+      var _this2 = this;
+
+      var initControl = function initControl() {
+        // Find the button element
+        _this2.controlElement_ = _this2.element.querySelector('.' + COLLAPSIBLE_CONTROL_CLASS) || _this2.element;
+
+        // Add "aria-expanded" attribute if not present
+        if (!_this2.controlElement.hasAttribute('aria-expanded')) {
+          _this2.controlElement.setAttribute('aria-expanded', 'false');
+        }
+
+        // Add role=button if control != <button>
+        if (_this2.controlElement.nodeName.toLowerCase() !== 'button') {
+          _this2.controlElement.setAttribute('role', 'button');
+        }
+
+        // Add tabindex
+        if (!(0, _domUtils.isFocusable)(_this2.controlElement) && !_this2.controlElement.hasAttribute('tabindex')) {
+          _this2.controlElement.setAttribute('tabindex', '0');
+        }
+      };
+
+      var initRegions = function initRegions() {
+        var regions = [];
+        if (!_this2.controlElement.hasAttribute('aria-controls')) {
+          // Add siblings as collapsible region(s)
+          var r = _this2.element.nextElementSibling;
+          while (r) {
+            if (r.classList.contains(COLLAPSIBLE_GROUP_CLASS) || r.classList.contains(COLLAPSIBLE_REGION_CLASS)) {
+              regions.push(r);
+            } else if (r.classList.contains(JS_COLLAPSIBLE)) {
+              // A new collapsible component
+              break;
+            }
+            r = r.nextElementSibling;
+          }
+        } else {
+          regions = _this2.regionElements;
+        }
+        regions.forEach(function (region) {
+          return _this2.addRegionElement(region);
+        });
+      };
+
+      var addListeners = function addListeners() {
+        _this2.controlElement.addEventListener('keydown', _this2.keyDownHandler);
+        _this2.controlElement.addEventListener('click', _this2.clickHandler);
+      };
+
+      initControl();
+      initRegions();
+      this.removeListeners();
+      addListeners();
+    }
+  }, {
+    key: 'downgrade',
+    value: function downgrade() {
+      this.removeListeners();
+    }
+  }, {
+    key: 'element',
+    get: function get() {
+      return this.element_;
+    }
+  }, {
+    key: 'controlElement',
+    get: function get() {
+      return this.controlElement_;
+    }
+  }, {
+    key: 'isDisabled',
+    get: function get() {
+      return this.controlElement.hasAttribute('disabled') && this.controlElement.getAttribute('disabled').toLowerCase() !== 'false' || this.controlElement.hasAttribute('aria-disabled') && this.controlElement.getAttribute('aria-disabled').toLowerCase() !== 'false';
+    }
+  }, {
+    key: 'isExpanded',
+    get: function get() {
+      return this.controlElement.hasAttribute('aria-expanded') && this.controlElement.getAttribute('aria-expanded').toLowerCase() === 'true';
+    }
+  }, {
+    key: 'regionIds',
+    get: function get() {
+      return this.controlElement.hasAttribute('aria-controls') ? this.controlElement.getAttribute('aria-controls').split(' ') : [];
+    }
+  }, {
+    key: 'regionElements',
+    get: function get() {
+      return this.regionIds.map(function (id) {
+        return document.querySelector('#' + id);
+      }).filter(function (el) {
+        return el != null;
+      });
+    }
+  }]);
+  return Collapsible;
+}();
+
+(function () {
+  'use strict';
+
+  /**
+   * @constructor
+   * @param {HTMLElement} element The element that will be upgraded.
+   */
+
+  var MaterialExtCollapsible = function MaterialExtCollapsible(element) {
+    this.element_ = element;
+    this.collapsible = null;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtCollapsible'] = MaterialExtCollapsible;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtCollapsible.prototype.init = function () {
+    if (this.element_) {
+      this.collapsible = new Collapsible(this.element_);
+      this.element_.classList.add(_constants.IS_UPGRADED);
+
+      // Listen to 'mdl-componentdowngraded' event
+      this.element_.addEventListener('mdl-componentdowngraded', this.mdlDowngrade_.bind(this));
+    }
+  };
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   */
+  MaterialExtCollapsible.prototype.mdlDowngrade_ = function () {
+    this.collapsible.downgrade();
+  };
+
+  // Public methods.
+
+  /**
+   * Get control element.
+   * @return {HTMLElement} element The element that controls the collapsible region.
+   * @public
+   */
+  MaterialExtCollapsible.prototype.getControlElement = function () {
+    return this.collapsible.controlElement;
+  };
+  MaterialExtCollapsible.prototype['getControlElement'] = MaterialExtCollapsible.prototype.getControlElement;
+
+  /**
+   * Get region elements controlled by this collapsible
+   * @returns {Array<HTMLElement>} the collapsible region elements
+   * @public
+   */
+  MaterialExtCollapsible.prototype.getRegionElements = function () {
+    return this.collapsible.regionElements;
+  };
+  MaterialExtCollapsible.prototype['getRegionElements'] = MaterialExtCollapsible.prototype.getRegionElements;
+
+  /**
+   * Add region elements.
+   * @param {Array<HTMLElement>} elements The element that will be upgraded.
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.addRegionElements = function () {
+    var _this3 = this;
+
+    for (var _len = arguments.length, elements = Array(_len), _key = 0; _key < _len; _key++) {
+      elements[_key] = arguments[_key];
+    }
+
+    elements.forEach(function (element) {
+      return _this3.collapsible.addRegionElement(element);
+    });
+  };
+  MaterialExtCollapsible.prototype['addRegionElements'] = MaterialExtCollapsible.prototype.addRegionElements;
+
+  /**
+   * Remove collapsible region(s) from component.
+   * Note: This operation does not delete the element from the DOM tree.
+   * @param {Array<HTMLElement>} elements The element that will be upgraded.
+   * @public
+   */
+  MaterialExtCollapsible.prototype.removeRegionElements = function () {
+    var _this4 = this;
+
+    for (var _len2 = arguments.length, elements = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+      elements[_key2] = arguments[_key2];
+    }
+
+    elements.forEach(function (element) {
+      return _this4.collapsible.removeRegionElement(element);
+    });
+  };
+  MaterialExtCollapsible.prototype['removeRegionElements'] = MaterialExtCollapsible.prototype.removeRegionElements;
+
+  /**
+   * Expand collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.expand = function () {
+    this.collapsible.expand();
+  };
+  MaterialExtCollapsible.prototype['expand'] = MaterialExtCollapsible.prototype.expand;
+
+  /**
+   * Collapse collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.collapse = function () {
+    this.collapsible.collapse();
+  };
+  MaterialExtCollapsible.prototype['collapse'] = MaterialExtCollapsible.prototype.collapse;
+
+  /**
+   * Toggle collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.toggle = function () {
+    this.collapsible.toggle();
+  };
+  MaterialExtCollapsible.prototype['toggle'] = MaterialExtCollapsible.prototype.toggle;
+
+  /**
+   * Check whether component has aria-expanded state true
+   * @return {Boolean} true if aria-expanded="true", otherwise false
+   */
+  MaterialExtCollapsible.prototype.isExpanded = function () {
+    return this.collapsible.isExpanded;
+  };
+  MaterialExtCollapsible.prototype['isExpanded'] = MaterialExtCollapsible.prototype.isExpanded;
+
+  /**
+   * Check whether component has aria-disabled state set to true
+   * @return {Boolean} true if aria-disabled="true", otherwise false
+   */
+  MaterialExtCollapsible.prototype.isDisabled = function () {
+    return this.collapsible.isDisabled;
+  };
+  MaterialExtCollapsible.prototype['isDisabled'] = MaterialExtCollapsible.prototype.isDisabled;
+
+  /**
+   * Disables toggling of collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.disableToggle = function () {
+    this.collapsible.disableToggle();
+  };
+  MaterialExtCollapsible.prototype['disableToggle'] = MaterialExtCollapsible.prototype.disableToggle;
+
+  /**
+   * Enables toggling of collapsible region(s)
+   * @return {void}
+   * @public
+   */
+  MaterialExtCollapsible.prototype.enableToggle = function () {
+    this.collapsible.enableToggle();
+  };
+  MaterialExtCollapsible.prototype['enableToggle'] = MaterialExtCollapsible.prototype.enableToggle;
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtCollapsible,
+    classAsString: 'MaterialExtCollapsible',
+    cssClass: JS_COLLAPSIBLE,
+    widget: true
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/formatfield/formatfield.js b/node_modules/mdl-ext/es/formatfield/formatfield.js
new file mode 100644
index 0000000..271da23
--- /dev/null
+++ b/node_modules/mdl-ext/es/formatfield/formatfield.js
@@ -0,0 +1,306 @@
+'use strict';
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _jsonUtils = require('../utils/json-utils');
+
+var _constants = require('../utils/constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * @license
+ * Copyright 2016-2017 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+var JS_FORMAT_FIELD = 'mdlext-js-formatfield';
+var FORMAT_FIELD_COMPONENT = 'MaterialExtFormatfield';
+
+/**
+ * Detect browser locale
+ * @returns {string} the locale
+ * @see http://stackoverflow.com/questions/1043339/javascript-for-detecting-browser-language-preference
+ */
+var browserLanguage = function browserLanguage() {
+  return navigator.languages ? navigator.languages[0] : navigator.language || navigator.userLanguage;
+};
+
+/**
+ * The formatfield  formats an input field  using language sensitive number formatting.
+ */
+
+var FormatField = function () {
+  function FormatField(element) {
+    var _this = this;
+
+    (0, _classCallCheck3.default)(this, FormatField);
+    this.options_ = {};
+
+    this.clickHandler = function () {
+      clearTimeout(FormatField.timer);
+    };
+
+    this.focusInHandler = function () {
+      if (!(_this.input.readOnly || _this.input.disabled)) {
+        _this.input.value = _this.unformatInput();
+        //setTimeout(() => this.input.setSelectionRange(0, this.input.value.length), 20);
+        FormatField.timer = setTimeout(function () {
+          return _this.input.select();
+        }, 200);
+      }
+    };
+
+    this.focusOutHandler = function () {
+      clearTimeout(FormatField.timer);
+
+      if (!(_this.input.readOnly || _this.input.disabled)) {
+        _this.formatValue();
+      }
+    };
+
+    this.element_ = element;
+    this.init();
+  }
+
+  (0, _createClass3.default)(FormatField, [{
+    key: 'stripSeparatorsFromValue',
+    value: function stripSeparatorsFromValue() {
+      var _this2 = this;
+
+      var doReplace = function doReplace() {
+        return _this2.input.value.replace(/\s/g, '').replace(new RegExp(_this2.options.groupSeparator, 'g'), '').replace(_this2.options.decimalSeparator, '.');
+      };
+      //.replace(this.intlGroupSeparator_, ''),
+      //.replace(this.intlDecimalSeparator_, '.');
+
+      return this.input.value ? doReplace() : this.input.value;
+    }
+  }, {
+    key: 'fixSeparators',
+    value: function fixSeparators(value) {
+      var _this3 = this;
+
+      var doReplace = function doReplace() {
+        return value.replace(new RegExp(_this3.intlGroupSeparator_, 'g'), _this3.options.groupSeparator).replace(_this3.intlDecimalSeparator_, _this3.options.decimalSeparator);
+      };
+
+      return value ? doReplace() : value;
+    }
+  }, {
+    key: 'formatValue',
+    value: function formatValue() {
+      if (this.input.value) {
+        var v = new Intl.NumberFormat(this.options.locales, this.options).format(this.stripSeparatorsFromValue());
+
+        if ('NaN' !== v) {
+          this.input.value = this.fixSeparators(v);
+        }
+      }
+    }
+  }, {
+    key: 'unformat',
+    value: function unformat() {
+      var _this4 = this;
+
+      var doReplace = function doReplace() {
+        return _this4.input.value.replace(/\s/g, '').replace(new RegExp(_this4.options.groupSeparator, 'g'), '').replace(_this4.options.decimalSeparator, '.');
+      };
+
+      return this.input.value ? doReplace() : this.input.value;
+    }
+  }, {
+    key: 'unformatInput',
+    value: function unformatInput() {
+      var _this5 = this;
+
+      var doReplace = function doReplace() {
+        return _this5.input.value.replace(/\s/g, '').replace(new RegExp(_this5.options.groupSeparator, 'g'), '');
+      };
+
+      return this.input.value ? doReplace() : this.input.value;
+    }
+  }, {
+    key: 'removeListeners',
+    value: function removeListeners() {
+      this.input.removeEventListener('click', this.clickHandler);
+      this.input.removeEventListener('focusin', this.focusInHandler);
+      this.input.removeEventListener('focusout', this.focusOutHandler);
+    }
+  }, {
+    key: 'init',
+    value: function init() {
+      var _this6 = this;
+
+      var addListeners = function addListeners() {
+        _this6.input.addEventListener('click', _this6.clickHandler);
+        _this6.input.addEventListener('focusin', _this6.focusInHandler);
+        _this6.input.addEventListener('focusout', _this6.focusOutHandler);
+      };
+
+      var addOptions = function addOptions() {
+        var opts = _this6.element.getAttribute('data-formatfield-options') || _this6.input.getAttribute('data-formatfield-options');
+        if (opts) {
+          _this6.options_ = (0, _jsonUtils.jsonStringToObject)(opts, _this6.options);
+        }
+      };
+
+      var addLocale = function addLocale() {
+        if (!_this6.options.locales) {
+          _this6.options.locales = browserLanguage() || 'en-US'; //'nb-NO', //'en-US',
+        }
+      };
+
+      var addGrouping = function addGrouping() {
+        var s = 1234.5.toLocaleString(_this6.options.locales, {
+          style: 'decimal',
+          useGrouping: true,
+          minimumFractionDigits: 1,
+          maximumFractionDigits: 1
+        });
+
+        _this6.intlGroupSeparator_ = s.charAt(1);
+        _this6.intlDecimalSeparator_ = s.charAt(s.length - 2);
+        _this6.options.groupSeparator = _this6.options.groupSeparator || _this6.intlGroupSeparator_;
+        _this6.options.decimalSeparator = _this6.options.decimalSeparator || _this6.intlDecimalSeparator_;
+
+        if (_this6.options.groupSeparator === _this6.options.decimalSeparator) {
+          var e = 'Error! options.groupSeparator, "' + _this6.options.groupSeparator + '" ' + 'and options.decimalSeparator, ' + ('"' + _this6.options.decimalSeparator + '" should not be equal');
+          throw new Error(e);
+        }
+      };
+
+      this.input_ = this.element.querySelector('input') || this.element;
+
+      addOptions();
+      addLocale();
+      addGrouping();
+      this.formatValue();
+      addListeners();
+    }
+  }, {
+    key: 'downgrade',
+    value: function downgrade() {
+      this.removeListeners();
+    }
+  }, {
+    key: 'element',
+    get: function get() {
+      return this.element_;
+    }
+  }, {
+    key: 'input',
+    get: function get() {
+      return this.input_;
+    }
+  }, {
+    key: 'options',
+    get: function get() {
+      return this.options_;
+    }
+  }]);
+  return FormatField;
+}();
+
+FormatField.timer = null;
+
+
+(function () {
+  'use strict';
+
+  /**
+   * @constructor
+   * @param {HTMLElement} element The element that will be upgraded.
+   */
+
+  var MaterialExtFormatfield = function MaterialExtFormatfield(element) {
+    this.element_ = element;
+    this.formatField_ = null;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtFormatfield'] = MaterialExtFormatfield;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtFormatfield.prototype.init = function () {
+    if (this.element_) {
+      this.element_.classList.add(_constants.IS_UPGRADED);
+      this.formatField_ = new FormatField(this.element_);
+
+      // Listen to 'mdl-componentdowngraded' event
+      this.element_.addEventListener('mdl-componentdowngraded', this.mdlDowngrade_.bind(this));
+    }
+  };
+
+  /**
+   * Get options object
+   *
+   * @public
+   *
+   * @returns {Object} the options object
+   */
+  MaterialExtFormatfield.prototype.getOptions = function () {
+    return this.formatField_.options;
+  };
+  MaterialExtFormatfield.prototype['getOptions'] = MaterialExtFormatfield.prototype.getOptions;
+
+  /**
+   * A unformatted value is a string value where the locale specific decimal separator
+   * is replaced with a '.' separator and group separators are stripped.
+   * The returned value is suitable for parsing to a JavaScript numerical value.
+   *
+   * @example
+   * input.value = '1 234,5';
+   * inputElement.MaterialExtFormatfield.getUnformattedValue();
+   * // Returns '1234.5'
+   *
+   * @public
+   *
+   * @returns {String} the unformatted value
+   */
+  MaterialExtFormatfield.prototype.getUnformattedValue = function () {
+    return this.formatField_.unformat();
+  };
+  MaterialExtFormatfield.prototype['getUnformattedValue'] = MaterialExtFormatfield.prototype.getUnformattedValue;
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   */
+  MaterialExtFormatfield.prototype.mdlDowngrade_ = function () {
+    this.formatField_.downgrade();
+  };
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtFormatfield,
+    classAsString: FORMAT_FIELD_COMPONENT,
+    cssClass: JS_FORMAT_FIELD,
+    widget: true
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/index.js b/node_modules/mdl-ext/es/index.js
new file mode 100644
index 0000000..4407b52
--- /dev/null
+++ b/node_modules/mdl-ext/es/index.js
@@ -0,0 +1,21 @@
+'use strict';
+
+require('./utils');
+
+require('./accordion/accordion');
+
+require('./collapsible/collapsible');
+
+require('./carousel/carousel');
+
+require('./lightboard/lightboard');
+
+require('./lightbox/lightbox');
+
+require('./menu-button/menu-button');
+
+require('./selectfield/selectfield');
+
+require('./formatfield/formatfield');
+
+require('./sticky-header/sticky-header');
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/lightboard/lightboard.js b/node_modules/mdl-ext/es/lightboard/lightboard.js
new file mode 100644
index 0000000..03ceed8
--- /dev/null
+++ b/node_modules/mdl-ext/es/lightboard/lightboard.js
@@ -0,0 +1,316 @@
+'use strict';
+
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
+var _constants = require('../utils/constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var MDL_RIPPLE_CONTAINER = 'mdlext-lightboard__slide__ripple-container'; /**
+                                                                          * @license
+                                                                          * Copyright 2016 Leif Olsen. All Rights Reserved.
+                                                                          *
+                                                                          * Licensed under the Apache License, Version 2.0 (the "License");
+                                                                          * you may not use this file except in compliance with the License.
+                                                                          * You may obtain a copy of the License at
+                                                                          *
+                                                                          *      http://www.apache.org/licenses/LICENSE-2.0
+                                                                          *
+                                                                          * Unless required by applicable law or agreed to in writing, software
+                                                                          * distributed under the License is distributed on an "AS IS" BASIS,
+                                                                          * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+                                                                          * See the License for the specific language governing permissions and
+                                                                          * limitations under the License.
+                                                                          *
+                                                                          * This code is built with Google Material Design Lite,
+                                                                          * which is Licensed under the Apache License, Version 2.0
+                                                                          */
+
+/**
+ * A lightboard is a translucent surface illuminated from behind, used for situations
+ * where a shape laid upon the surface needs to be seen with high contrast. In the "old days" of photography
+ * photograpers used a lightboard to get a quick view of their slides. The goal is to create a responsive lightbox
+ * design, based on flex layout, similar to what is used in Adobe LightRoom to browse images.
+ */
+
+(function () {
+  'use strict';
+
+  //const LIGHTBOARD = 'mdlext-lightboard';
+
+  var LIGHTBOARD_ROLE = 'grid';
+  var SLIDE = 'mdlext-lightboard__slide';
+  var SLIDE_ROLE = 'gridcell';
+  var SLIDE_TABSTOP = 'mdlext-lightboard__slide__frame';
+  /**
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  var MaterialExtLightboard = function MaterialExtLightboard(element) {
+    // Stores the element.
+    this.element_ = element;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtLightboard'] = MaterialExtLightboard;
+
+  // Helpers
+  var getSlide = function getSlide(element) {
+    return element ? element.closest('.' + SLIDE) : null;
+  };
+
+  // Private methods.
+
+  /**
+   * Select a slide, i.e. set aria-selected="true"
+   * @param element
+   * @private
+   */
+  MaterialExtLightboard.prototype.selectSlide_ = function (element) {
+    var slide = getSlide(element);
+    if (slide && !slide.hasAttribute('aria-selected')) {
+      [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE + '[aria-selected="true"]'))).forEach(function (selectedSlide) {
+        return selectedSlide.removeAttribute('aria-selected');
+      });
+
+      slide.setAttribute('aria-selected', 'true');
+    }
+  };
+
+  /**
+   * Dispatch select event
+   * @param {Element} slide The slide that caused the event
+   * @private
+   */
+  MaterialExtLightboard.prototype.dispatchSelectEvent_ = function (slide) {
+    this.element_.dispatchEvent(new CustomEvent('select', {
+      bubbles: true,
+      cancelable: true,
+      detail: { source: slide }
+    }));
+  };
+
+  /**
+   * Handles custom command event, 'first', 'next', 'prev', 'last', 'select' or upgrade
+   * @param event. A custom event
+   * @private
+   */
+  MaterialExtLightboard.prototype.commandHandler_ = function (event) {
+    event.preventDefault();
+    event.stopPropagation();
+
+    if (event && event.detail) {
+      this.command(event.detail);
+    }
+  };
+
+  // Public methods
+
+  /**
+   * Initialize lightboard slides
+   * @public
+   */
+  MaterialExtLightboard.prototype.upgradeSlides = function () {
+
+    var addRipple = function addRipple(slide) {
+      // Use slide frame as ripple container
+      if (!slide.querySelector('.' + MDL_RIPPLE_CONTAINER)) {
+        var a = slide.querySelector('.' + SLIDE_TABSTOP);
+        if (a) {
+          var rippleContainer = a;
+          rippleContainer.classList.add(MDL_RIPPLE_CONTAINER);
+          rippleContainer.classList.add(_constants.MDL_RIPPLE_EFFECT);
+          var ripple = document.createElement('span');
+          ripple.classList.add(_constants.MDL_RIPPLE);
+          rippleContainer.appendChild(ripple);
+          componentHandler.upgradeElement(rippleContainer, _constants.MDL_RIPPLE_COMPONENT);
+        }
+      }
+    };
+
+    var hasRippleEffect = this.element_.classList.contains(_constants.MDL_RIPPLE_EFFECT);
+
+    [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + SLIDE))).forEach(function (slide) {
+
+      slide.setAttribute('role', SLIDE_ROLE);
+
+      if (!slide.querySelector('a')) {
+        slide.setAttribute('tabindex', '0');
+      }
+      if (hasRippleEffect) {
+        addRipple(slide);
+      }
+    });
+  };
+  MaterialExtLightboard.prototype['upgradeSlides'] = MaterialExtLightboard.prototype.upgradeSlides;
+
+  /**
+   * Execute command
+   * @param detail
+   * @public
+   */
+  MaterialExtLightboard.prototype.command = function (detail) {
+    var _this = this;
+
+    var firstSlide = function firstSlide() {
+      return _this.element_.querySelector('.' + SLIDE + ':first-child');
+    };
+
+    var lastSlide = function lastSlide() {
+      return _this.element_.querySelector('.' + SLIDE + ':last-child');
+    };
+
+    var nextSlide = function nextSlide() {
+      var slide = _this.element_.querySelector('.' + SLIDE + '[aria-selected="true"]').nextElementSibling;
+      return slide ? slide : firstSlide();
+    };
+
+    var prevSlide = function prevSlide() {
+      var slide = _this.element_.querySelector('.' + SLIDE + '[aria-selected="true"]').previousElementSibling;
+      return slide ? slide : lastSlide();
+    };
+
+    if (detail && detail.action) {
+      var action = detail.action,
+          target = detail.target;
+
+
+      var slide = void 0;
+      switch (action.toLowerCase()) {
+        case 'select':
+          slide = getSlide(target);
+          this.dispatchSelectEvent_(slide);
+          break;
+        case 'first':
+          slide = firstSlide();
+          break;
+        case 'next':
+          slide = nextSlide();
+          break;
+        case 'prev':
+          slide = prevSlide();
+          break;
+        case 'last':
+          slide = lastSlide();
+          break;
+        case 'upgrade':
+          this.upgradeSlides();
+          break;
+        default:
+          throw new Error('Unknown action "' + action + '". Action must be one of "first", "next", "prev", "last", "select" or "upgrade"');
+      }
+
+      if (slide) {
+        var a = slide.querySelector('a');
+        if (a) {
+          a.focus();
+        } else {
+          slide.focus();
+        }
+
+        // Workaround for JSDom testing:
+        // In JsDom 'element.focus()' does not trigger any focus event
+        if (!slide.hasAttribute('aria-selected')) {
+          this.selectSlide_(slide);
+        }
+      }
+    }
+  };
+  MaterialExtLightboard.prototype['command'] = MaterialExtLightboard.prototype.command;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtLightboard.prototype.init = function () {
+    var _this2 = this;
+
+    var keydownHandler = function keydownHandler(event) {
+
+      if (event.target !== _this2.element_) {
+        var action = void 0;
+        var target = void 0;
+        switch (event.keyCode) {
+          case _constants.VK_HOME:
+            action = 'first';
+            break;
+          case _constants.VK_END:
+            action = 'last';
+            break;
+          case _constants.VK_ARROW_UP:
+          case _constants.VK_ARROW_LEFT:
+            action = 'prev';
+            break;
+          case _constants.VK_ARROW_DOWN:
+          case _constants.VK_ARROW_RIGHT:
+            action = 'next';
+            break;
+          case _constants.VK_ENTER:
+          case _constants.VK_SPACE:
+            action = 'select';
+            target = event.target;
+            break;
+        }
+        if (action) {
+          event.preventDefault();
+          event.stopPropagation();
+          _this2.command({ action: action, target: target });
+        }
+      }
+    };
+
+    var clickHandler = function clickHandler(event) {
+      event.preventDefault();
+      event.stopPropagation();
+
+      if (event.target !== _this2.element_) {
+        _this2.command({ action: 'select', target: event.target });
+      }
+    };
+
+    var focusHandler = function focusHandler(event) {
+      event.preventDefault();
+      event.stopPropagation();
+
+      if (event.target !== _this2.element_) {
+        _this2.selectSlide_(event.target);
+      }
+    };
+
+    if (this.element_) {
+      this.element_.setAttribute('role', LIGHTBOARD_ROLE);
+
+      if (this.element_.classList.contains(_constants.MDL_RIPPLE_EFFECT)) {
+        this.element_.classList.add(_constants.MDL_RIPPLE_EFFECT_IGNORE_EVENTS);
+      }
+
+      // Remove listeners, just in case ...
+      this.element_.removeEventListener('command', this.commandHandler_);
+      this.element_.removeEventListener('keydown', keydownHandler);
+      this.element_.removeEventListener('click', clickHandler);
+      this.element_.removeEventListener('focus', focusHandler);
+
+      this.element_.addEventListener('command', this.commandHandler_.bind(this), false);
+      this.element_.addEventListener('keydown', keydownHandler, true);
+      this.element_.addEventListener('click', clickHandler, true);
+      this.element_.addEventListener('focus', focusHandler, true);
+
+      this.upgradeSlides();
+
+      this.element_.classList.add(_constants.IS_UPGRADED);
+    }
+  };
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  /* jshint undef:false */
+  componentHandler.register({
+    constructor: MaterialExtLightboard,
+    classAsString: 'MaterialExtLightboard',
+    cssClass: 'mdlext-js-lightboard',
+    widget: true
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/lightbox/lightbox.js b/node_modules/mdl-ext/es/lightbox/lightbox.js
new file mode 100644
index 0000000..bc23129
--- /dev/null
+++ b/node_modules/mdl-ext/es/lightbox/lightbox.js
@@ -0,0 +1,396 @@
+'use strict';
+
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
+var _isInteger = require('babel-runtime/core-js/number/is-integer');
+
+var _isInteger2 = _interopRequireDefault(_isInteger);
+
+var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
+
+var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
+
+var _entries = require('babel-runtime/core-js/object/entries');
+
+var _entries2 = _interopRequireDefault(_entries);
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _fullThrottle = require('../utils/full-throttle');
+
+var _fullThrottle2 = _interopRequireDefault(_fullThrottle);
+
+var _constants = require('../utils/constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * @license
+ * Copyright 2016 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+/**
+ * Responsive Lightbox
+ */
+
+(function () {
+  'use strict';
+
+  var LIGHTBOX = 'mdlext-lightbox';
+  var LIGHTBOX_SLIDER = 'mdlext-lightbox__slider';
+  var LIGHTBOX_SLIDER_SLIDE = 'mdlext-lightbox__slider__slide';
+  var STICKY_FOOTER = 'mdlext-lightbox--sticky-footer';
+  var BUTTON = 'mdl-button';
+
+  /**
+   * https://github.com/google/material-design-lite/issues/4205
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  var MaterialExtLightbox = function MaterialExtLightbox(element) {
+    // Stores the element.
+    this.element_ = element;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtLightbox'] = MaterialExtLightbox;
+
+  /**
+   * Handle keypress
+   * @param event
+   * @private
+   */
+  MaterialExtLightbox.prototype.keyDownHandler_ = function (event) {
+
+    if (event) {
+      if (event.keyCode === _constants.VK_ESC || event.keyCode === _constants.VK_SPACE || event.keyCode === _constants.VK_END || event.keyCode === _constants.VK_HOME || event.keyCode === _constants.VK_ARROW_UP || event.keyCode === _constants.VK_ARROW_LEFT || event.keyCode === _constants.VK_ARROW_DOWN || event.keyCode === _constants.VK_ARROW_RIGHT) {
+
+        if (event.keyCode !== _constants.VK_ESC) {
+          event.preventDefault();
+          event.stopPropagation();
+        }
+
+        var action = 'first';
+        if (event.keyCode === _constants.VK_END) {
+          action = 'last';
+        } else if (event.keyCode === _constants.VK_ARROW_UP || event.keyCode === _constants.VK_ARROW_LEFT) {
+          action = 'prev';
+        } else if (event.keyCode === _constants.VK_ARROW_DOWN || event.keyCode === _constants.VK_ARROW_RIGHT) {
+          action = 'next';
+        } else if (event.keyCode === _constants.VK_SPACE) {
+          action = 'select';
+        } else if (event.keyCode === _constants.VK_ESC) {
+          action = 'cancel';
+        }
+
+        dispatchAction_(action, this);
+      }
+    }
+  };
+
+  /**
+   * Handle button clicks
+   * @param event
+   * @private
+   */
+  MaterialExtLightbox.prototype.buttonClickHandler_ = function (event) {
+
+    if (event) {
+      event.preventDefault();
+      event.stopPropagation();
+
+      dispatchAction_(this.getAttribute('data-action') || '', this);
+
+      var n = this.closest('.' + LIGHTBOX);
+      if (n) {
+        n.focus();
+      }
+    }
+  };
+
+  /**
+   * Dispatches an action custom event
+   * @param action
+   * @param source
+   * @param target
+   * @private
+   */
+  var dispatchAction_ = function dispatchAction_(action, source) {
+    var target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : source;
+
+
+    target.dispatchEvent(new CustomEvent('action', {
+      bubbles: true,
+      cancelable: true,
+      detail: {
+        action: action || '',
+        source: source
+      }
+    }));
+  };
+
+  /**
+   * Reposition dialog if component parent element is "DIALOG"
+   * @param lightboxElement
+   * @private
+   */
+  var repositionDialog_ = function repositionDialog_(lightboxElement) {
+    var footerHeight = function footerHeight(footer, isSticky) {
+      return isSticky && footer ? footer.offsetHeight : 0;
+    };
+
+    var reposition = function reposition(dialog, fh) {
+      if (window.getComputedStyle(dialog).position === 'absolute') {
+        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+        var topValue = scrollTop + (window.innerHeight - dialog.offsetHeight - fh) / 2;
+        dialog.style.top = Math.max(scrollTop, topValue) + 'px';
+      }
+    };
+
+    var p = lightboxElement.parentNode;
+    var dialog = p && p.nodeName === 'DIALOG' ? p : null;
+
+    if (dialog && dialog.hasAttribute('open')) {
+      lightboxElement.style.width = 'auto';
+      lightboxElement.style.maxWidth = '100%';
+      var img = lightboxElement.querySelector('img');
+      if (img) {
+        lightboxElement.style.maxWidth = img.naturalWidth !== undefined ? img.naturalWidth + 'px' : img.width + 'px' || '100%';
+      }
+
+      var fh = footerHeight(lightboxElement.querySelector('footer'), lightboxElement.classList.contains(STICKY_FOOTER));
+      var vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - fh;
+      if (dialog.offsetHeight > vh) {
+        var n = 0;
+        while (dialog.offsetHeight > vh && ++n < 4) {
+          lightboxElement.style.width = lightboxElement.offsetWidth * vh / lightboxElement.offsetHeight + 'px';
+        }
+      }
+      reposition(dialog, fh);
+    }
+  };
+
+  /**
+   * Handle image load
+   * @param event
+   * @private
+   */
+
+  MaterialExtLightbox.prototype.imgLoadHandler_ = function () /*event*/{
+    repositionDialog_(this);
+  };
+
+  /**
+   * Handle image drag
+   * @param event
+   * @private
+     */
+  MaterialExtLightbox.prototype.imgDragHandler_ = function (event) {
+
+    var setStyles = function setStyles(element, properties) {
+      //noinspection JSAnnotator
+      var _iteratorNormalCompletion = true;
+      var _didIteratorError = false;
+      var _iteratorError = undefined;
+
+      try {
+        for (var _iterator = (0, _getIterator3.default)((0, _entries2.default)(properties)), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+          var _step$value = (0, _slicedToArray3.default)(_step.value, 2),
+              key = _step$value[0],
+              value = _step$value[1];
+
+          element.style[key] = value;
+        }
+        // ... or:
+        //for (const key in properties) {
+        //  element.style[key] = properties[key];
+        //}
+      } catch (err) {
+        _didIteratorError = true;
+        _iteratorError = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion && _iterator.return) {
+            _iterator.return();
+          }
+        } finally {
+          if (_didIteratorError) {
+            throw _iteratorError;
+          }
+        }
+      }
+    };
+
+    event.preventDefault();
+    //event.stopPropagation();
+
+    var x = event.clientX || (event.touches !== undefined ? event.touches[0].clientX : 0);
+
+    var img = this;
+    img.style.opacity = '0.2';
+
+    var slider = document.createElement('div');
+    slider.classList.add(LIGHTBOX_SLIDER);
+    setStyles(slider, { 'width': img.offsetWidth + 'px', 'height': img.offsetHeight + 'px' });
+
+    var slide = document.createElement('div');
+    slide.classList.add(LIGHTBOX_SLIDER_SLIDE);
+    slide.textContent = '>';
+    setStyles(slide, {
+      'width': img.offsetWidth + 'px',
+      'height': img.offsetHeight + 'px',
+      'line-height': img.offsetHeight + 'px',
+      'font-size': img.offsetHeight / 4 + 'px',
+      'text-align': 'right',
+      'background-image': 'url("' + (img.getAttribute('data-img-url-prev') || '') + '")'
+    });
+    slider.appendChild(slide);
+
+    slide = document.createElement('div');
+    slide.classList.add(LIGHTBOX_SLIDER_SLIDE);
+    setStyles(slide, {
+      'width': img.offsetWidth + 'px',
+      'height': img.offsetHeight + 'px',
+      'background-image': 'url("' + img.src + '")'
+    });
+    slider.appendChild(slide);
+
+    slide = document.createElement('div');
+    slide.classList.add(LIGHTBOX_SLIDER_SLIDE);
+    slide.textContent = '<';
+    setStyles(slide, {
+      'width': img.offsetWidth + 'px',
+      'height': img.offsetHeight + 'px',
+      'line-height': img.offsetHeight + 'px',
+      'font-size': img.offsetHeight / 4 + 'px',
+      'text-align': 'left',
+      'background-image': 'url("' + (img.getAttribute('data-img-url-next') || '') + '")'
+    });
+    slider.appendChild(slide);
+
+    img.parentNode.appendChild(slider);
+
+    // drag handler
+    var drag = function drag(e) {
+      e.preventDefault();
+      var dx = (e.clientX || (e.touches !== undefined ? e.touches[0].clientX : 0)) - x; // TODO: maybe rewrite to improve performance
+
+      if (slider.offsetWidth - Math.abs(dx) > 19) {
+        slider.style.left = dx + 'px';
+      }
+    };
+
+    // end drag handler
+    var endDrag = function endDrag(e) {
+      e.preventDefault();
+      //e.stopPropagation();
+
+      window.removeEventListener('mousemove', drag);
+      window.removeEventListener('touchmove', drag);
+      window.removeEventListener('mouseup', endDrag);
+      window.removeEventListener('touchend', endDrag);
+
+      var dx = slider.offsetLeft;
+      img.parentNode.removeChild(slider);
+      img.style.opacity = '1.0';
+
+      if (Math.abs(dx) > 19) {
+        dispatchAction_(dx > 0 ? 'prev' : 'next', img);
+      }
+    };
+
+    window.addEventListener('mousemove', drag);
+    window.addEventListener('touchmove', drag);
+    window.addEventListener('mouseup', endDrag);
+    window.addEventListener('touchend', endDrag);
+  };
+
+  /**
+   * Initialize component
+   */
+  MaterialExtLightbox.prototype.init = function () {
+    var _this = this;
+
+    if (this.element_) {
+      // Do the init required for this component to work
+      this.element_.addEventListener('keydown', this.keyDownHandler_.bind(this.element_), true);
+
+      if (!(0, _isInteger2.default)(this.element_.getAttribute('tabindex'))) {
+        this.element_.setAttribute('tabindex', 1);
+      }
+
+      [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + BUTTON))).forEach(function (button) {
+        return button.addEventListener('click', _this.buttonClickHandler_.bind(button), false);
+      });
+
+      var figcaption = this.element_.querySelector('figcaption');
+      if (figcaption) {
+        figcaption.addEventListener('click', this.buttonClickHandler_.bind(figcaption), false);
+      }
+
+      var footer = this.element_.querySelector('footer');
+      if (footer) {
+        footer.addEventListener('click', this.buttonClickHandler_.bind(footer), false);
+      }
+
+      var img = this.element_.querySelector('img');
+      if (img) {
+        img.addEventListener('load', this.imgLoadHandler_.bind(this.element_), false);
+        img.addEventListener('click', function (e) {
+          return e.preventDefault();
+        }, true);
+        img.addEventListener('mousedown', this.imgDragHandler_.bind(img), true);
+        img.addEventListener('touchstart', this.imgDragHandler_.bind(img), true);
+      }
+      window.addEventListener('resize', (0, _fullThrottle2.default)(function () {
+        return repositionDialog_(_this.element_);
+      }));
+      window.addEventListener('orientationchange', function () {
+        return repositionDialog_(_this.element_);
+      });
+
+      // Set upgraded flag
+      this.element_.classList.add(_constants.IS_UPGRADED);
+    }
+  };
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   *
+   * Nothing to downgrade
+   *
+  MaterialExtLightbox.prototype.mdlDowngrade_ = function() {
+  };
+  */
+
+  /**
+   * The component registers itself. It can assume componentHandler is available in the global scope.
+   */
+  /* jshint undef:false */
+  componentHandler.register({
+    constructor: MaterialExtLightbox,
+    classAsString: 'MaterialExtLightbox',
+    cssClass: 'mdlext-js-lightbox'
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/menu-button/menu-button.js b/node_modules/mdl-ext/es/menu-button/menu-button.js
new file mode 100644
index 0000000..9f7ed17
--- /dev/null
+++ b/node_modules/mdl-ext/es/menu-button/menu-button.js
@@ -0,0 +1,790 @@
+'use strict';
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
+var _stringUtils = require('../utils/string-utils');
+
+var _fullThrottle = require('../utils/full-throttle');
+
+var _fullThrottle2 = _interopRequireDefault(_fullThrottle);
+
+var _constants = require('../utils/constants');
+
+var _domUtils = require('../utils/dom-utils');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * @license
+ * Copyright 2016 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+/**
+ * A menu button is a button that opens a menu. It is often styled as a
+ * typical push button with a downward pointing arrow or triangle to hint
+ * that activating the button will display a menu.
+ */
+var JS_MENU_BUTTON = 'mdlext-js-menu-button';
+var MENU_BUTTON_MENU = 'mdlext-menu';
+var MENU_BUTTON_MENU_ITEM = 'mdlext-menu__item';
+var MENU_BUTTON_MENU_ITEM_SEPARATOR = 'mdlext-menu__item-separator';
+//const MDL_LAYOUT_CONTENT = 'mdl-layout__content';
+
+/**
+ * Creates the menu controlled by the menu button
+ * @param element
+ * @return {{element: Element, selected: Element, open: (function(*=)), removeListeners: (function()), downgrade: (function())}}
+ */
+
+var menuFactory = function menuFactory(element) {
+
+  var ariaControls = null;
+  var parentNode = null;
+
+  var removeAllSelected = function removeAllSelected() {
+    [].concat((0, _toConsumableArray3.default)(element.querySelectorAll('.' + MENU_BUTTON_MENU_ITEM + '[aria-selected="true"]'))).forEach(function (selectedItem) {
+      return selectedItem.removeAttribute('aria-selected');
+    });
+  };
+
+  var setSelected = function setSelected(item) {
+    var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+
+    if (force || item && !item.hasAttribute('aria-selected')) {
+      removeAllSelected();
+      if (item) {
+        item.setAttribute('aria-selected', 'true');
+      }
+    }
+  };
+
+  var getSelected = function getSelected() {
+    return element.querySelector('.' + MENU_BUTTON_MENU_ITEM + '[aria-selected="true"]');
+  };
+
+  var isDisabled = function isDisabled(item) {
+    return item && item.hasAttribute('disabled');
+  };
+
+  var isSeparator = function isSeparator(item) {
+    return item && item.classList.contains(MENU_BUTTON_MENU_ITEM_SEPARATOR);
+  };
+
+  var focus = function focus(item) {
+    if (item) {
+      item = item.closest('.' + MENU_BUTTON_MENU_ITEM);
+    }
+    if (item) {
+      item.focus();
+    }
+  };
+
+  var nextItem = function nextItem(current) {
+    var n = current.nextElementSibling;
+    if (!n) {
+      n = element.firstElementChild;
+    }
+    if (!isDisabled(n) && !isSeparator(n)) {
+      focus(n);
+    } else {
+      var i = element.children.length;
+      while (n && i-- > 0) {
+        if (isDisabled(n) || isSeparator(n)) {
+          n = n.nextElementSibling;
+          if (!n) {
+            n = element.firstElementChild;
+          }
+        } else {
+          focus(n);
+          break;
+        }
+      }
+    }
+  };
+
+  var previousItem = function previousItem(current) {
+    var p = current.previousElementSibling;
+    if (!p) {
+      p = element.lastElementChild;
+    }
+    if (!isDisabled(p) && !isSeparator(p)) {
+      focus(p);
+    } else {
+      var i = element.children.length;
+      while (p && i-- > 0) {
+        if (isDisabled(p) || isSeparator(p)) {
+          p = p.previousElementSibling;
+          if (!p) {
+            p = element.lastElementChild;
+          }
+        } else {
+          focus(p);
+          break;
+        }
+      }
+    }
+  };
+
+  var firstItem = function firstItem() {
+    var item = element.firstElementChild;
+    if (isDisabled(item) || isSeparator(item)) {
+      nextItem(item);
+    } else {
+      focus(item);
+    }
+  };
+
+  var lastItem = function lastItem() {
+    var item = element.lastElementChild;
+    if (isDisabled(item) || isSeparator(item)) {
+      previousItem(item);
+    } else {
+      focus(item);
+    }
+  };
+
+  var selectItem = function selectItem(item) {
+    if (item && !isDisabled(item) && !isSeparator(item)) {
+      setSelected(item);
+      close(true, item);
+    }
+  };
+
+  var keyDownHandler = function keyDownHandler(event) {
+
+    var item = event.target.closest('.' + MENU_BUTTON_MENU_ITEM);
+
+    switch (event.keyCode) {
+      case _constants.VK_ARROW_UP:
+      case _constants.VK_ARROW_LEFT:
+        if (item) {
+          previousItem(item);
+        } else {
+          firstItem();
+        }
+        break;
+
+      case _constants.VK_ARROW_DOWN:
+      case _constants.VK_ARROW_RIGHT:
+        if (item) {
+          nextItem(item);
+        } else {
+          lastItem();
+        }
+        break;
+
+      case _constants.VK_HOME:
+        firstItem();
+        break;
+
+      case _constants.VK_END:
+        lastItem();
+        break;
+
+      case _constants.VK_SPACE:
+      case _constants.VK_ENTER:
+        selectItem(item);
+        break;
+
+      case _constants.VK_ESC:
+        close(true);
+        break;
+
+      case _constants.VK_TAB:
+        // We do not have a "natural" tab order from menu, so the best we can do is to set focus back to the button
+        close(true);
+        break;
+
+      default:
+        return;
+    }
+    event.preventDefault();
+  };
+
+  var blurHandler = function blurHandler(event) {
+
+    // See: https://github.com/facebook/react/issues/2011
+    var t = event.relatedTarget || event.explicitOriginalTarget || // FF
+    document.activeElement; // IE11
+
+    //console.log('***** blur, target, relatedTarget', event.target, t);
+
+    try {
+      if (t) {
+        if (t.closest('.' + MENU_BUTTON_MENU) !== element && shouldClose(t)) {
+          close();
+        }
+      } else {
+        close();
+      }
+    } catch (err) {
+      // FF throws error: "TypeError: n.closest is not a function" if related target is a text node
+      close();
+    }
+  };
+
+  var clickHandler = function clickHandler(event) {
+    //console.log('***** click, target', event.target);
+
+    event.preventDefault();
+    var t = event.target;
+    if (t && t.closest('.' + MENU_BUTTON_MENU) === element) {
+      var item = t.closest('.' + MENU_BUTTON_MENU_ITEM);
+      if (item) {
+        selectItem(item);
+      }
+    } else {
+      if (shouldClose(t)) {
+        close();
+      }
+    }
+  };
+
+  var touchStartHandler = function touchStartHandler(event) {
+    //console.log('***** touchStart, target', event.target);
+
+    var t = event.target;
+    if (!(t && t.closest('.' + MENU_BUTTON_MENU) === element)) {
+      if (event.type === 'touchstart') {
+        event.preventDefault();
+      }
+      close();
+    }
+  };
+
+  var addListeners = function addListeners() {
+    element.addEventListener('keydown', keyDownHandler, false);
+    element.addEventListener('blur', blurHandler, true);
+    element.addEventListener('click', clickHandler, true);
+    document.documentElement.addEventListener('touchstart', touchStartHandler, true);
+  };
+
+  var _removeListeners = function _removeListeners() {
+    element.removeEventListener('keydown', keyDownHandler, false);
+    element.removeEventListener('blur', blurHandler, true);
+    element.removeEventListener('click', clickHandler, true);
+    document.documentElement.removeEventListener('touchstart', touchStartHandler, true);
+  };
+
+  var _open = function _open(controlElement) {
+    var position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'first';
+
+
+    ariaControls = controlElement.closest('.' + JS_MENU_BUTTON);
+
+    element.style['min-width'] = Math.max(124, controlElement.getBoundingClientRect().width) + 'px';
+    element.removeAttribute('hidden');
+    (0, _domUtils.tether)(controlElement, element);
+
+    var item = void 0;
+    switch (position.toLowerCase()) {
+      case 'first':
+        firstItem();
+        break;
+
+      case 'last':
+        lastItem();
+        break;
+
+      case 'selected':
+        item = getSelected();
+        if (item && !item.hasAttribute('disabled')) {
+          focus(item);
+        } else {
+          firstItem();
+        }
+        break;
+    }
+
+    addListeners();
+  };
+
+  var shouldClose = function shouldClose(target) {
+    //console.log('***** shouldClose');
+
+    var result = false;
+    var btn = target && target.closest('.' + JS_MENU_BUTTON) || null;
+    if (!btn) {
+      result = true;
+    } else if (btn.getAttribute('aria-controls') === element.id) {
+      if (btn !== ariaControls) {
+        result = true;
+      }
+    } else {
+      result = true;
+    }
+    return result;
+  };
+
+  var close = function close() {
+    var forceFocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
+    var item = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+
+    _removeListeners();
+
+    element.dispatchEvent(new CustomEvent('_closemenu', {
+      bubbles: true,
+      cancelable: true,
+      detail: { forceFocus: forceFocus, item: item }
+    }));
+  };
+
+  var addWaiAria = function addWaiAria() {
+    if (!element.hasAttribute('id')) {
+      // Generate a random id
+      element.id = 'menu-button-' + (0, _stringUtils.randomString)();
+    }
+    element.setAttribute('tabindex', '-1');
+    element.setAttribute('role', 'menu');
+    element.setAttribute('hidden', '');
+
+    [].concat((0, _toConsumableArray3.default)(element.querySelectorAll('.' + MENU_BUTTON_MENU_ITEM))).forEach(function (menuitem) {
+      menuitem.setAttribute('tabindex', '-1');
+      menuitem.setAttribute('role', 'menuitem');
+    });
+
+    [].concat((0, _toConsumableArray3.default)(element.querySelectorAll('.' + MENU_BUTTON_MENU_ITEM_SEPARATOR))).forEach(function (menuitem) {
+      menuitem.setAttribute('role', 'separator');
+    });
+  };
+
+  var init = function init() {
+    addWaiAria();
+    parentNode = element.parentNode;
+    element.classList.add('is-upgraded');
+  };
+
+  var _downgrade = function _downgrade() {
+    _removeListeners();
+    if (element.parentNode !== parentNode) {
+      parentNode.appendChild(element);
+    }
+    element.classList.remove('is-upgraded');
+  };
+
+  init();
+
+  return {
+    /**
+     * Get the menu element
+     * @returns {Element} the menu element
+     */
+    get element() {
+      return element;
+    },
+
+    /**
+     * Set selected menu item
+     * @param item
+     */
+    set selected(item) {
+      setSelected(item, true);
+    },
+
+    /**
+     * Open menu
+     * @param {Element} controlElement the element where the menu should be aligned to
+     * @param {String} position menuElement item to receive focus after menu element is opened
+     */
+    open: function open(controlElement) {
+      var position = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'first';
+      return _open(controlElement, position);
+    },
+
+    /**
+     * Remove event listeners.
+     */
+    removeListeners: function removeListeners() {
+      return _removeListeners();
+    },
+
+    /**
+     * Downgrade menu
+     */
+    downgrade: function downgrade() {
+      return _downgrade();
+    }
+  };
+};
+
+/**
+ * The menubutton component
+ */
+
+var MenuButton = function () {
+  function MenuButton(element) {
+    var _this = this;
+
+    (0, _classCallCheck3.default)(this, MenuButton);
+
+    this.keyDownHandler = function (event) {
+      if (!_this.isDisabled()) {
+        switch (event.keyCode) {
+          case _constants.VK_ARROW_UP:
+            _this.openMenu('last');
+            break;
+
+          case _constants.VK_ARROW_DOWN:
+            _this.openMenu();
+            break;
+
+          case _constants.VK_SPACE:
+          case _constants.VK_ENTER:
+            _this.openMenu('selected');
+            break;
+
+          case _constants.VK_ESC:
+            _this.closeMenu();
+            break;
+
+          case _constants.VK_TAB:
+            _this.closeMenu();
+            return;
+
+          default:
+            return;
+        }
+      }
+      //event.stopPropagation();
+      event.preventDefault();
+    };
+
+    this.clickHandler = function () {
+      if (!_this.isDisabled()) {
+        if (_this.element.getAttribute('aria-expanded').toLowerCase() === 'true') {
+          _this.closeMenu(true);
+        } else {
+          _this.openMenu('selected');
+        }
+      }
+    };
+
+    this.recalcMenuPosition = (0, _fullThrottle2.default)(function () {
+      var c = _this.focusElement.getBoundingClientRect();
+      var dx = _this.focusElementLastScrollPosition.left - c.left;
+      var dy = _this.focusElementLastScrollPosition.top - c.top;
+      var left = (parseFloat(_this.menu.element.style.left) || 0) - dx;
+      var top = (parseFloat(_this.menu.element.style.top) || 0) - dy;
+
+      _this.menu.element.style.left = left + 'px';
+      _this.menu.element.style.top = top + 'px';
+      _this.focusElementLastScrollPosition = c;
+    });
+
+    this.positionChangeHandler = function () {
+      _this.recalcMenuPosition(_this);
+    };
+
+    this.closeMenuHandler = function (event) {
+      if (event && event.detail) {
+        if (event.detail.item && event.detail.item !== _this.selectedItem) {
+          _this.selectedItem = event.detail.item;
+          _this.dispatchMenuSelect();
+        }
+        _this.closeMenu(event.detail.forceFocus);
+      }
+    };
+
+    this.element = element;
+    this.focusElement = undefined;
+    this.focusElementLastScrollPosition = undefined;
+    this.scrollElements = [];
+    this.menu = undefined;
+    this.selectedItem = null;
+    this.init();
+  }
+
+  /**
+   * Re-position menu if content is scrolled, window is resized or orientation change
+   * @see https://javascriptweblog.wordpress.com/2015/11/02/of-classes-and-arrow-functions-a-cautionary-tale/
+   */
+
+
+  (0, _createClass3.default)(MenuButton, [{
+    key: 'dispatchMenuSelect',
+    value: function dispatchMenuSelect() {
+      this.element.dispatchEvent(new CustomEvent('menuselect', {
+        bubbles: true,
+        cancelable: true,
+        detail: { source: this.selectedItem }
+      }));
+    }
+  }, {
+    key: 'isDisabled',
+    value: function isDisabled() {
+      return this.element.hasAttribute('disabled');
+    }
+  }, {
+    key: 'removeListeners',
+    value: function removeListeners() {
+      this.element.removeEventListener('keydown', this.keyDownHandler);
+      this.element.removeEventListener('click', this.clickHandler);
+    }
+  }, {
+    key: 'openMenu',
+    value: function openMenu() {
+      var _this2 = this;
+
+      var position = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'first';
+
+
+      if (!this.isDisabled() && this.menu) {
+
+        // Close the menu if button position change
+        this.scrollElements = (0, _domUtils.getScrollParents)(this.element);
+        this.scrollElements.forEach(function (el) {
+          return el.addEventListener('scroll', _this2.positionChangeHandler);
+        });
+
+        window.addEventListener('resize', this.positionChangeHandler);
+        window.addEventListener('orientationchange', this.positionChangeHandler);
+        this.menu.element.addEventListener('_closemenu', this.closeMenuHandler);
+
+        this.menu.selected = this.selectedItem;
+        this.menu.open(this.focusElement, position);
+        this.element.setAttribute('aria-expanded', 'true');
+
+        this.focusElementLastScrollPosition = this.focusElement.getBoundingClientRect();
+      }
+    }
+  }, {
+    key: 'closeMenu',
+    value: function closeMenu() {
+      var _this3 = this;
+
+      var forceFocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
+
+      if (this.menu) {
+        this.menu.removeListeners();
+        this.scrollElements.forEach(function (el) {
+          return el.removeEventListener('scroll', _this3.positionChangeHandler);
+        });
+        window.removeEventListener('resize', this.positionChangeHandler);
+        window.removeEventListener('orientationchange', this.positionChangeHandler);
+        this.menu.element.removeEventListener('_closemenu', this.closeMenuHandler);
+
+        if (forceFocus) {
+          this.focus();
+        }
+        this.element.setAttribute('aria-expanded', 'false');
+        this.menu.element.setAttribute('hidden', '');
+      }
+    }
+  }, {
+    key: 'focus',
+    value: function focus() {
+      if (!this.isDisabled()) {
+        this.focusElement.focus();
+      }
+    }
+  }, {
+    key: 'init',
+    value: function init() {
+      var _this4 = this;
+
+      var addListeners = function addListeners() {
+        _this4.element.addEventListener('keydown', _this4.keyDownHandler);
+        _this4.element.addEventListener('click', _this4.clickHandler);
+      };
+
+      var addWaiAria = function addWaiAria() {
+        _this4.element.setAttribute('role', 'button');
+        _this4.element.setAttribute('aria-expanded', 'false');
+        _this4.element.setAttribute('aria-haspopup', 'true');
+      };
+
+      var addFocusElement = function addFocusElement() {
+        _this4.focusElement = _this4.element.querySelector('input[type="text"]');
+        if (!_this4.focusElement) {
+          _this4.focusElement = _this4.element;
+
+          if (!(_this4.focusElement.tagName.toLowerCase() === 'button' || _this4.focusElement.tagName.toLowerCase() === 'input')) {
+            if (!_this4.focusElement.hasAttribute('tabindex')) {
+              _this4.focusElement.setAttribute('tabindex', '0');
+            }
+          }
+        }
+      };
+
+      var moveElementToDocumentBody = function moveElementToDocumentBody(element) {
+        // To position an element on top of all other z-indexed elements, the element should be moved to document.body
+        //       See: https://philipwalton.com/articles/what-no-one-told-you-about-z-index/
+
+        if (element.parentNode !== document.body) {
+          return document.body.appendChild(element);
+        }
+        return element;
+      };
+
+      var findMenuElement = function findMenuElement() {
+        var menuElement = void 0;
+        var menuElementId = _this4.element.getAttribute('aria-controls');
+        if (menuElementId !== null) {
+          menuElement = document.querySelector('#' + menuElementId);
+        } else {
+          menuElement = _this4.element.parentNode.querySelector('.' + MENU_BUTTON_MENU);
+        }
+        return menuElement;
+      };
+
+      var addMenu = function addMenu() {
+        var menuElement = findMenuElement();
+        if (menuElement) {
+          if (menuElement.componentInstance) {
+            _this4.menu = menuElement.componentInstance;
+          } else {
+            _this4.menu = menuFactory(menuElement);
+            menuElement.componentInstance = _this4.menu;
+            moveElementToDocumentBody(menuElement);
+          }
+          _this4.element.setAttribute('aria-controls', _this4.menu.element.id);
+        }
+      };
+
+      addFocusElement();
+      addWaiAria();
+      addMenu();
+      this.removeListeners();
+      addListeners();
+    }
+  }, {
+    key: 'downgrade',
+    value: function downgrade() {
+      var _this5 = this;
+
+      if (this.menu) {
+        // Do not downgrade menu if there are other buttons sharing this menu
+        var related = [].concat((0, _toConsumableArray3.default)(document.querySelectorAll('.' + JS_MENU_BUTTON + '[aria-controls="' + this.element.getAttribute('aria-controls') + '"]')));
+        if (related.filter(function (c) {
+          return c !== _this5.element && c.getAttribute('data-upgraded').indexOf('MaterialExtMenuButton') >= 0;
+        }).length === 0) {
+          this.menu.downgrade();
+        }
+      }
+      this.removeListeners();
+    }
+  }]);
+  return MenuButton;
+}();
+
+(function () {
+  'use strict';
+
+  /**
+   * https://github.com/google/material-design-lite/issues/4205
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+
+  var MaterialExtMenuButton = function MaterialExtMenuButton(element) {
+    this.element_ = element;
+    this.menuButton_ = null;
+
+    // Initialize instance.
+    this.init();
+  };
+  window['MaterialExtMenuButton'] = MaterialExtMenuButton;
+
+  // Public methods.
+
+  /**
+   * Get the menu element controlled by this button, null if no menu is controlled by this button
+   * @public
+   */
+  MaterialExtMenuButton.prototype.getMenuElement = function () {
+    return this.menuButton_.menu ? this.menuButton_.menu.element : null;
+  };
+  MaterialExtMenuButton.prototype['getMenuElement'] = MaterialExtMenuButton.prototype.getMenuElement;
+
+  /**
+   * Open menu
+   * @public
+   * @param {String} position one of "first", "last" or "selected"
+   */
+  MaterialExtMenuButton.prototype.openMenu = function (position) {
+    this.menuButton_.openMenu(position);
+  };
+  MaterialExtMenuButton.prototype['openMenu'] = MaterialExtMenuButton.prototype.openMenu;
+
+  /**
+   * Close menu
+   * @public
+   */
+  MaterialExtMenuButton.prototype.closeMenu = function () {
+    this.menuButton_.closeMenu(true);
+  };
+  MaterialExtMenuButton.prototype['closeMenu'] = MaterialExtMenuButton.prototype.closeMenu;
+
+  /**
+   * Get selected menu item
+   * @public
+   * @returns {Element} The selected menu item or null if no item selected
+   */
+  MaterialExtMenuButton.prototype.getSelectedMenuItem = function () {
+    return this.menuButton_.selectedItem;
+  };
+  MaterialExtMenuButton.prototype['getSelectedMenuItem'] = MaterialExtMenuButton.prototype.getSelectedMenuItem;
+
+  /**
+   * Set (default) selected menu item
+   * @param {Element} item
+   */
+  MaterialExtMenuButton.prototype.setSelectedMenuItem = function (item) {
+    this.menuButton_.selectedItem = item;
+  };
+  MaterialExtMenuButton.prototype['setSelectedMenuItem'] = MaterialExtMenuButton.prototype.setSelectedMenuItem;
+
+  /**
+   * Initialize component
+   */
+  MaterialExtMenuButton.prototype.init = function () {
+    if (this.element_) {
+      this.menuButton_ = new MenuButton(this.element_);
+      this.element_.addEventListener('mdl-componentdowngraded', this.mdlDowngrade_.bind(this));
+      this.element_.classList.add(_constants.IS_UPGRADED);
+    }
+  };
+
+  /**
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   */
+  MaterialExtMenuButton.prototype.mdlDowngrade_ = function () {
+    this.menuButton_.downgrade();
+  };
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtMenuButton,
+    classAsString: 'MaterialExtMenuButton',
+    cssClass: JS_MENU_BUTTON,
+    widget: true
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/selectfield/selectfield.js b/node_modules/mdl-ext/es/selectfield/selectfield.js
new file mode 100644
index 0000000..5274789
--- /dev/null
+++ b/node_modules/mdl-ext/es/selectfield/selectfield.js
@@ -0,0 +1,271 @@
+'use strict';
+
+var _stringUtils = require('../utils/string-utils');
+
+var _constants = require('../utils/constants');
+
+/**
+ * @license
+ * Copyright 2016 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code is built with Google Material Design Lite,
+ * which is Licensed under the Apache License, Version 2.0
+ */
+
+/*
+ * Copied/Modified from https://github.com/google/material-design-lite/tree/master/src/textfield
+ */
+
+(function () {
+  'use strict';
+
+  var LABEL = 'mdlext-selectfield__label';
+  var INPUT = 'mdlext-selectfield__select';
+
+  /**
+   * Class constructor for Selectfield MDLEXT component.
+   * Implements MDL component design pattern defined at:
+   * https://github.com/jasonmayes/mdl-component-design-pattern
+   *
+   * @constructor
+   * @param {HTMLElement} element The element that will be upgraded.
+   */
+  var MaterialExtSelectfield = function MaterialExtSelectfield(element) {
+    this.element_ = element;
+    this.init(); // Initialize instance.
+  };
+
+  window['MaterialExtSelectfield'] = MaterialExtSelectfield;
+
+  /**
+   * Handle focus.
+   *
+   * @param {Event} event The event that fired.
+   * @private
+   */
+  /*eslint no-unused-vars: 0*/
+  MaterialExtSelectfield.prototype.onFocus_ = function () /*event*/{
+    this.element_.classList.add(_constants.IS_FOCUSED);
+  };
+
+  /**
+   * Handle lost focus.
+   *
+   * @param {Event} event The event that fired.
+   * @private
+   */
+  /*eslint no-unused-vars: 0*/
+  MaterialExtSelectfield.prototype.onBlur_ = function () /*event*/{
+    this.element_.classList.remove(_constants.IS_FOCUSED);
+  };
+
+  /**
+   * Handle reset event from out side.
+   *
+   * @param {Event} event The event that fired.
+   * @private
+   */
+  MaterialExtSelectfield.prototype.onReset_ = function () /*event*/{
+    this.updateClasses_();
+  };
+
+  /**
+   * Handle class updates.
+   *
+   * @private
+   */
+  MaterialExtSelectfield.prototype.updateClasses_ = function () {
+    this.checkDisabled();
+    this.checkValidity();
+    this.checkDirty();
+    this.checkFocus();
+  };
+
+  // Public methods.
+
+  /**
+   * Check the disabled state and update field accordingly.
+   *
+   * @public
+   */
+  MaterialExtSelectfield.prototype.checkDisabled = function () {
+    if (this.select_.disabled) {
+      this.element_.classList.add(_constants.IS_DISABLED);
+    } else {
+      this.element_.classList.remove(_constants.IS_DISABLED);
+    }
+  };
+  MaterialExtSelectfield.prototype['checkDisabled'] = MaterialExtSelectfield.prototype.checkDisabled;
+
+  /**
+   * Check the focus state and update field accordingly.
+   *
+   * @public
+   */
+  MaterialExtSelectfield.prototype.checkFocus = function () {
+    // Note: element.querySelector(':focus') always return null in JsDom, even if select element has focus
+    /*eslint no-extra-boolean-cast: 0*/
+    if (Boolean(this.element_.querySelector(':focus'))) {
+      this.element_.classList.add(_constants.IS_FOCUSED);
+    } else {
+      this.element_.classList.remove(_constants.IS_FOCUSED);
+    }
+  };
+
+  MaterialExtSelectfield.prototype['checkFocus'] = MaterialExtSelectfield.prototype.checkFocus;
+
+  /**
+   * Check the validity state and update field accordingly.
+   *
+   * @public
+   */
+  MaterialExtSelectfield.prototype.checkValidity = function () {
+
+    /* Don't think it makes any sense to check validity.
+       Tests I've done, so far, indicates that setting an illegal value via JS returns selectedIndex=0
+     if (this.select_.validity) {
+      if (this.select_.validity.valid) {
+        this.element_.classList.remove(this.CssClasses_.IS_INVALID);
+      } else {
+        this.element_.classList.add(this.CssClasses_.IS_INVALID);
+      }
+    }
+    */
+  };
+
+  MaterialExtSelectfield.prototype['checkValidity'] = MaterialExtSelectfield.prototype.checkValidity;
+
+  /**
+   * Check the dirty state and update field accordingly.
+   *
+   * @public
+   */
+  MaterialExtSelectfield.prototype.checkDirty = function () {
+    if (this.select_.value && this.select_.value.length > 0) {
+      this.element_.classList.add(_constants.IS_DIRTY);
+    } else {
+      this.element_.classList.remove(_constants.IS_DIRTY);
+    }
+  };
+
+  MaterialExtSelectfield.prototype['checkDirty'] = MaterialExtSelectfield.prototype.checkDirty;
+
+  /**
+   * Disable select field.
+   *
+   * @public
+   */
+  MaterialExtSelectfield.prototype.disable = function () {
+    this.select_.disabled = true;
+    this.updateClasses_();
+  };
+
+  MaterialExtSelectfield.prototype['disable'] = MaterialExtSelectfield.prototype.disable;
+
+  /**
+   * Enable select field.
+   *
+   * @public
+   */
+  MaterialExtSelectfield.prototype.enable = function () {
+    this.select_.disabled = false;
+    this.updateClasses_();
+  };
+
+  MaterialExtSelectfield.prototype['enable'] = MaterialExtSelectfield.prototype.enable;
+
+  /**
+   * Update select field value.
+   *
+   * @param {string} value The value to which to set the control (optional).
+   * @public
+   */
+  MaterialExtSelectfield.prototype.change = function (value) {
+    this.select_.value = value || '';
+    this.updateClasses_();
+  };
+  MaterialExtSelectfield.prototype['change'] = MaterialExtSelectfield.prototype.change;
+
+  /**
+   * Initialize element.
+   */
+  MaterialExtSelectfield.prototype.init = function () {
+    if (this.element_) {
+      this.label_ = this.element_.querySelector('.' + LABEL);
+      this.select_ = this.element_.querySelector('.' + INPUT);
+
+      if (this.select_) {
+        // Remove listeners, just in case ...
+        this.select_.removeEventListener('change', this.updateClasses_);
+        this.select_.removeEventListener('focus', this.onFocus_);
+        this.select_.removeEventListener('blur', this.onBlur_);
+        this.select_.removeEventListener('reset', this.onReset_);
+
+        this.select_.addEventListener('change', this.updateClasses_.bind(this));
+        this.select_.addEventListener('focus', this.onFocus_.bind(this));
+        this.select_.addEventListener('blur', this.onBlur_.bind(this));
+        this.select_.addEventListener('reset', this.onReset_.bind(this));
+
+        if (this.label_) {
+          var id = void 0;
+          if (!this.select_.hasAttribute('id')) {
+            id = 'select-' + (0, _stringUtils.randomString)();
+            this.select_.id = id;
+          } else {
+            id = this.select_.id;
+          }
+
+          if (!this.label_.hasAttribute('for')) {
+            this.label_.setAttribute('for', id);
+          }
+        }
+
+        var invalid = this.element_.classList.contains(_constants.IS_INVALID);
+        this.updateClasses_();
+        this.element_.classList.add(_constants.IS_UPGRADED);
+
+        if (invalid) {
+          this.element_.classList.add(_constants.IS_INVALID);
+        }
+        if (this.select_.hasAttribute('autofocus')) {
+          this.element_.focus();
+          this.checkFocus();
+        }
+      }
+    }
+  };
+
+  /**
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   *
+   * Nothing to downgrade
+   *
+  MaterialExtSelectfield.prototype.mdlDowngrade_ = function() {
+    'use strict';
+    console.log('***** MaterialExtSelectfield.mdlDowngrade ');
+  };
+  */
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /*eslint no-undef: 0*/
+  componentHandler.register({
+    constructor: MaterialExtSelectfield,
+    classAsString: 'MaterialExtSelectfield',
+    cssClass: 'mdlext-js-selectfield',
+    widget: true
+  });
+})();
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/sticky-header/sticky-header.js b/node_modules/mdl-ext/es/sticky-header/sticky-header.js
new file mode 100644
index 0000000..08593aa
--- /dev/null
+++ b/node_modules/mdl-ext/es/sticky-header/sticky-header.js
@@ -0,0 +1,252 @@
+'use strict';
+
+var _fullThrottle = require('../utils/full-throttle');
+
+var _fullThrottle2 = _interopRequireDefault(_fullThrottle);
+
+var _jsonUtils = require('../utils/json-utils');
+
+var _constants = require('../utils/constants');
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+(function () {
+  'use strict';
+
+  var MDL_LAYOUT_CONTENT = 'mdl-layout__content';
+  var IS_SCROLL_CLASS = 'mdlext-is-scroll';
+
+  /**
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  var MaterialExtStickyHeader = function MaterialExtStickyHeader(element) {
+    // Stores the element.
+    this.header_ = element;
+
+    // Heder listens to scroll events from content
+    this.content_ = null;
+    this.lastScrollTop_ = 0;
+
+    // Default config
+    this.config_ = {
+      visibleAtScrollEnd: false
+    };
+
+    this.mutationObserver_ = null;
+
+    this.drawing_ = false;
+
+    // Initialize instance.
+    this.init();
+  };
+
+  window['MaterialExtStickyHeader'] = MaterialExtStickyHeader;
+
+  /**
+   * Update header width
+   * @private
+   */
+  MaterialExtStickyHeader.prototype.recalcWidth_ = function () {
+    this.header_.style.width = this.content_.clientWidth + 'px';
+  };
+
+  var throttleResize = (0, _fullThrottle2.default)(function (self) {
+    return self.recalcWidth_();
+  });
+
+  /**
+   * Adjust header width when window resizes or oreientation changes
+   * @param event
+   * @private
+   */
+  MaterialExtStickyHeader.prototype.resizeHandler_ = function () /* event */{
+    throttleResize(this);
+  };
+
+  /**
+   * Update header position
+   * @private
+   */
+  MaterialExtStickyHeader.prototype.reposition_ = function () {
+
+    var currentContentScrollTop = this.content_.scrollTop;
+    var scrollDiff = this.lastScrollTop_ - currentContentScrollTop;
+
+    if (currentContentScrollTop <= 0) {
+      // Scrolled to the top. Header sticks to the top
+      this.header_.style.top = '0';
+      this.header_.classList.remove(IS_SCROLL_CLASS);
+    } else if (scrollDiff > 0) {
+
+      if (scrollDiff >= this.header_.offsetHeight) {
+
+        // Scrolled up. Header slides in
+        var headerTop = parseInt(window.getComputedStyle(this.header_).getPropertyValue('top')) || 0;
+        if (headerTop != 0) {
+          this.header_.style.top = '0';
+          this.header_.classList.add(IS_SCROLL_CLASS);
+        }
+        this.lastScrollTop_ = currentContentScrollTop;
+      }
+      return;
+    } else if (scrollDiff < 0) {
+      // Scrolled down
+      this.header_.classList.add(IS_SCROLL_CLASS);
+      var _headerTop = parseInt(window.getComputedStyle(this.header_).getPropertyValue('top')) || 0;
+
+      if (this.content_.scrollHeight - this.content_.scrollTop <= this.content_.offsetHeight) {
+        // Bottom of content
+        if (_headerTop != 0) {
+          this.header_.style.top = this.config_.visibleAtScrollEnd ? '0' : '-' + this.header_.offsetHeight + 'px';
+        }
+      } else {
+        _headerTop += scrollDiff;
+        var offsetHeight = this.header_.offsetHeight;
+        this.header_.style.top = (Math.abs(_headerTop) > offsetHeight ? -offsetHeight : _headerTop) + 'px';
+      }
+    }
+
+    this.lastScrollTop_ = currentContentScrollTop;
+  };
+
+  var throttleScroll = (0, _fullThrottle2.default)(function (self) {
+    return self.reposition_();
+  });
+
+  /**
+   * Scroll header when content scrolls
+   * @param event
+   * @private
+   */
+  MaterialExtStickyHeader.prototype.scrollHandler_ = function () /* event */{
+    throttleScroll(this);
+  };
+
+  /**
+   * Init header position
+   * @private
+   */
+  MaterialExtStickyHeader.prototype.updatePosition_ = function () /* event */{
+    this.recalcWidth_();
+    this.reposition_();
+  };
+
+  /**
+   * Add mutation observer
+   * @private
+   */
+  MaterialExtStickyHeader.prototype.addMutationObserver_ = function () {
+    var _this = this;
+
+    // jsdom does not support MutationObserver - so this is not testable
+    /* istanbul ignore next */
+    this.mutationObserver_ = new MutationObserver(function () /*mutations*/{
+      // Adjust header width if content changes (e.g. in a SPA)
+      _this.updatePosition_();
+    });
+
+    this.mutationObserver_.observe(this.content_, {
+      attributes: false,
+      childList: true,
+      characterData: false,
+      subtree: true
+    });
+  };
+
+  /**
+  * Removes event listeners
+  * @private
+  */
+  MaterialExtStickyHeader.prototype.removeListeners_ = function () {
+
+    window.removeEventListener('resize', this.resizeHandler_);
+    window.removeEventListener('orientationchange', this.resizeHandler_);
+
+    if (this.content_) {
+      this.content_.removeEventListener('scroll', this.scrollHandler_);
+    }
+
+    if (this.mutationObserver_) {
+      this.mutationObserver_.disconnect();
+      this.mutationObserver_ = null;
+    }
+  };
+
+  /**
+   * Initialize component
+   */
+  MaterialExtStickyHeader.prototype.init = function () {
+
+    if (this.header_) {
+
+      this.removeListeners_();
+
+      if (this.header_.hasAttribute('data-config')) {
+        this.config_ = (0, _jsonUtils.jsonStringToObject)(this.header_.getAttribute('data-config'));
+      }
+
+      this.content_ = this.header_.parentNode.querySelector('.' + MDL_LAYOUT_CONTENT) || null;
+
+      if (this.content_) {
+        this.content_.style.paddingTop = this.header_.offsetHeight + 'px'; // Make room for sticky header
+        this.lastScrollTop_ = this.content_.scrollTop;
+
+        this.content_.addEventListener('scroll', this.scrollHandler_.bind(this));
+        window.addEventListener('resize', this.resizeHandler_.bind(this));
+        window.addEventListener('orientationchange', this.resizeHandler_.bind(this));
+
+        this.addMutationObserver_();
+        this.updatePosition_();
+
+        // Set upgraded flag
+        this.header_.classList.add(_constants.IS_UPGRADED);
+      }
+    }
+  };
+
+  /*
+   * Downgrade component
+   * E.g remove listeners and clean up resources
+   *
+   * Nothing to clean
+   *
+   MaterialExtStickyHeader.prototype.mdlDowngrade_ = function() {
+     'use strict';
+     console.log('***** MaterialExtStickyHeader.prototype.mdlDowngrade_');
+   };
+   */
+
+  // The component registers itself. It can assume componentHandler is available
+  // in the global scope.
+  /* eslint no-undef: 0 */
+  componentHandler.register({
+    constructor: MaterialExtStickyHeader,
+    classAsString: 'MaterialExtStickyHeader',
+    cssClass: 'mdlext-js-sticky-header'
+  });
+})(); /**
+       * @license
+       * Copyright 2016 Leif Olsen. All Rights Reserved.
+       *
+       * Licensed under the Apache License, Version 2.0 (the "License");
+       * you may not use this file except in compliance with the License.
+       * You may obtain a copy of the License at
+       *
+       *      http://www.apache.org/licenses/LICENSE-2.0
+       *
+       * Unless required by applicable law or agreed to in writing, software
+       * distributed under the License is distributed on an "AS IS" BASIS,
+       * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+       * See the License for the specific language governing permissions and
+       * limitations under the License.
+       *
+       * This code is built with Google Material Design Lite,
+       * which is Licensed under the Apache License, Version 2.0
+       */
+
+/**
+ * A sticky header makes site navigation easily accessible anywhere on the page and saves content space at the same.
+ * The header should auto-hide, i.e. hiding the header automatically when a user starts scrolling down the page and
+ * bringing the header back when a user might need it: they reach the bottom of the page or start scrolling up.
+ */
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/constants.js b/node_modules/mdl-ext/es/utils/constants.js
new file mode 100644
index 0000000..2d063e2
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/constants.js
@@ -0,0 +1,63 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var VK_TAB = 9;
+var VK_ENTER = 13;
+var VK_ESC = 27;
+var VK_SPACE = 32;
+var VK_PAGE_UP = 33;
+var VK_PAGE_DOWN = 34;
+var VK_END = 35;
+var VK_HOME = 36;
+var VK_ARROW_LEFT = 37;
+var VK_ARROW_UP = 38;
+var VK_ARROW_RIGHT = 39;
+var VK_ARROW_DOWN = 40;
+
+var ARIA_EXPANDED = 'aria-expanded';
+var ARIA_HIDDEN = 'aria-hidden';
+var ARIA_MULTISELECTABLE = 'aria-multiselectable';
+var ARIA_SELECTED = 'aria-selected';
+
+var IS_DIRTY = 'is-dirty';
+var IS_DISABLED = 'is-disabled';
+var IS_EXPANDED = 'is-expanded';
+var IS_FOCUSED = 'is-focused';
+var IS_INVALID = 'is-invalid';
+var IS_UPGRADED = 'is-upgraded';
+var DATA_UPGRADED = 'data-upgraded';
+
+var MDL_RIPPLE = 'mdl-ripple';
+var MDL_RIPPLE_COMPONENT = 'MaterialRipple';
+var MDL_RIPPLE_EFFECT = 'mdl-js-ripple-effect';
+var MDL_RIPPLE_EFFECT_IGNORE_EVENTS = 'mdl-js-ripple-effect--ignore-events';
+
+exports.VK_TAB = VK_TAB;
+exports.VK_ENTER = VK_ENTER;
+exports.VK_ESC = VK_ESC;
+exports.VK_SPACE = VK_SPACE;
+exports.VK_PAGE_UP = VK_PAGE_UP;
+exports.VK_PAGE_DOWN = VK_PAGE_DOWN;
+exports.VK_END = VK_END;
+exports.VK_HOME = VK_HOME;
+exports.VK_ARROW_LEFT = VK_ARROW_LEFT;
+exports.VK_ARROW_UP = VK_ARROW_UP;
+exports.VK_ARROW_RIGHT = VK_ARROW_RIGHT;
+exports.VK_ARROW_DOWN = VK_ARROW_DOWN;
+exports.ARIA_EXPANDED = ARIA_EXPANDED;
+exports.ARIA_HIDDEN = ARIA_HIDDEN;
+exports.ARIA_MULTISELECTABLE = ARIA_MULTISELECTABLE;
+exports.ARIA_SELECTED = ARIA_SELECTED;
+exports.IS_DIRTY = IS_DIRTY;
+exports.IS_DISABLED = IS_DISABLED;
+exports.IS_EXPANDED = IS_EXPANDED;
+exports.IS_FOCUSED = IS_FOCUSED;
+exports.IS_INVALID = IS_INVALID;
+exports.IS_UPGRADED = IS_UPGRADED;
+exports.DATA_UPGRADED = DATA_UPGRADED;
+exports.MDL_RIPPLE = MDL_RIPPLE;
+exports.MDL_RIPPLE_COMPONENT = MDL_RIPPLE_COMPONENT;
+exports.MDL_RIPPLE_EFFECT = MDL_RIPPLE_EFFECT;
+exports.MDL_RIPPLE_EFFECT_IGNORE_EVENTS = MDL_RIPPLE_EFFECT_IGNORE_EVENTS;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/debounce-function.js b/node_modules/mdl-ext/es/utils/debounce-function.js
new file mode 100644
index 0000000..27f977c
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/debounce-function.js
@@ -0,0 +1,89 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _apply = require("babel-runtime/core-js/reflect/apply");
+
+var _apply2 = _interopRequireDefault(_apply);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Debouncing enforces that a function not be called again until a certain
+ * amount of time has passed without it being called.
+ *
+ * @see https://css-tricks.com/the-difference-between-throttling-and-debouncing/
+ * @see https://github.com/webmodules/raf-debounce
+ * @see https://github.com/moszeed/es6-promise-debounce
+ * @see https://gist.github.com/philbirnie/893950093611d5c1dff4246a572cfbeb/
+ * @see https://github.com/SliceMeNice-ES6/event-utils/blob/master/debounce.js
+ * @see https://github.com/jeromedecoster/raf-funcs
+ * @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
+ * @see http://davidwalsh.name/javascript-debounce-function
+ *
+ * @param callback the callback
+ * @param threshold optional delay, default to 250 ms, min to 1000/60ms ms
+ * @param context  optional context of this, default to global
+ * @return {Function} reference to immediate and cancel
+ */
+var MIN_THRESHOLD = 1000 / 60;
+
+var debounceFunction = function debounceFunction(callback) {
+  var threshold = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 250;
+  var context = arguments[2];
+
+
+  if (threshold < MIN_THRESHOLD) {
+    threshold = MIN_THRESHOLD;
+  }
+
+  if (!context) {
+    context = this || window;
+  }
+
+  var next = null;
+  var start = 0;
+
+  return function () {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    var _cancel = function _cancel() {
+      if (next) {
+        window.cancelAnimationFrame(next);
+        next = null;
+      }
+    };
+
+    var execute = function execute() {
+      _cancel();
+      return (0, _apply2.default)(callback, context, args);
+    };
+
+    var later = function later() {
+      if (threshold - (Date.now() - start) <= 0) {
+        return execute();
+      }
+      next = window.requestAnimationFrame(later);
+    };
+
+    _cancel();
+    start = Date.now();
+    next = window.requestAnimationFrame(later);
+
+    return {
+      cancel: function cancel() {
+        return _cancel();
+      },
+      immediate: function immediate() {
+        return execute();
+      }
+    };
+  };
+};
+
+exports.default = debounceFunction;
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/dom-utils.js b/node_modules/mdl-ext/es/utils/dom-utils.js
new file mode 100644
index 0000000..3f63f90
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/dom-utils.js
@@ -0,0 +1,424 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.tether = exports.removeChildElements = exports.moveElements = exports.isRectInsideWindowViewport = exports.isFocusable = exports.getScrollParents = exports.getParentElements = exports.getWindowViewport = undefined;
+
+var _isNan = require('babel-runtime/core-js/number/is-nan');
+
+var _isNan2 = _interopRequireDefault(_isNan);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Remove child element(s)
+ * element.innerHTNL = '' has a performance penality!
+ * @see http://jsperf.com/empty-an-element/16
+ * @see http://jsperf.com/force-reflow
+ * @param element
+ * @param forceReflow
+ */
+var removeChildElements = function removeChildElements(element) {
+  var forceReflow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
+
+
+  // See: http://jsperf.com/empty-an-element/16
+  while (element.lastChild) {
+    element.removeChild(element.lastChild);
+  }
+  if (forceReflow) {
+    // See: http://jsperf.com/force-reflow
+    var d = element.style.display;
+
+    element.style.display = 'none';
+    element.style.display = d;
+  }
+};
+
+/**
+ * Moves child elements from a DOM node to another dom node.
+ * @param source {HTMLElement}
+ * @param target {HTMLElement} If the target parameter is ommited, a document fragment is created
+ * @return {HTMLElement} The target node
+ *
+ * @example
+ * // Moves child elements from a DOM node to another dom node.
+ * moveElements(source, destination);
+ *
+ * @example
+ * // If the second parameter is ommited, a document fragment is created:
+ * let fragment = moveElements(source);
+ *
+ * @See: https://github.com/webmodules/dom-move
+ */
+var moveElements = function moveElements(source, target) {
+  if (!target) {
+    target = source.ownerDocument.createDocumentFragment();
+  }
+  while (source.firstChild) {
+    target.appendChild(source.firstChild);
+  }
+  return target;
+};
+
+/**
+ * Get the browser viewport dimensions
+ * @see http://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript
+ * @return {{windowWidth: number, windowHeight: number}}
+ */
+var getWindowViewport = function getWindowViewport() {
+  return {
+    viewportWidth: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0),
+    viewportHeight: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
+  };
+};
+
+/**
+ * Check whether an element is in the window viewport
+ * @see http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/
+ * @param top
+ * @param left
+ * @param bottom
+ * @param right
+ * @return {boolean} true if rectangle is inside window viewport, otherwise false
+ */
+var isRectInsideWindowViewport = function isRectInsideWindowViewport(_ref) {
+  var top = _ref.top,
+      left = _ref.left,
+      bottom = _ref.bottom,
+      right = _ref.right;
+
+  var _getWindowViewport = getWindowViewport(),
+      viewportWidth = _getWindowViewport.viewportWidth,
+      viewportHeight = _getWindowViewport.viewportHeight;
+
+  return top >= 0 && left >= 0 && bottom <= viewportHeight && right <= viewportWidth;
+};
+
+/**
+ * Get a list of parent elements that can possibly scroll
+ * @param el the element to get parents for
+ * @returns {Array}
+ */
+var getScrollParents = function getScrollParents(el) {
+  var elements = [];
+
+  /*
+  for (el = el.parentNode; el; el = el.parentNode) {
+    const cs = window.getComputedStyle(el);
+    if(!(cs.overflowY === 'hidden' && cs.overflowX === 'hidden')) {
+      elements.unshift(el);
+    }
+    if(el === document.body) {
+      break;
+    }
+  }
+  */
+
+  var element = el.parentNode;
+  while (element) {
+    var cs = window.getComputedStyle(element);
+    if (!(cs.overflowY === 'hidden' && cs.overflowX === 'hidden')) {
+      elements.unshift(element);
+    }
+    if (element === document.body) {
+      break;
+    }
+    element = element.parentNode;
+  }
+
+  return elements;
+};
+
+/**
+ * Get a list of parent elements, from a given element to a given element
+ * @param {HTMLElement} from
+ * @param {HTMLElement} to
+ * @return {Array<HTMLElement>} the parent elements, not including from and to
+ */
+var getParentElements = function getParentElements(from, to) {
+  var result = [];
+  var element = from.parentNode;
+  while (element) {
+    if (element === to) {
+      break;
+    }
+    result.unshift(element);
+    element = element.parentNode;
+  }
+  return result;
+};
+
+/**
+ * Position element next to button
+ *
+ * Positioning strategy
+ *  1. element.height > viewport.height
+ *     let element.height = viewport.heigt
+ *     let element.overflow-y = auto
+ *  2. element.width > viewport.width
+ *     let element.width = viewport.width
+ *  3. position element below button, align left edge of element with button left
+ *       done if element inside viewport
+ *  4. position element below button, align right edge of element with button right
+ *       done if element inside viewport
+ *  5. positions element above button, aligns left edge of element with button left
+ *       done if element inside viewport
+ *  6. position element above the control element, aligned to its right.
+ *       done if element inside viewport
+ *  7. position element at button right hand side, aligns element top with button top
+ *       done if element inside viewport
+ *  8. position element at button left hand side, aligns element top with button top
+ *       done if element inside viewport
+ *  9. position element inside viewport
+ *     1. position element at viewport bottom
+ *     2. position element at button right hand side
+ *        done if element inside viewport
+ *     3. position element at button left hand side
+ *       done if element inside viewport
+ *     4. position element at viewport right
+ * 10. done
+ *
+ */
+var tether = function tether(controlledBy, element) {
+  var controlRect = controlledBy.getBoundingClientRect();
+
+  // 1. will element height fit inside window viewport?
+
+  var _getWindowViewport2 = getWindowViewport(),
+      viewportWidth = _getWindowViewport2.viewportWidth,
+      viewportHeight = _getWindowViewport2.viewportHeight;
+
+  element.style.height = 'auto';
+  //element.style.overflowY = 'hidden';
+  if (element.offsetHeight > viewportHeight) {
+    element.style.height = viewportHeight + 'px';
+    element.style.overflowY = 'auto';
+  }
+
+  // 2. will element width fit inside window viewport?
+  element.style.width = 'auto';
+  if (element.offsetWidth > viewportWidth) {
+    element.style.width = viewportWidth + 'px';
+  }
+
+  var elementRect = element.getBoundingClientRect();
+
+  // element to control distance
+  var dy = controlRect.top - elementRect.top;
+  var dx = controlRect.left - elementRect.left;
+
+  // element rect, window coordinates relative to top,left of control
+  var top = elementRect.top + dy;
+  var left = elementRect.left + dx;
+  var bottom = top + elementRect.height;
+  var right = left + elementRect.width;
+
+  // Position relative to control
+  var ddy = dy;
+  var ddx = dx;
+
+  if (isRectInsideWindowViewport({
+    top: top + controlRect.height,
+    left: left,
+    bottom: bottom + controlRect.height,
+    right: right
+  })) {
+    // 3 position element below the control element, aligned to its left
+    ddy = controlRect.height + dy;
+    //console.log('***** 3');
+  } else if (isRectInsideWindowViewport({
+    top: top + controlRect.height,
+    left: left + controlRect.width - elementRect.width,
+    bottom: bottom + controlRect.height,
+    right: left + controlRect.width
+  })) {
+    // 4 position element below the control element, aligned to its right
+    ddy = controlRect.height + dy;
+    ddx = dx + controlRect.width - elementRect.width;
+    //console.log('***** 4');
+  } else if (isRectInsideWindowViewport({
+    top: top - elementRect.height,
+    left: left,
+    bottom: bottom - elementRect.height,
+    right: right
+  })) {
+    // 5. position element above the control element, aligned to its left.
+    ddy = dy - elementRect.height;
+    //console.log('***** 5');
+  } else if (isRectInsideWindowViewport({
+    top: top - elementRect.height,
+    left: left + controlRect.width - elementRect.width,
+    bottom: bottom - elementRect.height,
+    right: left + controlRect.width
+  })) {
+    // 6. position element above the control element, aligned to its right.
+    ddy = dy - elementRect.height;
+    ddx = dx + controlRect.width - elementRect.width;
+    //console.log('***** 6');
+  } else if (isRectInsideWindowViewport({
+    top: top,
+    left: left + controlRect.width,
+    bottom: bottom,
+    right: right + controlRect.width
+  })) {
+    // 7. position element at button right hand side
+    ddx = controlRect.width + dx;
+    //console.log('***** 7');
+  } else if (isRectInsideWindowViewport({
+    top: top,
+    left: left - controlRect.width,
+    bottom: bottom,
+    right: right - controlRect.width
+  })) {
+    // 8. position element at button left hand side
+    ddx = dx - elementRect.width;
+    //console.log('***** 8');
+  } else {
+    // 9. position element inside viewport, near controlrect if possible
+    //console.log('***** 9');
+
+    // 9.1 position element near controlrect bottom
+    ddy = dy - bottom + viewportHeight;
+    if (top + controlRect.height >= 0 && bottom + controlRect.height <= viewportHeight) {
+      ddy = controlRect.height + dy;
+    } else if (top - elementRect.height >= 0 && bottom - elementRect.height <= viewportHeight) {
+      ddy = dy - elementRect.height;
+    }
+
+    if (left + elementRect.width + controlRect.width <= viewportWidth) {
+      // 9.2 Position element at button right hand side
+      ddx = controlRect.width + dx;
+      //console.log('***** 9.2');
+    } else if (left - elementRect.width >= 0) {
+      // 9.3 Position element at button left hand side
+      ddx = dx - elementRect.width;
+      //console.log('***** 9.3');
+    } else {
+      // 9.4 position element at (near) viewport right
+      var r = left + elementRect.width - viewportWidth;
+      ddx = dx - r;
+      //console.log('***** 9.4');
+    }
+  }
+
+  // 10. done
+  element.style.top = element.offsetTop + ddy + 'px';
+  element.style.left = element.offsetLeft + ddx + 'px';
+  //console.log('***** 10. done');
+};
+
+/**
+ * Check if the given element can receive focus
+ * @param {HTMLElement} element the element to check
+ * @return {boolean} true if the element is focusable, otherwise false
+ */
+var isFocusable = function isFocusable(element) {
+  // https://github.com/stephenmathieson/is-focusable/blob/master/index.js
+  // http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
+
+  if (element.hasAttribute('tabindex')) {
+    var tabindex = element.getAttribute('tabindex');
+    if (!(0, _isNan2.default)(tabindex)) {
+      return parseInt(tabindex) > -1;
+    }
+  }
+
+  if (element.hasAttribute('contenteditable') && element.getAttribute('contenteditable') !== 'false') {
+    // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-contenteditable
+    return true;
+  }
+
+  // natively focusable, but only when enabled
+  var selector = /input|select|textarea|button|details/i;
+  var name = element.nodeName;
+  if (selector.test(name)) {
+    return element.type.toLowerCase() !== 'hidden' && !element.disabled;
+  }
+
+  // anchors and area must have an href
+  if (name === 'A' || name === 'AREA') {
+    return !!element.href;
+  }
+
+  if (name === 'IFRAME') {
+    // Check visible iframe
+    var cs = window.getComputedStyle(element);
+    return cs.getPropertyValue('display').toLowerCase() !== 'none';
+  }
+
+  return false;
+};
+
+/**
+ * Get a list of offset parents for given element
+ * @see https://www.benpickles.com/articles/51-finding-a-dom-nodes-common-ancestor-using-javascript
+ * @param el the element
+ * @return {Array} a list of offset parents
+ */
+/*
+const offsetParents = (el) => {
+  const elements = [];
+  for (; el; el = el.offsetParent) {
+    elements.unshift(el);
+  }
+  if(!elements.find(e => e === document.body)) {
+    elements.unshift(document.body);
+  }
+  return elements;
+};
+*/
+
+/**
+ * Finds the common offset ancestor of two DOM nodes
+ * @see https://www.benpickles.com/articles/51-finding-a-dom-nodes-common-ancestor-using-javascript
+ * @see https://gist.github.com/benpickles/4059636
+ * @param a
+ * @param b
+ * @return {Element} The common offset ancestor of a and b
+ */
+/*
+const commonOffsetAncestor = (a, b) => {
+  const parentsA = offsetParents(a);
+  const parentsB = offsetParents(b);
+
+  for (let i = 0; i < parentsA.length; i++) {
+    if (parentsA[i] !== parentsB[i]) return parentsA[i-1];
+  }
+};
+*/
+
+/**
+ * Calculate position relative to a target element
+ * @see http://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
+ * @param target
+ * @param el
+ * @return {{top: number, left: number}}
+ */
+/*
+const calcPositionRelativeToTarget = (target, el) => {
+  let top = 0;
+  let left = 0;
+
+  while(el) {
+    top += (el.offsetTop - el.scrollTop + el.clientTop) || 0;
+    left += (el.offsetLeft - el.scrollLeft + el.clientLeft) || 0;
+    el = el.offsetParent;
+
+    if(el === target) {
+      break;
+    }
+  }
+  return { top: top, left: left };
+};
+*/
+
+exports.getWindowViewport = getWindowViewport;
+exports.getParentElements = getParentElements;
+exports.getScrollParents = getScrollParents;
+exports.isFocusable = isFocusable;
+exports.isRectInsideWindowViewport = isRectInsideWindowViewport;
+exports.moveElements = moveElements;
+exports.removeChildElements = removeChildElements;
+exports.tether = tether;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/easing.js b/node_modules/mdl-ext/es/utils/easing.js
new file mode 100644
index 0000000..95da687
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/easing.js
@@ -0,0 +1,22 @@
+'use strict';
+
+// See: http://robertpenner.com/easing/
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var easeInOutQuad = function easeInOutQuad(t, b, c, d) {
+  t /= d / 2;
+  if (t < 1) return c / 2 * t * t + b;
+  t--;
+  return -c / 2 * (t * (t - 2) - 1) + b;
+};
+
+var inOutQuintic = function inOutQuintic(t, b, c, d) {
+  var ts = (t /= d) * t;
+  var tc = ts * t;
+  return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
+};
+
+exports.easeInOutQuad = easeInOutQuad;
+exports.inOutQuintic = inOutQuintic;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/full-throttle.js b/node_modules/mdl-ext/es/utils/full-throttle.js
new file mode 100644
index 0000000..da32123
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/full-throttle.js
@@ -0,0 +1,52 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _apply = require("babel-runtime/core-js/reflect/apply");
+
+var _apply2 = _interopRequireDefault(_apply);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Since some events can fire at a high rate, the event handler should be limited to execute computationally
+ * expensive operations, such as DOM modifications, inside a single rendered frame.
+ * When listening to e.g. scroll and resize events, the browser tends to fire off more events per
+ * second than are actually useful. For instance, if your event listener sets some element positions, then it
+ * is possible for those positions to be updated multiple times in a single rendered frame. In this case, all of
+ * the layout calculations triggered by setting the elements' positions will be wasted except for the one time that
+ * it runs immediately prior to the browser rendering the updated layout to the screen.
+ * To avoid wasting cycles, we can use requestAnimationFrame to only run the event listener once just before the page
+ * is rendered to the screen.
+ * *
+ * @param callback the function to throttle
+ * @param context  optional context of this, default to global
+ * @return {function(...[*])}
+ */
+var fullThrottle = function fullThrottle(callback, context) {
+
+  if (!context) {
+    context = undefined || window;
+  }
+
+  var throttling = false;
+
+  return function () {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    if (!throttling) {
+      throttling = true;
+      window.requestAnimationFrame(function () {
+        throttling = false;
+        return (0, _apply2.default)(callback, context, args);
+      });
+    }
+  };
+};
+
+exports.default = fullThrottle;
+module.exports = exports["default"];
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/index.js b/node_modules/mdl-ext/es/utils/index.js
new file mode 100644
index 0000000..4ef9db4
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/index.js
@@ -0,0 +1,15 @@
+'use strict';
+
+require('./constants');
+
+require('./dom-utils');
+
+require('./string-utils');
+
+require('./json-utils');
+
+require('./full-throttle');
+
+require('./easing');
+
+require('./interval-function');
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/interval-function.js b/node_modules/mdl-ext/es/utils/interval-function.js
new file mode 100644
index 0000000..abe7f9b
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/interval-function.js
@@ -0,0 +1,96 @@
+'use strict';
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+var MIN_INERVAL = 1000 / 60;
+
+/**
+ * Trigger a callback at a given interval
+ * @param interval defaults to 1000/60 ms
+ * @return {function()} reference to start, stop, immediate and started
+ */
+
+var intervalFunction = function intervalFunction() {
+  var interval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : MIN_INERVAL;
+
+
+  var lapse = interval < MIN_INERVAL ? MIN_INERVAL : interval;
+  var cb = undefined;
+  var next = null;
+  var timeElapsed = 0;
+
+  var execute = function execute() {
+    var f = cb(timeElapsed);
+    if (!f) {
+      cancel();
+    }
+  };
+
+  var cancel = function cancel() {
+    if (next) {
+      window.cancelAnimationFrame(next);
+    }
+    next = null;
+    timeElapsed = 0;
+  };
+
+  var _start = function _start() {
+    var timeStart = Date.now();
+
+    var loop = function loop(now) {
+      if (next) {
+        next = window.requestAnimationFrame(function () {
+          return loop(Date.now());
+        });
+
+        timeElapsed += now - timeStart;
+
+        if (timeElapsed >= lapse) {
+          execute();
+          if ((timeElapsed -= lapse) > lapse) {
+            // time elapsed - interval_ > interval_ , indicates inactivity
+            // Could be due to browser minimized, tab changed, screen saver started, computer sleep, and so on
+            timeElapsed = 0;
+          }
+        }
+        timeStart = now;
+      }
+    };
+
+    next = 1; // a truthy value for first loop
+    loop(timeStart);
+  };
+
+  return {
+    get started() {
+      return next != null;
+    },
+    get interval() {
+      return lapse;
+    },
+    set interval(value) {
+      lapse = value < MIN_INERVAL ? MIN_INERVAL : value;
+    },
+    start: function start(callback) {
+      if (typeof callback !== 'function') {
+        throw new TypeError('callback parameter must be a function');
+      }
+      cb = callback;
+      _start();
+    },
+    immediate: function immediate() {
+      if (!cb) {
+        throw new ReferenceError('callback parameter is not defined. Call start before immediate.');
+      }
+      execute();
+    },
+
+    stop: function stop() {
+      return cancel();
+    }
+  };
+};
+
+exports.default = intervalFunction;
+module.exports = exports['default'];
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/json-utils.js b/node_modules/mdl-ext/es/utils/json-utils.js
new file mode 100644
index 0000000..50be006
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/json-utils.js
@@ -0,0 +1,31 @@
+'use strict';
+
+/**
+ * Converts a JSON string to object
+ * @param jsonString
+ * @param source
+ */
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.jsonStringToObject = undefined;
+
+var _assign = require('babel-runtime/core-js/object/assign');
+
+var _assign2 = _interopRequireDefault(_assign);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var jsonStringToObject = function jsonStringToObject(jsonString) {
+  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+  var s = jsonString.replace(/'/g, '"');
+  try {
+    return (0, _assign2.default)(source, JSON.parse(s));
+  } catch (e) {
+    throw new Error('Failed to parse json string: ' + s + '. Error: ' + e.message);
+  }
+};
+
+exports.jsonStringToObject = jsonStringToObject;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/resize-observer.js b/node_modules/mdl-ext/es/utils/resize-observer.js
new file mode 100644
index 0000000..37f0e0d
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/resize-observer.js
@@ -0,0 +1,377 @@
+'use strict';
+
+var _getIterator2 = require('babel-runtime/core-js/get-iterator');
+
+var _getIterator3 = _interopRequireDefault(_getIterator2);
+
+var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
+
+var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
+
+var _createClass2 = require('babel-runtime/helpers/createClass');
+
+var _createClass3 = _interopRequireDefault(_createClass2);
+
+var _intervalFunction = require('./interval-function');
+
+var _intervalFunction2 = _interopRequireDefault(_intervalFunction);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+(function (window, document) {
+  'use strict';
+
+  if (typeof window.ResizeObserver !== 'undefined') {
+    return;
+  }
+
+  document.resizeObservers = [];
+
+  /**
+   * The content rect is defined in section 2.3 ResizeObserverEntry of the spec
+   * @param target the element to calculate the content rect for
+   * @return {{top: (Number|number), left: (Number|number), width: number, height: number}}
+   *
+   * Note:
+   * Avoid using margins on the observed element. The calculation can return incorrect values when margins are involved.
+   *
+   * The following CSS will report incorrect width (Chrome OSX):
+   *
+   * <div id="outer" style="width: 300px; height:300px; background-color: green;overflow:auto;">
+   *   <div id="observed" style="width: 400px; height:400px; background-color: yellow; margin:30px; border: 20px solid red; padding:10px;">
+   *   </div>
+   * </div>
+   *
+   * The calculated width is 280. The actual (correct) width is 340 since Chrome clips the margin.
+   *
+   * Use an outer container if you really need a "margin":
+   *
+   * <div id="outer" style="width: 300px; height:300px; background-color: green;overflow:auto; padding:30px;">
+   *   <div id="observed" style="width: 400px; height:400px; background-color: yellow; margin: 0; border: 20px solid red; padding:10px;">
+   *   </div>
+   * </div>
+   *
+   * A more detailed explanation can be fund here:
+   * http://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively
+   */
+  var getContentRect = function getContentRect(target) {
+    var cs = window.getComputedStyle(target);
+    var r = target.getBoundingClientRect();
+    var top = parseFloat(cs.paddingTop) || 0;
+    var left = parseFloat(cs.paddingLeft) || 0;
+    var width = r.width - ((parseFloat(cs.marginLeft) || 0) + (parseFloat(cs.marginRight) || 0) + (parseFloat(cs.borderLeftWidth) || 0) + (parseFloat(cs.borderRightWidth) || 0) + left + (parseFloat(cs.paddingRight) || 0));
+    var height = r.height - ((parseFloat(cs.marginTop) || 0) + (parseFloat(cs.marginBottom) || 0) + (parseFloat(cs.borderTopWidth) || 0) + (parseFloat(cs.borderBottomWidth) || 0) + top + (parseFloat(cs.paddingBottom) || 0));
+    return { width: width, height: height, top: top, left: left };
+  };
+
+  var dimensionHasChanged = function dimensionHasChanged(target, lastWidth, lastHeight) {
+    var _getContentRect = getContentRect(target),
+        width = _getContentRect.width,
+        height = _getContentRect.height;
+
+    return width !== lastWidth || height !== lastHeight;
+  };
+
+  /**
+   * ResizeObservation holds observation information for a single Element.
+   * @param target
+   * @return {{target: *, broadcastWidth, broadcastHeight, isOrphan: (function()), isActive: (function())}}
+   * @constructor
+   */
+  var ResizeObservation = function ResizeObservation(target) {
+    var _getContentRect2 = getContentRect(target),
+        width = _getContentRect2.width,
+        height = _getContentRect2.height;
+
+    return {
+      target: target,
+      broadcastWidth: width,
+      broadcastHeight: height,
+
+      isOrphan: function isOrphan() {
+        return !this.target || !this.target.parentNode;
+      },
+      isActive: function isActive() {
+        return dimensionHasChanged(this.target, this.broadcastWidth, this.broadcastHeight);
+      }
+    };
+  };
+
+  /**
+   * A snapshot of the observed element
+   * @param target
+   * @param rect
+   * @return {{target: *, contentRect: *}}
+   * @constructor
+   */
+  var ResizeObserverEntry = function ResizeObserverEntry(target, rect) {
+    return {
+      target: target,
+      contentRect: rect
+    };
+  };
+
+  /**
+   * The ResizeObserver is used to observe changes to Element's content rect.
+   */
+
+  var ResizeObserver = function () {
+
+    /**
+     * Constructor for instantiating new Resize observers.
+     * @param callback void (sequence<ResizeObserverEntry> entries). The function which will be called on each resize.
+     * @throws {TypeError}
+     */
+    function ResizeObserver(callback) {
+      (0, _classCallCheck3.default)(this, ResizeObserver);
+
+
+      if (typeof callback !== 'function') {
+        throw new TypeError('callback parameter must be a function');
+      }
+
+      this.callback_ = callback;
+      this.observationTargets_ = [];
+      this.activeTargets_ = [];
+
+      document.resizeObservers.push(this);
+    }
+
+    /**
+     * A list of ResizeObservations. It represents all Elements being observed.
+     *
+     * @return {Array}
+     */
+
+
+    (0, _createClass3.default)(ResizeObserver, [{
+      key: 'observe',
+
+
+      /**
+       * Adds target to the list of observed elements.
+       * @param {HTMLElement} target The target to observe
+       */
+      value: function observe(target) {
+        if (target) {
+          if (!(target instanceof HTMLElement)) {
+            throw new TypeError('target parameter must be an HTMLElement');
+          }
+          if (!this.observationTargets_.find(function (t) {
+            return t.target === target;
+          })) {
+            this.observationTargets_.push(ResizeObservation(target));
+            resizeController.start();
+          }
+        }
+      }
+
+      /**
+       * Removes target from the list of observed elements.
+       * @param target The target to remove
+       */
+
+    }, {
+      key: 'unobserve',
+      value: function unobserve(target) {
+        var i = this.observationTargets_.findIndex(function (t) {
+          return t.target === target;
+        });
+        if (i > -1) {
+          this.observationTargets_.splice(i, 1);
+        }
+      }
+
+      /**
+       * Stops the ResizeObserver instance from receiving notifications of resize changes.
+       * Until the observe() method is used again, observer's callback will not be invoked.
+       */
+
+    }, {
+      key: 'disconnect',
+      value: function disconnect() {
+        this.observationTargets_ = [];
+        this.activeTargets_ = [];
+      }
+
+      /**
+       * Removes the ResizeObserver from the list of observers
+       */
+
+    }, {
+      key: 'destroy',
+      value: function destroy() {
+        var _this = this;
+
+        this.disconnect();
+        var i = document.resizeObservers.findIndex(function (o) {
+          return o === _this;
+        });
+        if (i > -1) {
+          document.resizeObservers.splice(i, 1);
+        }
+      }
+    }, {
+      key: 'deleteOrphansAndPopulateActiveTargets_',
+      value: function deleteOrphansAndPopulateActiveTargets_() {
+
+        // Works, but two iterations
+        //this.observationTargets_ = this.observationTargets_.filter( resizeObervation => !resizeObervation.isOrphan());
+        //this.activeTargets_ = this.observationTargets_.filter( resizeObervation => resizeObervation.isActive());
+
+        // Same result as above, one iteration
+        /*
+        this.activeTargets_ = [];
+        let n = this.observationTargets_.length-1;
+        while(n >= 0) {
+          if(this.observationTargets_[n].isOrphan()) {
+            this.observationTargets_.splice(n, 1);
+          }
+          else if(this.observationTargets_[n].isActive()) {
+            this.activeTargets_.push(this.observationTargets_[n]);
+          }
+          n -= 1;
+        }
+        */
+
+        // Same result as above - but reduce is cooler :-)
+        this.activeTargets_ = this.observationTargets_.reduceRight(function (prev, resizeObservation, index, arr) {
+          if (resizeObservation.isOrphan()) {
+            arr.splice(index, 1);
+          } else if (resizeObservation.isActive()) {
+            prev.push(resizeObservation);
+          }
+          return prev;
+        }, []);
+      }
+    }, {
+      key: 'broadcast_',
+      value: function broadcast_() {
+        this.deleteOrphansAndPopulateActiveTargets_();
+        if (this.activeTargets_.length > 0) {
+          var entries = [];
+          var _iteratorNormalCompletion = true;
+          var _didIteratorError = false;
+          var _iteratorError = undefined;
+
+          try {
+            for (var _iterator = (0, _getIterator3.default)(this.activeTargets_), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+              var resizeObservation = _step.value;
+
+              var rect = getContentRect(resizeObservation.target);
+              resizeObservation.broadcastWidth = rect.width;
+              resizeObservation.broadcastHeight = rect.height;
+              entries.push(ResizeObserverEntry(resizeObservation.target, rect));
+            }
+          } catch (err) {
+            _didIteratorError = true;
+            _iteratorError = err;
+          } finally {
+            try {
+              if (!_iteratorNormalCompletion && _iterator.return) {
+                _iterator.return();
+              }
+            } finally {
+              if (_didIteratorError) {
+                throw _iteratorError;
+              }
+            }
+          }
+
+          this.callback_(entries);
+          this.activeTargets_ = [];
+        }
+      }
+    }, {
+      key: 'observationTargets',
+      get: function get() {
+        return this.observationTargets_;
+      }
+
+      /**
+       *  A list of ResizeObservations. It represents all Elements whose size has
+       *  changed since last observation broadcast that are eligible for broadcast.
+       *
+       * @return {Array}
+       */
+
+    }, {
+      key: 'activeTargets',
+      get: function get() {
+        return this.activeTargets_;
+      }
+    }]);
+    return ResizeObserver;
+  }();
+
+  //let interval = require('./interval-function');
+
+  /**
+   * Broadcasts Element.resize events
+   * @return {{start: (function()), stop: (function())}}
+   * @constructor
+   */
+
+
+  var ResizeController = function ResizeController() {
+
+    var shouldStop = function shouldStop() {
+      return document.resizeObservers.findIndex(function (resizeObserver) {
+        return resizeObserver.observationTargets.length > 0;
+      }) > -1;
+    };
+
+    var execute = function execute() {
+      //console.log('***** Execute');
+      var _iteratorNormalCompletion2 = true;
+      var _didIteratorError2 = false;
+      var _iteratorError2 = undefined;
+
+      try {
+        for (var _iterator2 = (0, _getIterator3.default)(document.resizeObservers), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+          var resizeObserver = _step2.value;
+
+          resizeObserver.broadcast_();
+        }
+      } catch (err) {
+        _didIteratorError2 = true;
+        _iteratorError2 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion2 && _iterator2.return) {
+            _iterator2.return();
+          }
+        } finally {
+          if (_didIteratorError2) {
+            throw _iteratorError2;
+          }
+        }
+      }
+
+      return shouldStop();
+    };
+
+    var interval = (0, _intervalFunction2.default)(200);
+
+    return {
+      start: function start() {
+        if (!interval.started) {
+          //console.log('***** Start poll');
+          interval.start(execute);
+        }
+      }
+    };
+  };
+
+  window.ResizeObserver = ResizeObserver;
+
+  var resizeController = ResizeController();
+  //console.log('***** ResizeObserver ready');
+})(window, document);
+/**
+ * An API for observing changes to Element’s size.
+ *
+ * @See https://wicg.github.io/ResizeObserver/
+ * @ee https://github.com/pelotoncycle/resize-observer
+ *
+ */
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/string-utils.js b/node_modules/mdl-ext/es/utils/string-utils.js
new file mode 100644
index 0000000..ab0045a
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/string-utils.js
@@ -0,0 +1,114 @@
+'use strict';
+
+/**
+ * @license
+ * Copyright 2016 Leif Olsen. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A javascript utility for conditionally creating a list of strings.
+ * The function takes any number of arguments which can be a string or object.
+ * Inspired by (but not copied from) JedWatson/classnames, https://github.com/JedWatson/classnames
+ *
+ * @param  {*} args the strings and/or objects to
+ * @return {Array} a list of strings
+ * @example
+ * // Returns ['foo', 'bar', 'baz', 'quux']
+ * stringList(', ', 'foo', { bar: true, duck: false }, 'baz', { quux: true });
+ * @example see the tests for more examples
+ */
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.stringList = exports.randomString = exports.joinStrings = undefined;
+
+var _keys = require('babel-runtime/core-js/object/keys');
+
+var _keys2 = _interopRequireDefault(_keys);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var stringList = function stringList() {
+  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+    args[_key] = arguments[_key];
+  }
+
+  var isString = function isString(str) {
+    return str != null && typeof str === 'string';
+  };
+
+  var flatten = function flatten(list) {
+    return list.reduce(function (a, b) {
+      return a.concat(Array.isArray(b) ? flatten(b) : b);
+    }, []);
+  };
+
+  var objectToStrings = function objectToStrings(arg) {
+    return (0, _keys2.default)(arg).filter(function (key) {
+      return arg[key];
+    }).map(function (key) {
+      return key;
+    });
+  };
+
+  return args.filter(function (arg) {
+    return !!arg;
+  }).map(function (arg) {
+    return isString(arg) ? arg : objectToStrings(arg);
+  }).reduce(function (result, arg) {
+    return result.concat(Array.isArray(arg) ? flatten(arg) : arg);
+  }, []);
+};
+
+/**
+ * A simple javascript utility for conditionally joining strings together.
+ * The function takes a delimiter string and any number of arguments which can be a string or object.
+ *
+ * @param delimiter delimiter to separate joined strings
+ * @param  {*} args the strings and/or objects to join
+ * @return {String} the joined strings
+ * @example
+ * // Returns 'foo, bar, baz, quux'
+ * joinStrings(', ', 'foo', { bar: true, duck: false }, 'baz', { quux: true });
+ * @example see the tests for more examples
+ */
+var joinStrings = function joinStrings() {
+  for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+    args[_key2 - 1] = arguments[_key2];
+  }
+
+  var delimiter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ' ';
+  return stringList.apply(undefined, args).join(delimiter);
+};
+
+/**
+ * Generates a random string with a given length
+ * @param n {Integer} length of generated string
+ * @see http://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
+ * @return {String} the random string
+ * @example
+ * // Returns e.g. 'pd781w0y'
+ * randomString(8);
+ * @example see the tests for more examples
+ */
+var randomString = function randomString() {
+  var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 12;
+  return Array(n + 1).join((Math.random().toString(36) + '00000000000000000').slice(2, 18)).slice(0, n);
+};
+
+exports.joinStrings = joinStrings;
+exports.randomString = randomString;
+exports.stringList = stringList;
\ No newline at end of file
diff --git a/node_modules/mdl-ext/es/utils/throttle-function.js b/node_modules/mdl-ext/es/utils/throttle-function.js
new file mode 100644
index 0000000..03a5cea
--- /dev/null
+++ b/node_modules/mdl-ext/es/utils/throttle-function.js
@@ -0,0 +1,84 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _apply = require("babel-runtime/core-js/reflect/apply");
+
+var _apply2 = _interopRequireDefault(_apply);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Throttling enforces a maximum number of times a function can be called over time.
+ *
+ * @param callback the function to throttle
+ * @param delay optional delay, default to 1000/60ms
+ * @param context optional context of this, default to global window
+ * @returns {Function} reference to immediate and cancel functions
+ * @see https://developer.mozilla.org/en-US/docs/Web/Events/resize#Example
+ * @see https://gist.github.com/yoavniran/d1d33f278bb7744d55c3
+ * @see https://github.com/pelotoncycle/frame-throttle
+ * @see https://github.com/jeromedecoster/raf-funcs
+ */
+var MIN_DELAY = 1000 / 60;
+
+var throttleFunction = function throttleFunction(callback) {
+  var delay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : MIN_DELAY;
+  var context = arguments[2];
+
+
+  if (delay < MIN_DELAY) {
+    delay = MIN_DELAY;
+  }
+
+  if (!context) {
+    context = undefined || window;
+  }
+
+  var next = null;
+  var start = 0;
+
+  return function () {
+    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+      args[_key] = arguments[_key];
+    }
+
+    var _cancel = function _cancel() {
+      if (next !== null) {
+        window.cancelAnimationFrame(next);
+        next = null;
+      }
+    };
+
+    var execute = function execute() {
+      _cancel();
+      return (0, _apply2.default)(callback, context, args);
+    };
+
+    var later = function later() {
+      if (delay - (Date.now() - start) <= 0) {
+        return execute();
+      }
+      next = window.requestAnimationFrame(later);
+    };
+
+    if (next === null) {
+      start = Date.now();
+      next = window.requestAnimationFrame(later);
+    }
+
+    return {
+      cancel: function cancel() {
+        return _cancel();
+      },
+      immediate: function immediate() {
+        return execute();
+      }
+    };
+  };
+};
+
+exports.default = throttleFunction;
+module.exports = exports["default"];
\ No newline at end of file