Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/mdl-ext/src/carousel/_carousel.scss b/node_modules/mdl-ext/src/carousel/_carousel.scss
new file mode 100644
index 0000000..674751a
--- /dev/null
+++ b/node_modules/mdl-ext/src/carousel/_carousel.scss
@@ -0,0 +1,141 @@
+@charset "UTF-8";
+
+/**
+ * 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 carousel ...
+ */
+
+// Use of this module requires the user to include variables from material-design-lite
+//@import "../../node_modules/material-design-lite/src/variables";
+//@import "../../node_modules/material-design-lite/src/mixins";
+
+ul.mdlext-carousel {
+  list-style: none;
+}
+
+.mdlext-carousel {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  overflow: hidden;
+  height: 100%;  // Use a container to constrain height and width
+  width: 100%;
+  display: block;
+  white-space: nowrap;
+  font-size: 0;
+  background-color: transparent;
+}
+
+.mdlext-carousel__slide {
+  box-sizing: border-box;
+  display: inline-block;
+  position: relative;
+  outline: 0;
+  margin: 0 $mdlext-carousel-slide-margin-horizontal;
+  padding:0;
+  height: 100%;
+  border-top: $mdlext-carousel-slide-border-top-width solid transparent; // Makes room for the animated select/focus line
+
+  //&:focus,
+  &[aria-selected],
+  &[aria-selected='true'] {
+    figcaption {
+      // As far as I can see there is no way to darken/lighten a text color
+      // defined by MDL, due to the "unqote" functions.
+      // So this is a hack
+      color: rgba(0, 0, 0, 1) !important;
+      background-color: rgba(255, 255, 255, 0.25);
+    }
+  }
+
+  &[aria-selected]::after,
+  &[aria-selected='true']::after {
+    height: $mdlext-carousel-slide-border-top-width;
+    width: 100%;
+    display: block;
+    content: ' ';
+    top: (-$mdlext-carousel-slide-border-top-width);
+    left: 0;
+    position: absolute;
+    background: $mdlext-carousel-slide-border-top-color;
+    animation: border-expand 0.2s cubic-bezier(0.4, 0.0, 0.4, 1) 0.01s alternate forwards;
+    transition: all 1s cubic-bezier(0.4, 0.0, 1, 1);
+  }
+
+
+  a {
+    text-decoration: none;
+  }
+
+  figure {
+    box-sizing: border-box;
+    position: relative;
+    height: 100%;
+    margin: 0;
+    padding: 0;
+
+    img {
+      box-sizing: border-box;
+      max-height: 100%;
+    }
+
+    figcaption {
+      box-sizing: border-box;
+      @include typo-caption($colorContrast: false, $usePreferred: true);
+
+      color: $mdlext-carousel-slide-figcaption-color;
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      white-space: nowrap;
+      overflow: hidden;
+      max-width: 100%;
+      width: 100%;
+      text-align: center;
+      text-overflow: ellipsis;
+      padding: 4px 0;
+    }
+    &:hover {
+      figcaption {
+        // As far as I can see there is no way to darken/lighten a text color
+        // defined by MDL, due to the "unqote" functions.
+        // So this is a hack
+        color: rgba(0, 0, 0, 1) !important;
+        background-color: rgba(255, 255, 255, 0.25);
+      }
+    }
+  }
+
+  .mdlext-carousel__slide__ripple-container {
+    text-decoration: none;
+    display: block;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    outline: 0;
+
+    & .mdl-ripple {
+      background: $mdlext-carousel-slide-ripple-color;
+    }
+  }
+}
+
+
diff --git a/node_modules/mdl-ext/src/carousel/carousel.js b/node_modules/mdl-ext/src/carousel/carousel.js
new file mode 100644
index 0000000..8fe59e1
--- /dev/null
+++ b/node_modules/mdl-ext/src/carousel/carousel.js
@@ -0,0 +1,726 @@
+/**
+ * @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
+ */
+
+import intervalFunction from '../utils/interval-function';
+import { inOutQuintic } from '../utils/easing';
+import { jsonStringToObject} from '../utils/json-utils';
+import {
+  VK_TAB,
+  VK_ENTER,
+  VK_ESC,
+  VK_SPACE,
+  VK_PAGE_UP,
+  VK_PAGE_DOWN,
+  VK_END,
+  VK_HOME,
+  VK_ARROW_LEFT,
+  VK_ARROW_UP,
+  VK_ARROW_RIGHT,
+  VK_ARROW_DOWN,
+  IS_UPGRADED,
+  IS_FOCUSED,
+  MDL_RIPPLE,
+  MDL_RIPPLE_COMPONENT,
+  MDL_RIPPLE_EFFECT,
+  MDL_RIPPLE_EFFECT_IGNORE_EVENTS
+} from '../utils/constants';
+
+const MDL_RIPPLE_CONTAINER = 'mdlext-carousel__slide__ripple-container';
+
+
+(function() {
+  'use strict';
+
+  //const CAROUSEL = 'mdlext-carousel';
+  const SLIDE      = 'mdlext-carousel__slide';
+  const ROLE       = 'list';
+  const SLIDE_ROLE = 'listitem';
+
+
+  /**
+   * @constructor
+   * @param {Element} element The element that will be upgraded.
+   */
+  const MaterialExtCarousel = function MaterialExtCarousel(element) {
+    // Stores the element.
+    this.element_ = element;
+
+    // Default config
+    this.config_ = {
+      interactive  : true,
+      autostart    : false,
+      type         : 'slide',
+      interval     : 1000,
+      animationLoop: intervalFunction(1000)
+    };
+
+    this.scrollAnimation_ = intervalFunction(33);
+
+    // Initialize instance.
+    this.init();
+  };
+
+  window['MaterialExtCarousel'] = MaterialExtCarousel;
+
+
+  /**
+   * Start slideshow animation
+   * @private
+   */
+  MaterialExtCarousel.prototype.startSlideShow_ = function() {
+
+    const nextSlide = () => {
+      let 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;
+    };
+
+    const nextScroll = direction => {
+      let nextDirection = direction;
+
+      if('next' === direction &&  this.element_.scrollLeft === this.element_.scrollWidth - this.element_.clientWidth) {
+        nextDirection = 'prev';
+      }
+      else if(this.element_.scrollLeft === 0) {
+        nextDirection = 'next';
+      }
+      const 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) {
+      this.config_.animationLoop.interval = this.config_.interval;
+      let direction = 'next';
+
+      if('scroll' === this.config_.type) {
+        this.config_.animationLoop.start( () => {
+          direction = nextScroll(direction);
+          return true; // It runs until cancelSlideShow_ is triggered
+        });
+      }
+      else {
+        nextSlide();
+        this.config_.animationLoop.start( () => {
+          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', VK_ESC, this.element_.querySelector(`.${SLIDE}[aria-selected]`));
+    }
+  };
+
+  /**
+   * Animate scroll
+   * @param newPosition
+   * @param newDuration
+   * @param completedCallback
+   * @private
+   */
+  MaterialExtCarousel.prototype.animateScroll_ = function( newPosition, newDuration, completedCallback ) {
+
+    const start = this.element_.scrollLeft;
+    const distance = newPosition - start;
+
+    if(distance !== 0) {
+      const duration = Math.max(Math.min(Math.abs(distance), newDuration||400), 100); // duration is between 100 and newDuration||400ms||distance
+      let t = 0;
+      this.scrollAnimation_.stop();
+      this.scrollAnimation_.start( timeElapsed => {
+        t += timeElapsed;
+        if(t < duration) {
+          this.element_.scrollLeft = inOutQuintic(t, start, distance, duration);
+          return true;
+        }
+        else {
+          this.element_.scrollLeft = newPosition;
+          if(completedCallback) {
+            completedCallback();
+          }
+          return false;
+        }
+      });
+    }
+    else {
+      if(completedCallback) {
+        completedCallback();
+      }
+    }
+  };
+
+  /**
+   * Execute commend
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.command_ = function( event ) {
+    let x = 0;
+    let slide = null;
+    const 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':
+        Object.assign(this.config_, event.detail);
+        this.startSlideShow_();
+        return;
+
+      case 'pause':
+        return;
+
+      default:
+        return;
+    }
+
+    this.animateScroll_(x, undefined, () => {
+      if ('scroll-next' === a || 'scroll-prev' === a) {
+        const slides = this.getSlidesInViewport_();
+        if (slides.length > 0) {
+          slide = 'scroll-next' === a ? slides[0] : slides[slides.length - 1];
+        }
+      }
+      this.setAriaSelected_(slide);
+      this.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_) {
+
+      let action = 'first';
+
+      if ( event.keyCode === VK_HOME    || event.keyCode === VK_END
+        || event.keyCode === VK_PAGE_UP || event.keyCode === VK_PAGE_DOWN) {
+
+        event.preventDefault();
+        if (event.keyCode === VK_END) {
+          action = 'last';
+        }
+        else if (event.keyCode === VK_PAGE_UP) {
+          action = 'scroll-prev';
+        }
+        else if (event.keyCode === VK_PAGE_DOWN) {
+          action = 'scroll-next';
+        }
+
+        const cmd = new CustomEvent('select', {
+          detail: {
+            action: action,
+          }
+        });
+        this.command_(cmd);
+      }
+      else if ( event.keyCode === VK_TAB
+        || event.keyCode === VK_ENTER      || event.keyCode === VK_SPACE
+        || event.keyCode === VK_ARROW_UP   || event.keyCode === VK_ARROW_LEFT
+        || event.keyCode === VK_ARROW_DOWN || event.keyCode === VK_ARROW_RIGHT) {
+
+        let slide = getSlide_(event.target);
+
+        if(!slide) {
+          return;
+        }
+
+        // Cancel slideshow if running
+        this.cancelSlideShow_();
+
+        switch (event.keyCode) {
+          case VK_ARROW_UP:
+          case VK_ARROW_LEFT:
+            action = 'prev';
+            slide = slide.previousElementSibling;
+            break;
+
+          case VK_ARROW_DOWN:
+          case VK_ARROW_RIGHT:
+            action = 'next';
+            slide = slide.nextElementSibling;
+            break;
+
+          case VK_TAB:
+            if (event.shiftKey) {
+              action = 'prev';
+              slide = slide.previousElementSibling;
+            }
+            else {
+              action = 'next';
+              slide = slide.nextElementSibling;
+            }
+            break;
+
+          case VK_SPACE:
+          case 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) {
+    event.preventDefault();
+
+    // Cancel slideshow if running
+    this.cancelSlideShow_();
+
+    let updating = false;
+    let rAFDragId = 0;
+
+    const startX = event.clientX || (event.touches !== undefined ? event.touches[0].clientX : 0);
+    let prevX = startX;
+    const targetElement = event.target;
+
+    const update = e => {
+      const currentX = (e.clientX || (e.touches !== undefined ? e.touches[0].clientX : 0));
+      const dx = prevX - currentX;
+
+      if(dx < 0) {
+        this.element_.scrollLeft = Math.max(this.element_.scrollLeft + dx, 0);
+      }
+      else if(dx > 0) {
+        this.element_.scrollLeft = Math.min(this.element_.scrollLeft + dx, this.element_.scrollWidth - this.element_.clientWidth);
+      }
+
+      prevX = currentX;
+      updating = false;
+    };
+
+    // drag handler
+    const drag = e => {
+      e.preventDefault();
+
+      if(!updating) {
+        rAFDragId = window.requestAnimationFrame( () => update(e));
+        updating = true;
+      }
+    };
+
+    // end drag handler
+    const endDrag = e => {
+      e.preventDefault();
+
+      this.element_.removeEventListener('mousemove', drag);
+      this.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);
+
+      const slide = getSlide_(targetElement);
+      setFocus_(slide);
+      this.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) {
+    const 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(IS_FOCUSED);
+    }
+  };
+
+  /**
+   * Handle blur
+   * @param event
+   * @private
+   */
+  MaterialExtCarousel.prototype.blurHandler_ = function(event) {
+    const slide = getSlide_(event.target);
+    if(slide) {
+      slide.classList.remove(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);
+
+      const 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() {
+    const carouselRect = this.element_.getBoundingClientRect();
+
+    const slidesInViewport = [...this.element_.querySelectorAll(`.${SLIDE}`)].filter( slide => {
+      const 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) {
+    const carouselRect = this.element_.getBoundingClientRect();
+    const slideRect = slide.getBoundingClientRect();
+
+    if(slideRect.left < carouselRect.left) {
+      const x = this.element_.scrollLeft - (carouselRect.left - slideRect.left);
+      this.animateScroll_(x);
+    }
+    else if(slideRect.right > carouselRect.right) {
+      const 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) {
+      [...this.element_.querySelectorAll(`.${SLIDE}[aria-selected]`)].forEach(
+        slide => 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
+  const getSlide_ = element => {
+    return element.closest(`.${SLIDE}`);
+  };
+
+  const setFocus_ = slide => {
+    if(slide) {
+      slide.focus();
+    }
+  };
+
+  const addRipple_ = slide => {
+    if(!slide.querySelector(`.${MDL_RIPPLE_CONTAINER}`)) {
+      const rippleContainer = document.createElement('span');
+      rippleContainer.classList.add(MDL_RIPPLE_CONTAINER);
+      rippleContainer.classList.add(MDL_RIPPLE_EFFECT);
+      const ripple = document.createElement('span');
+      ripple.classList.add(MDL_RIPPLE);
+      rippleContainer.appendChild(ripple);
+
+      const img = slide.querySelector('img');
+      if (img) {
+        // rippleContainer blocks image title
+        rippleContainer.title = img.title;
+      }
+      slide.appendChild(rippleContainer);
+      componentHandler.upgradeElement(rippleContainer, 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() {
+
+    const hasRippleEffect = this.element_.classList.contains(MDL_RIPPLE_EFFECT);
+
+    [...this.element_.querySelectorAll(`.${SLIDE}`)].forEach( slide => {
+
+      slide.setAttribute('role', SLIDE_ROLE);
+
+      if(this.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_ = jsonStringToObject(this.element_.getAttribute('data-config'), this.config_);
+      }
+
+      // Wai-Aria
+      this.element_.setAttribute('role', ROLE);
+
+      // Prefer tabindex -1
+      if(!Number.isInteger(this.element_.getAttribute('tabindex'))) {
+        this.element_.setAttribute('tabindex', -1);
+      }
+
+      // Remove listeners, just in case ...
+      this.removeListeners_();
+
+      if(this.config_.interactive) {
+
+        // Ripple
+        const hasRippleEffect = this.element_.classList.contains(MDL_RIPPLE_EFFECT);
+        if (hasRippleEffect) {
+          this.element_.classList.add(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(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
+  });
+})();
diff --git a/node_modules/mdl-ext/src/carousel/readme.md b/node_modules/mdl-ext/src/carousel/readme.md
new file mode 100644
index 0000000..6485972
--- /dev/null
+++ b/node_modules/mdl-ext/src/carousel/readme.md
@@ -0,0 +1,271 @@
+# Carousel
+
+![Carousel](../../etc/carousel.png)
+
+A responsive image carousel.
+
+## Introduction
+The Material Design Lite Ext (MDLEXT) Carousel, commonly also referred to as “slide shows” or “sliders”, is a component 
+for cycling through a series of images. The carousel is defined and enclosed by a container element and 
+distributes images horizontally, with repect to the available container size. Images outside the container viewport slides
+into view, triggerd by a user action or by running an animation loop (rAF). 
+
+This component does not attempt in any way to resemble a three-dimensional image carousel that is used to show slides 
+from a projector. The component is perceived more as a slider, but the terms slider and carousel, are often used 
+interchangeably.
+
+### Features:
+* Navigate carousel using keyboard (arrow keys, tab, pgup, pgdown, home, end), mouse drag, touch events, or by sending custom events to the carousel (first, scroll-prev, prev, next, scroll-next, last, play, pause)
+* Select a particular image  using enter or space key, or by clicking an image 
+* Cycle images at a given interval - a slideshow
+* Set slideshow interval via a data attribute or as a part of the play custom event
+* Stop slideshow via custom event (pause) or by a user interaction, e.g clicking an image
+* User interactions via keyboard, mouse or touch events may be blocked, if configured 
+* Start slideshow at component initialization using a data attribute
+* The carousel emits custom events reflecting a user action. E.g. clicking an image will emit a 'select' event with a detail object holding a reference to the selected image.
+
+### Limitations:
+* The carousel should pause any running animation on window.bur or tab.blur - not implemented
+* The carousel should pause any running animation when the carousel is not in window viewport - not implemented
+* Only horizontal layout in first release
+
+
+### To include a MDLEXT **carousel** component:
+&nbsp;1. Code a block element, e.g. a `<div>` element, to hold dimensions of the carousel. 
+```html
+<div style="height: 200px; width: 100%;">
+</div>
+```
+
+&nbsp;2. Code a `<ul>` element with `class="mdlext-carousel mdlext-js-carousel"` to hold the carousel. 
+```html
+<div style="height: 200px; width: 100%;">
+  <ul class="mdlext-carousel mdlext-js-carousel">
+  <ul>
+</div>
+```
+
+&nbsp;3. Code a `<li>` element with `class="mdlext-carousel__slide"`  to hold an individual image (thumbnail). 
+```html
+<div style="height: 200px; width: 100%;">
+  <ul class="mdlext-carousel mdlext-js-carousel">
+    <li class="mdlext-carousel__slide">
+    <li>
+  <ul>
+</div>
+```
+
+&nbsp;4. Code a `<figure>` element to hold the image and the image title.  
+```html
+<div style="height: 200px; width: 100%;">
+  <ul class="mdlext-carousel mdlext-js-carousel">
+    <li class="mdlext-carousel__slide">
+      <figure>
+      </figure>
+    <li>
+  <ul>
+</div>
+```
+
+&nbsp;5. Inside the `<figure>` element add an `<img>` element with reference to the thumbnail image to be shown. Optionally add a `<figcaption>` element to hold the image title.    
+```html
+<div style="height: 200px; width: 100%;">
+  <ul class="mdlext-carousel mdlext-js-carousel">
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="_D802591.jpg" title="Whooper swans in flight"/>
+        <figcaption>_D802591.jpg</figcaption>
+      </figure>
+    <li>
+  <ul>
+</div>
+```
+
+&nbsp;6. Repeat steps 3..5 for each slide required.
+
+### Examples
+* See: [snippets/carousel.html](./snippets/carousel.html)
+* Or try out the [live demo](http://leifoolsen.github.io/mdl-ext/demo/carousel.html)
+
+## Interactions
+
+### Keyboard interaction
+The carousel interacts with the following keyboard keys.
+
+*   `Tab` - When focus is on a slide, pressing the `Tab` key moves focus in the following manner:
+      *   If there is a next slide, focus moves to the next slide.
+      *   If focus is on the last slide, focus moves to the first focusable element outside the carousel component.
+*   `Shift+Tab` - Generally the reverse of `Tab`.
+*   `Left arrow` - Moves focus to the previous slide. If the current slide is the first slide, focus stays on that slide.
+*   `Right arrow` - Moves focus to the next slide. If the current slide is the last slide, focus stays on that slide.
+*   `Up arrow` - behaves the same as left arrow.
+*   `Down arrow` - behaves the same as right arrow.
+*   `End` - When focus is on a slide, an `End` key press moves focus to the last slide.
+*   `Home` - When focus is on a slide, a `Home` key press moves focus to the first slide.
+*   `Enter/Space` - When focus is on a slide, pressing `Enter` or `Space` selects the focused slide.
+
+### Mouse / Touch interaction
+*   `Drag or Swipe left` - Move slides outside container viewport into view.
+*   `Drag or Swipe right` - Move slides outside container viewport into view
+
+
+## Component configuration
+The component can be configured using a `data-config` attribute. The attribute value is a JSON string with the following properties.
+
+| Property        |    |    |
+|-----------------|----|----|
+| `interactive`   | if `true`, the user can use keyboard or mouse to navigate the slides | default: `true` |
+| `autostart`     | if `true`, the slideshow starts immediately after component initialization | default: `false` |
+| `type`          | animation type, `'slide'`, advances one slide,  `'scroll'`, moves next sequence of slides into view | default `'slide'` |
+| `interval`      | animation interval, in milliseconds | default `1000` |
+
+
+The `data-config` attribute must be a valid JSON string. You can use single or double quotes for the JSON properties. 
+
+Example 1, single quotes in JSON config string:
+```html
+<ul class="mdlext-carousel mdlext-js-carousel mdl-js-ripple-effect mdl-js-ripple-effect--ignore-events" 
+  data-config="{ 'interactive': true, 'autostart': false, 'type': 'slide', 'interval': 2000 }">
+  ......
+</ul>
+```
+
+Example 2, double quotes in JSON config string:
+```html
+<ul class="mdlext-carousel mdlext-js-carousel" 
+  data-config='{ "interactive": false, "autostart": true, "type": "scroll", "interval": 5000 }'>
+  ......
+</ul>
+```
+
+## Events
+Interaction with the component programmatically is performed by sending events to the component, and receive responses 
+from the component.  
+
+### Events the component listenes to
+A client can send a `command` custom event to the carousel. The command event holds an action detail object defining 
+the action to perform.
+
+```javascript
+new CustomEvent('command', { detail: { action : 'first' } });
+new CustomEvent('command', { detail: { action : 'scroll-prev' } });
+new CustomEvent('command', { detail: { action : 'prev' } });
+new CustomEvent('command', { detail: { action : 'next' } });
+new CustomEvent('command', { detail: { action : 'scroll-next' } });
+new CustomEvent('command', { detail: { action : 'last' } });
+new CustomEvent('command', { detail: 
+  { 
+    action : 'play', 
+    interval: 3000   // Interval is optional, overrides value set by 'data-config'
+  } 
+}); 
+new CustomEvent('command', { detail: { action : 'pause' } });
+
+// Trigger the event
+myCarousel.dispatchEvent(ev);
+```
+
+Refer to [snippets/lightbox.html](./snippets/carousel.html) for usage.
+
+### Events emitted
+When a user interacts with the component, or the component receives a `command` custom event, the component responds
+with a `select` custom event reflecting the action performed and a detail object holding the selected slide element.
+
+The `select` detail object has the following format:
+
+```javascript
+detail: {
+  command, // The command executed (`first`, `scroll-prev`, `prev`, `next`, `scroll-next`, `last`) 
+  keyCode, // Key pressed, if any 
+  source   // The element that caused the event
+}
+```
+
+Set up a `select` listener.
+```javascript
+document.querySelector('#my-carousel').addEventListener('select', function(e) {
+  var selectedElement = e.detail.source;
+  console.log('Selected element', selectedElement);
+  var selectImage = selectedElement.querySelector('img');
+});
+
+```
+Refer to [snippets/lightbox.html](./snippets/carousel.html) for usage.
+
+
+## Public methods
+
+### `stopAnimation()`
+
+Stops animation - if any.
+
+### `upgradeSlides()`
+Upgrade slides. If you add slides to the carousel after the page has loaded, you must call `upgradeSlides` to 
+notify the component about the newly inserted slides.
+
+```javascript
+myCarousel = document.querySelector('#my-carousel');
+myCarousel.MaterialExtCarousel.upgradeSlides();
+```
+
+### `getConfig()`
+Returns the `config` object.
+
+
+## Configuration options
+The MDLEXT CSS classes apply various predefined visual and behavioral enhancements to the carousel.
+The table below lists the available classes and their effects.
+
+| MDLEXT class | Effect | Remarks |
+|--------------|--------|---------|
+| `mdlext-carousel` | Defines a container as an MDLEXT carousel component | Required on `<ul>` element |
+| `mdlext-js-carousel` | Assigns basic MDL behavior to carousel | Required on `<ul>` element |
+| `mdlext-carousel__slide` | Defines a carousel slide | Required on `<li>` element |
+
+Attributes.
+
+| Attribute | Effect | Remarks |
+|-----------|--------|---------|
+| `data-config` | A JSON object defining startup configurations |  |
+| `aria-selected` | The selected `mdlext-carousel__slide` element | Only one element can be selected at the same time |
+| `list` | The component add the role `list` to self |  |
+| `listitem` | The component add the role `listitem` to `mdlext-carousel__slide` items |  |
+
+
+## Note for single page applications
+If you use Material Design Lite in a dynamic page, e.g. a single page application, any running animations must be 
+stopped before a page frament containing a carousel component is removed from the DOM. Call 
+`componentHandler.downgradeElements` to stop any running animation and clean up component resources. 
+In a static web application there should be no need to call `componentHandler.downgradeElements`.
+
+The following code snippet demonstrates how to properly clean up components before removing them from DOM.
+
+```javascript
+// Call 'componentHandler.downgradeElements' to clean up
+const content = document.querySelector('#content');
+const components = content.querySelectorAll('.is-upgraded');
+componentHandler.downgradeElements([...components]);
+
+// Remove elements from DOM.
+// See: http://jsperf.com/empty-an-element/16
+const removeChildElements = (element, forceReflow = true) => {
+  while (element.lastChild) {
+    element.removeChild(element.lastChild);
+  }
+  if(forceReflow) {
+    // See: http://jsperf.com/force-reflow
+    const d = element.style.display;
+    element.style.display = 'none';
+    element.style.display = d;
+  }
+}
+
+removeChildElements(content); 
+```
+
+## How to use the component programmatically
+The [tests](../../test/carousel/carousel.spec.js) and the [snippets/lightbox.html](./snippets/carousel.html) 
+code provides examples on how to use the component programmatically.
+
+## Reference
+[WCAG Carousel Concepts](https://www.w3.org/WAI/tutorials/carousels/)
diff --git a/node_modules/mdl-ext/src/carousel/snippets/carousel.html b/node_modules/mdl-ext/src/carousel/snippets/carousel.html
new file mode 100644
index 0000000..ac51213
--- /dev/null
+++ b/node_modules/mdl-ext/src/carousel/snippets/carousel.html
@@ -0,0 +1,612 @@
+<p><strong>Note:</strong> Does not work as expected in IE11</p>
+
+<style>
+  .carousel-demo {
+    box-sizing: border-box;
+    display: block;
+    padding: 4px;
+    border: 1px solid #dddddd;
+    border-radius: 4px;
+    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+  }
+
+  .carousel-demo * {
+    box-sizing: border-box;
+  }
+
+  #carousel-imgviewer {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-pack: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+  }
+
+  #carousel-imgviewer figure {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-box-direction: normal;
+    -ms-flex-direction: column;
+    flex-direction: column;
+    -webkit-box-pack: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+
+    position: relative;
+    height: 100%;
+    padding: 0;
+    margin: 0;
+  }
+
+  #carousel-imgviewer figure img {
+    width: auto;
+    max-width: 100%;
+    max-height: 100%;
+    border: 0;
+    outline: 0;
+
+    -webkit-animation: fade-in-element 0.25s ease-out;
+    -moz-animation: fade-in-element 0.25s ease-out;
+    -o-animation: fade-in-element 025s ease-out;
+    animation: fade-in-element 0.25s ease-out;
+  }
+
+  #carousel-footer {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-pack: justify;
+    -ms-flex-pack: justify;
+    justify-content: space-between;
+    -webkit-box-align: center;
+    -ms-flex-align: center;
+    align-items: center;
+  }
+
+  #carousel-footer .mdl-card__supporting-text {
+    -webkit-box-flex: 1;
+    -ms-flex: 1;
+    flex: 1;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    padding: 0;
+    width: 100%;
+  }
+
+  #carousel-footer nav {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+  }
+
+  .carousel-button {
+  }
+  .carousel-button__icon {
+    font-size: 24px;
+  }
+
+  #carousel-container {
+  }
+
+  @media (max-width: 479px) {
+    #carousel-imgviewer {
+      height: 260px;
+    }
+    .carousel-button {
+      width: 28px;
+      min-width: 28px;
+      height: 28px;
+    }
+    .carousel-button__icon {
+      font-size: 20px;
+    }
+    #carousel-container {
+      height: 60px;
+      margin-top: 4px;
+    }
+  }
+
+  @media (min-width: 480px) and (max-width: 839px) {
+    #carousel-imgviewer {
+      height: 360px;
+    }
+    #carousel-container {
+      height: 90px;
+    }
+  }
+
+  @media (min-width: 840px) {
+    #carousel-imgviewer {
+      height: 500px;
+    }
+    #carousel-container {
+      height: 110px;
+    }
+  }
+
+  @-webkit-keyframes fade-in-element {
+    0%   { opacity: 0; }
+    100% { opacity: 1; }
+  }
+  @-moz-keyframes fade-in-element {
+    0%   { opacity: 0; }
+    100% { opacity: 1; }
+  }
+  @-o-keyframes fade-in-element {
+    0%   { opacity: 0; }
+    100% { opacity: 1; }
+  }
+  @keyframes fade-in-element {
+    0%   { opacity: 0; }
+    100% { opacity: 1; }
+  }
+
+</style>
+
+<artichle class="carousel-demo" style="height: 64px; margin-bottom: 16px; padding-top: 1px;">
+  <ul class="mdlext-carousel mdlext-js-carousel"
+      data-config="{ 'interactive': false, 'autostart': true, 'type': 'scroll', 'interval': 5000 }">
+
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D802141.jpg" title="Northern goshawk with prey"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D802143.jpg" title="Northern goshawk with prey"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D802591.jpg" title="Whooper swans in flight"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D804370-3.jpg" title="European green woodpecker"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D808689.jpg" title="The bridge"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D802181.jpg" title="Landscape in blue pastel"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+     <figure>
+        <img src="./images/_D800912.jpg" title="Hiking the mountains of Dovre"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D809453-_D809457-4.jpg" title="The Polar Express. End of Line. Ny Aalesund, Spitsbergen" />
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_DSC8214.jpg" title="Still got the blues"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800017.jpg" title="Flowers"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800023.jpg" title="Red-breasted merganser"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800851.jpg" title="Musk oxes, Dovre, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800166.jpg" title="Arctic Fox, Svalbard, Norway" />
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800951.jpg" title="Fly fishing the arctic waters, Svalbard, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D801188.jpg" title="Lady of the snows (Pulsatilla vernalis), Dovre, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D801205-2.jpg" title="PULSE, Kilden Consert Hall, Kristiansand"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D801274.jpg" title="PULSE, Kilden Consert Hall, Kristiansand"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D801392.jpg" title="Peregrine falcon, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D801436.jpg" title="Peregrine falcon, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D801952-4.jpg" title="Mr. Per E Knudsen"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D807603.jpg" title="Black Woodpecker"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D807689.jpg" title="Goshina"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D807558.jpg" title="Goshina"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800464.jpg" title="Svalbard Rock ptarmigan"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_DSC7535.jpg" title="Nice, France"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D802478.jpg" title="Cheetah, Bloemfontain, South Africa"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D800698.jpg" title="Red Squirrel"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D803118.jpg" title="Milky Way, Bloemfontain, South Africa"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D803521.jpg" title="Winter Light, Senja, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D803465-3.jpg" title="Selfie with Aurora B :)"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D806374.jpg" title="Lista Lighthouse, Norway"/>
+      </figure>
+    </li>
+    <li class="mdlext-carousel__slide">
+      <figure>
+        <img src="./images/_D805345-12.jpg" title="Osprey"/>
+      </figure>
+    </li>
+  </ul>
+</artichle>
+
+
+
+
+<artichle class="carousel-demo">
+
+  <section id="carousel-imgviewer">
+    <figure>
+      <img src="./images/_D802143-2.jpg" alt="" title=""/>
+    </figure>
+  </section>
+
+  <footer id="carousel-footer">
+    <div id="carousel-viewer-title" class="mdl-card__supporting-text">Northern goshawk with prey</div>
+    <nav>
+      <button id="carousel-btn-first" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Scroll First">
+        <i class="material-icons carousel-button__icon">first_page</i>
+      </button>
+      <button id="carousel-btn-scroll-prev" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Scroll Previous">
+        <i class="material-icons carousel-button__icon">fast_rewind</i>
+      </button>
+      <button id="carousel-btn-prev" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Previous">
+        <i class="material-icons carousel-button__icon">navigate_before</i>
+      </button>
+      <button id="carousel-btn-play-pause" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Play">
+        <i class="material-icons carousel-button__icon">play_circle_outline</i>
+      </button>
+      <button id="carousel-btn-next" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Next">
+        <i class="material-icons carousel-button__icon">navigate_next</i>
+      </button>
+      <button id="carousel-btn-scroll-next" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Scroll Next">
+        <i class="material-icons carousel-button__icon">fast_forward</i>
+      </button>
+      <button id="carousel-btn-last" class="mdl-button mdl-button--icon mdl-js-button carousel-button" title="Scroll Last">
+        <i class="material-icons carousel-button__icon">last_page</i>
+      </button>
+    </nav>
+  </footer>
+
+  <section id="carousel-container">
+    <ul id="mdlext-carousel-demo2" class="mdlext-carousel mdlext-js-carousel mdl-js-ripple-effect mdl-js-ripple-effect--ignore-events">
+      <li class="mdlext-carousel__slide" aria-selected >
+        <a href="./images/_D802143-2.jpg">
+          <figure>
+            <img src="./images/_D802143.jpg" title="Northern goshawk with prey"/>
+            <figcaption>_D802143.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_D802591-2.jpg">
+          <figure>
+            <img src="./images/_D802591.jpg" title="Whooper swans in flight"/>
+            <figcaption>_D802591.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D802181.jpg" title="Landscape in blue pastel"/>
+          <figcaption>_D802181.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_D809453-_D809457-3.jpg">
+          <figure>
+            <img src="./images/_D809453-_D809457-4.jpg" title="The Polar Express. End of Line. Ny Aalesund, Spitsbergen" />
+            <figcaption>_D809453</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_DSC8214-2.jpg">
+          <figure>
+            <img src="./images/_DSC8214.jpg" title="Still got the blues"/>
+            <figcaption>_DSC8214.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_D800166-2.jpg">
+          <figure>
+            <img src="./images/_D800166.jpg" title="Arctic Fox, Svalbard, Norway" />
+            <figcaption>_D800166.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_D801392-2.jpg">
+          <figure>
+            <img src="./images/_D801392.jpg" title="Peregrine falcon, Norway"/>
+            <figcaption>_D801392.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_D801436-2.jpg">
+          <figure>
+            <img src="./images/_D801436.jpg" title="Peregrine falcon, Norway"/>
+            <figcaption>_D801436.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D807558.jpg" title="Goshina"/>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_D800464.jpg">
+          <figure>
+            <img src="./images/_D800464.jpg" title="Svalbard Rock ptarmigan"/>
+            <figcaption>_D800464.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <a href="./images/_DSC7535-2.jpg">
+          <figure>
+            <img src="./images/_DSC7535.jpg" title="Nice, France"/>
+            <figcaption>_DSC7535.jpg</figcaption>
+          </figure>
+        </a>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D802478.jpg" title="Cheetah, Bloemfontain, South Africa"/>
+          <figcaption>_D802478.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D800698.jpg" title="Red Squirrel"/>
+          <figcaption>_D800698.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D803118.jpg" title="Milky Way, Bloemfontain, South Africa"/>
+          <figcaption>_D803118.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D803521.jpg" title="Winter Light, Senja, Norway"/>
+          <figcaption>_D803521.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D803465-3.jpg" title="Selfie with Aurora B :)"/>
+          <figcaption>_D803465.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D806374.jpg" title="Lista Lighthouse, Norway"/>
+          <figcaption>_D806374.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D801087.jpg" title="Brokke, Norway"/>
+          <figcaption>_D801087.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D803221.jpg" title="Mnt. Seglan, Senja, Norway"/>
+          <figcaption>_D803221.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D803759.jpg" title="Fruit bat, Mauritius"/>
+          <figcaption>_D803759.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D809758-2.jpg" title="Polar bear, Svalbard, Norway"/>
+          <figcaption>_D809758.jpg</figcaption>
+        </figure>
+      </li>
+      <li class="mdlext-carousel__slide">
+        <figure>
+          <img src="./images/_D805345-12.jpg" title="Osprey"/>
+          <figcaption>_D805345.jpg</figcaption>
+        </figure>
+      </li>
+    </ul>
+  </section>
+</artichle>
+
+
+<div style="margin-top: 8px">
+  <button id="btn-add-image" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--raised mdl-button--colored">
+    Add image to carousel
+  </button>
+  <p class="mdl-typography--caption" style="margin-top: 8px">
+    Click the button to add a new image to the carousel. Move to the inserted
+    image and click it. It should have ripple effect if upgraded correctly.
+  </p>
+</div>
+
+<p class="mdl-typography--caption" style="margin-top: 32px;">
+  All images appearing in this page are the exclusive property of Leif Olsen and are protected under the United States
+  and International Copyright laws. The images may not be reproduced or manipulated without the written permission of
+  Leif Olsen. Use of any image as the basis for another photographic concept or illustration (digital, artist rendering
+  or alike) is a violation of the United States and International Copyright laws. All images are copyrighted &copy; Leif Olsen, 2016.
+</p>
+
+
+<script>
+  (function() {
+    'use strict';
+
+    window.addEventListener('load', function() {
+
+      document.querySelector('#carousel-btn-first').addEventListener('click', function (e) {
+        var ev = new CustomEvent('command', {detail: {action: 'first'}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#carousel-btn-scroll-prev').addEventListener('click', function (e) {
+        var ev = new CustomEvent('command', {detail: {action: 'scroll-prev'}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#carousel-btn-prev').addEventListener('click', function (e) {
+        var ev = new CustomEvent('command', {detail: {action: 'prev'}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#carousel-btn-next').addEventListener('click', function (e) {
+        var ev = new CustomEvent('command', {detail: {action: 'next'}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#carousel-btn-scroll-next').addEventListener('click', function (e) {
+        var ev = new CustomEvent('command', {detail: {action: 'scroll-next'}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#carousel-btn-last').addEventListener('click', function (e) {
+        var ev = new CustomEvent('command', {detail: {action: 'last'}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#carousel-btn-play-pause').addEventListener('click', function (e) {
+        // Toggle play icon
+        var i = this.querySelector('i');
+        var action = i.innerText === 'play_circle_outline' ? 'play' : 'pause';
+        i.textContent = action === 'play' ? 'pause_circle_outline' : 'play_circle_outline';
+
+        var ev = new CustomEvent('command', {detail: {action: action, interval: 3000}});
+        document.querySelector('#mdlext-carousel-demo2').dispatchEvent(ev);
+      });
+
+      document.querySelector('#mdlext-carousel-demo2').addEventListener('select', function (e) {
+
+        if ('pause' === e.detail.command) {
+          // Set play icon
+          var i = document.querySelector('#carousel-btn-play-pause i');
+          i.textContent = 'play_circle_outline';
+        }
+        else {
+          var oldImage = document.querySelector('#carousel-imgviewer img');
+          var selectImage = e.detail.source.querySelector('img');
+          var selectImageSrc = selectImage.src;
+          if (e.detail.source.querySelector('a')) {
+            selectImageSrc = e.detail.source.querySelector('a').href;
+          }
+
+          if (selectImageSrc !== oldImage.src) {
+            var newImage = oldImage.cloneNode(true);
+            newImage.src = selectImageSrc;
+            oldImage.parentNode.replaceChild(newImage, oldImage);
+
+            var title = document.querySelector('#carousel-viewer-title');
+            title.textContent = selectImage.title;
+          }
+        }
+      });
+
+      document.querySelector('#btn-add-image').addEventListener('click', function (e) {
+        var slide_fragment = '<li class="mdlext-carousel__slide"><figure><img src="./images/_D809914-2.jpg" alt="Humpback whale" title="Humpback whale"/></figure></li>';
+        var carousel = document.querySelector('#mdlext-carousel-demo2');
+        carousel.insertAdjacentHTML('beforeend', slide_fragment);
+        carousel.MaterialExtCarousel.upgradeSlides();
+      });
+
+    });
+
+  }());
+
+</script>