diff --git a/css/impress-common.css b/website/demo/css/impress-common.css
similarity index 100%
rename from css/impress-common.css
rename to website/demo/css/impress-common.css
diff --git a/css/impress-demo.css b/website/demo/css/impress-demo.css
similarity index 100%
rename from css/impress-demo.css
rename to website/demo/css/impress-demo.css
diff --git a/examples/2D-navigation/css/fonts.css b/website/demo/examples/2D-navigation/css/fonts.css
similarity index 100%
rename from examples/2D-navigation/css/fonts.css
rename to website/demo/examples/2D-navigation/css/fonts.css
diff --git a/examples/2D-navigation/css/presentation.css b/website/demo/examples/2D-navigation/css/presentation.css
similarity index 100%
rename from examples/2D-navigation/css/presentation.css
rename to website/demo/examples/2D-navigation/css/presentation.css
diff --git a/examples/2D-navigation/images/35535918670_f1d12627ff_o.png b/website/demo/examples/2D-navigation/images/35535918670_f1d12627ff_o.png
similarity index 100%
rename from examples/2D-navigation/images/35535918670_f1d12627ff_o.png
rename to website/demo/examples/2D-navigation/images/35535918670_f1d12627ff_o.png
diff --git a/examples/2D-navigation/images/6296334551_b3d5c27823_b.png b/website/demo/examples/2D-navigation/images/6296334551_b3d5c27823_b.png
similarity index 100%
rename from examples/2D-navigation/images/6296334551_b3d5c27823_b.png
rename to website/demo/examples/2D-navigation/images/6296334551_b3d5c27823_b.png
diff --git a/examples/2D-navigation/images/6636957665_5e7c4a79de_o.png b/website/demo/examples/2D-navigation/images/6636957665_5e7c4a79de_o.png
similarity index 100%
rename from examples/2D-navigation/images/6636957665_5e7c4a79de_o.png
rename to website/demo/examples/2D-navigation/images/6636957665_5e7c4a79de_o.png
diff --git a/examples/2D-navigation/index.html b/website/demo/examples/2D-navigation/index.html
similarity index 100%
rename from examples/2D-navigation/index.html
rename to website/demo/examples/2D-navigation/index.html
diff --git a/examples/3D-positions/index.html b/website/demo/examples/3D-positions/index.html
similarity index 100%
rename from examples/3D-positions/index.html
rename to website/demo/examples/3D-positions/index.html
diff --git a/examples/3D-rotations/css/3D-rotations.css b/website/demo/examples/3D-rotations/css/3D-rotations.css
similarity index 100%
rename from examples/3D-rotations/css/3D-rotations.css
rename to website/demo/examples/3D-rotations/css/3D-rotations.css
diff --git a/examples/3D-rotations/css/fonts.css b/website/demo/examples/3D-rotations/css/fonts.css
similarity index 100%
rename from examples/3D-rotations/css/fonts.css
rename to website/demo/examples/3D-rotations/css/fonts.css
diff --git a/examples/3D-rotations/index.html b/website/demo/examples/3D-rotations/index.html
similarity index 100%
rename from examples/3D-rotations/index.html
rename to website/demo/examples/3D-rotations/index.html
diff --git a/examples/classic-slides/css/classic-slides.css b/website/demo/examples/classic-slides/css/classic-slides.css
similarity index 100%
rename from examples/classic-slides/css/classic-slides.css
rename to website/demo/examples/classic-slides/css/classic-slides.css
diff --git a/examples/classic-slides/css/fonts.css b/website/demo/examples/classic-slides/css/fonts.css
similarity index 100%
rename from examples/classic-slides/css/fonts.css
rename to website/demo/examples/classic-slides/css/fonts.css
diff --git a/examples/classic-slides/images/3476636111_c551295ca4_b.jpg b/website/demo/examples/classic-slides/images/3476636111_c551295ca4_b.jpg
similarity index 100%
rename from examples/classic-slides/images/3476636111_c551295ca4_b.jpg
rename to website/demo/examples/classic-slides/images/3476636111_c551295ca4_b.jpg
diff --git a/examples/classic-slides/images/background-title.png b/website/demo/examples/classic-slides/images/background-title.png
similarity index 100%
rename from examples/classic-slides/images/background-title.png
rename to website/demo/examples/classic-slides/images/background-title.png
diff --git a/examples/classic-slides/images/background-title.svg b/website/demo/examples/classic-slides/images/background-title.svg
similarity index 100%
rename from examples/classic-slides/images/background-title.svg
rename to website/demo/examples/classic-slides/images/background-title.svg
diff --git a/examples/classic-slides/images/background.png b/website/demo/examples/classic-slides/images/background.png
similarity index 100%
rename from examples/classic-slides/images/background.png
rename to website/demo/examples/classic-slides/images/background.png
diff --git a/examples/classic-slides/images/background.svg b/website/demo/examples/classic-slides/images/background.svg
similarity index 100%
rename from examples/classic-slides/images/background.svg
rename to website/demo/examples/classic-slides/images/background.svg
diff --git a/examples/classic-slides/index.html b/website/demo/examples/classic-slides/index.html
similarity index 100%
rename from examples/classic-slides/index.html
rename to website/demo/examples/classic-slides/index.html
diff --git a/examples/cube/css/cube.css b/website/demo/examples/cube/css/cube.css
similarity index 100%
rename from examples/cube/css/cube.css
rename to website/demo/examples/cube/css/cube.css
diff --git a/examples/cube/css/fonts.css b/website/demo/examples/cube/css/fonts.css
similarity index 100%
rename from examples/cube/css/fonts.css
rename to website/demo/examples/cube/css/fonts.css
diff --git a/examples/cube/img/Henrik_mustache.jpg b/website/demo/examples/cube/img/Henrik_mustache.jpg
similarity index 100%
rename from examples/cube/img/Henrik_mustache.jpg
rename to website/demo/examples/cube/img/Henrik_mustache.jpg
diff --git a/examples/cube/index.html b/website/demo/examples/cube/index.html
similarity index 100%
rename from examples/cube/index.html
rename to website/demo/examples/cube/index.html
diff --git a/examples/index.html b/website/demo/examples/index.html
similarity index 100%
rename from examples/index.html
rename to website/demo/examples/index.html
diff --git a/examples/markdown/css/devopsy.css b/website/demo/examples/markdown/css/devopsy.css
similarity index 100%
rename from examples/markdown/css/devopsy.css
rename to website/demo/examples/markdown/css/devopsy.css
diff --git a/examples/markdown/css/effects.css b/website/demo/examples/markdown/css/effects.css
similarity index 100%
rename from examples/markdown/css/effects.css
rename to website/demo/examples/markdown/css/effects.css
diff --git a/examples/markdown/css/fonts.css b/website/demo/examples/markdown/css/fonts.css
similarity index 100%
rename from examples/markdown/css/fonts.css
rename to website/demo/examples/markdown/css/fonts.css
diff --git a/examples/markdown/css/markdown-slides.css b/website/demo/examples/markdown/css/markdown-slides.css
similarity index 100%
rename from examples/markdown/css/markdown-slides.css
rename to website/demo/examples/markdown/css/markdown-slides.css
diff --git a/examples/markdown/images/3476636111_c551295ca4_b.jpg b/website/demo/examples/markdown/images/3476636111_c551295ca4_b.jpg
similarity index 100%
rename from examples/markdown/images/3476636111_c551295ca4_b.jpg
rename to website/demo/examples/markdown/images/3476636111_c551295ca4_b.jpg
diff --git a/examples/markdown/index.html b/website/demo/examples/markdown/index.html
similarity index 100%
rename from examples/markdown/index.html
rename to website/demo/examples/markdown/index.html
diff --git a/index.html b/website/demo/index.html
similarity index 100%
rename from index.html
rename to website/demo/index.html
diff --git a/website/demo/js/impress.js b/website/demo/js/impress.js
new file mode 100644
index 0000000..6a865c8
--- /dev/null
+++ b/website/demo/js/impress.js
@@ -0,0 +1,4978 @@
+// This file was automatically generated from files in src/ directory.
+
+/*! Licensed under MIT License - http://github.com/impress/impress.js */
+/**
+ * impress.js
+ *
+ * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
+ * in modern browsers and inspired by the idea behind prezi.com.
+ *
+ *
+ * Copyright 2011-2012 Bartek Szopka (@bartaz), 2016-2020 Henrik Ingo (@henrikingo)
+ *
+ * Released under the MIT License.
+ *
+ * ------------------------------------------------
+ * author: Bartek Szopka, Henrik Ingo
+ * version: 1.1.0
+ * url: http://impress.js.org
+ * source: http://github.com/impress/impress.js/
+ */
+
+// You are one of those who like to know how things work inside?
+// Let me show you the cogs that make impress.js run...
+( function( document, window ) {
+ "use strict";
+ var lib;
+
+ // HELPER FUNCTIONS
+
+ // `pfx` is a function that takes a standard CSS property name as a parameter
+ // and returns it's prefixed version valid for current browser it runs in.
+ // The code is heavily inspired by Modernizr http://www.modernizr.com/
+ var pfx = ( function() {
+
+ var style = document.createElement( "dummy" ).style,
+ prefixes = "Webkit Moz O ms Khtml".split( " " ),
+ memory = {};
+
+ return function( prop ) {
+ if ( typeof memory[ prop ] === "undefined" ) {
+
+ var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+ props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
+
+ memory[ prop ] = null;
+ for ( var i in props ) {
+ if ( style[ props[ i ] ] !== undefined ) {
+ memory[ prop ] = props[ i ];
+ break;
+ }
+ }
+
+ }
+
+ return memory[ prop ];
+ };
+
+ } )();
+
+ var validateOrder = function( order, fallback ) {
+ var validChars = "xyz";
+ var returnStr = "";
+ if ( typeof order === "string" ) {
+ for ( var i in order.split( "" ) ) {
+ if ( validChars.indexOf( order[ i ] ) >= 0 ) {
+ returnStr += order[ i ];
+
+ // Each of x,y,z can be used only once.
+ validChars = validChars.split( order[ i ] ).join( "" );
+ }
+ }
+ }
+ if ( returnStr ) {
+ return returnStr;
+ } else if ( fallback !== undefined ) {
+ return fallback;
+ } else {
+ return "xyz";
+ }
+ };
+
+ // `css` function applies the styles given in `props` object to the element
+ // given as `el`. It runs all property names through `pfx` function to make
+ // sure proper prefixed version of the property is used.
+ var css = function( el, props ) {
+ var key, pkey;
+ for ( key in props ) {
+ if ( props.hasOwnProperty( key ) ) {
+ pkey = pfx( key );
+ if ( pkey !== null ) {
+ el.style[ pkey ] = props[ key ];
+ }
+ }
+ }
+ return el;
+ };
+
+ // `translate` builds a translate transform string for given data.
+ var translate = function( t ) {
+ return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
+ };
+
+ // `rotate` builds a rotate transform string for given data.
+ // By default the rotations are in X Y Z order that can be reverted by passing `true`
+ // as second parameter.
+ var rotate = function( r, revert ) {
+ var order = r.order ? r.order : "xyz";
+ var css = "";
+ var axes = order.split( "" );
+ if ( revert ) {
+ axes = axes.reverse();
+ }
+
+ for ( var i = 0; i < axes.length; i++ ) {
+ css += " rotate" + axes[ i ].toUpperCase() + "(" + r[ axes[ i ] ] + "deg)";
+ }
+ return css;
+ };
+
+ // `scale` builds a scale transform string for given data.
+ var scale = function( s ) {
+ return " scale(" + s + ") ";
+ };
+
+ // `computeWindowScale` counts the scale factor between window size and size
+ // defined for the presentation in the config.
+ var computeWindowScale = function( config ) {
+ var hScale = window.innerHeight / config.height,
+ wScale = window.innerWidth / config.width,
+ scale = hScale > wScale ? wScale : hScale;
+
+ if ( config.maxScale && scale > config.maxScale ) {
+ scale = config.maxScale;
+ }
+
+ if ( config.minScale && scale < config.minScale ) {
+ scale = config.minScale;
+ }
+
+ return scale;
+ };
+
+ // CHECK SUPPORT
+ var body = document.body;
+ var impressSupported =
+
+ // Browser should support CSS 3D transtorms
+ ( pfx( "perspective" ) !== null ) &&
+
+ // And `classList` and `dataset` APIs
+ ( body.classList ) &&
+ ( body.dataset );
+
+ if ( !impressSupported ) {
+
+ // We can't be sure that `classList` is supported
+ body.className += " impress-not-supported ";
+ }
+
+ // GLOBALS AND DEFAULTS
+
+ // This is where the root elements of all impress.js instances will be kept.
+ // Yes, this means you can have more than one instance on a page, but I'm not
+ // sure if it makes any sense in practice ;)
+ var roots = {};
+
+ var preInitPlugins = [];
+ var preStepLeavePlugins = [];
+
+ // Some default config values.
+ var defaults = {
+ width: 1920,
+ height: 1080,
+ maxScale: 3,
+ minScale: 0,
+
+ perspective: 1000,
+
+ transitionDuration: 1000
+ };
+
+ // Configuration options
+ var config = null;
+
+ // It's just an empty function ... and a useless comment.
+ var empty = function() { return false; };
+
+ // IMPRESS.JS API
+
+ // And that's where interesting things will start to happen.
+ // It's the core `impress` function that returns the impress.js API
+ // for a presentation based on the element with given id ("impress"
+ // by default).
+ var impress = window.impress = function( rootId ) {
+
+ // If impress.js is not supported by the browser return a dummy API
+ // it may not be a perfect solution but we return early and avoid
+ // running code that may use features not implemented in the browser.
+ if ( !impressSupported ) {
+ return {
+ init: empty,
+ goto: empty,
+ prev: empty,
+ next: empty,
+ swipe: empty,
+ tear: empty,
+ lib: {}
+ };
+ }
+
+ rootId = rootId || "impress";
+
+ // If given root is already initialized just return the API
+ if ( roots[ "impress-root-" + rootId ] ) {
+ return roots[ "impress-root-" + rootId ];
+ }
+
+ // The gc library depends on being initialized before we do any changes to DOM.
+ lib = initLibraries( rootId );
+
+ body.classList.remove( "impress-not-supported" );
+ body.classList.add( "impress-supported" );
+
+ // Data of all presentation steps
+ var stepsData = {};
+
+ // Element of currently active step
+ var activeStep = null;
+
+ // Current state (position, rotation and scale) of the presentation
+ var currentState = null;
+
+ // Array of step elements
+ var steps = null;
+
+ // Scale factor of the browser window
+ var windowScale = null;
+
+ // Root presentation elements
+ var root = lib.util.byId( rootId );
+ var canvas = document.createElement( "div" );
+
+ var initialized = false;
+
+ // STEP EVENTS
+ //
+ // There are currently two step events triggered by impress.js
+ // `impress:stepenter` is triggered when the step is shown on the
+ // screen (the transition from the previous one is finished) and
+ // `impress:stepleave` is triggered when the step is left (the
+ // transition to next step just starts).
+
+ // Reference to last entered step
+ var lastEntered = null;
+
+ // `onStepEnter` is called whenever the step element is entered
+ // but the event is triggered only if the step is different than
+ // last entered step.
+ // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as
+ // after screen resize. In this case - more precisely, in any case - we trigger a
+ // `impress:steprefresh` event.
+ var onStepEnter = function( step ) {
+ if ( lastEntered !== step ) {
+ lib.util.triggerEvent( step, "impress:stepenter" );
+ lastEntered = step;
+ }
+ lib.util.triggerEvent( step, "impress:steprefresh" );
+ };
+
+ // `onStepLeave` is called whenever the currentStep element is left
+ // but the event is triggered only if the currentStep is the same as
+ // lastEntered step.
+ var onStepLeave = function( currentStep, nextStep ) {
+ if ( lastEntered === currentStep ) {
+ lib.util.triggerEvent( currentStep, "impress:stepleave", { next: nextStep } );
+ lastEntered = null;
+ }
+ };
+
+ // `initStep` initializes given step element by reading data from its
+ // data attributes and setting correct styles.
+ var initStep = function( el, idx ) {
+ var data = el.dataset,
+ step = {
+ translate: {
+ x: lib.util.toNumberAdvanced( data.x ),
+ y: lib.util.toNumberAdvanced( data.y ),
+ z: lib.util.toNumberAdvanced( data.z )
+ },
+ rotate: {
+ x: lib.util.toNumber( data.rotateX ),
+ y: lib.util.toNumber( data.rotateY ),
+ z: lib.util.toNumber( data.rotateZ || data.rotate ),
+ order: validateOrder( data.rotateOrder )
+ },
+ scale: lib.util.toNumber( data.scale, 1 ),
+ transitionDuration: lib.util.toNumber(
+ data.transitionDuration, config.transitionDuration
+ ),
+ el: el
+ };
+
+ if ( !el.id ) {
+ el.id = "step-" + ( idx + 1 );
+ }
+
+ stepsData[ "impress-" + el.id ] = step;
+
+ css( el, {
+ position: "absolute",
+ transform: "translate(-50%,-50%)" +
+ translate( step.translate ) +
+ rotate( step.rotate ) +
+ scale( step.scale ),
+ transformStyle: "preserve-3d"
+ } );
+ };
+
+ // Initialize all steps.
+ // Read the data-* attributes, store in internal stepsData, and render with CSS.
+ var initAllSteps = function() {
+ steps = lib.util.$$( ".step", root );
+ steps.forEach( initStep );
+ };
+
+ // Build configuration from root and defaults
+ var buildConfig = function() {
+ var rootData = root.dataset;
+ return {
+ width: lib.util.toNumber( rootData.width, defaults.width ),
+ height: lib.util.toNumber( rootData.height, defaults.height ),
+ maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),
+ minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),
+ perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),
+ transitionDuration: lib.util.toNumber(
+ rootData.transitionDuration, defaults.transitionDuration
+ )
+ };
+ };
+
+ // `init` API function that initializes (and runs) the presentation.
+ var init = function() {
+ if ( initialized ) { return; }
+
+ // Initialize the configuration object, so it can be used by pre-init plugins.
+ config = buildConfig();
+ execPreInitPlugins( root );
+
+ // First we set up the viewport for mobile devices.
+ // For some reason iPad goes nuts when it is not done properly.
+ var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" );
+ meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
+ if ( meta.parentNode !== document.head ) {
+ meta.name = "viewport";
+ document.head.appendChild( meta );
+ }
+
+ windowScale = computeWindowScale( config );
+
+ // Wrap steps with "canvas" element
+ lib.util.arrayify( root.childNodes ).forEach( function( el ) {
+ canvas.appendChild( el );
+ } );
+ root.appendChild( canvas );
+
+ // Set initial styles
+ document.documentElement.style.height = "100%";
+
+ css( body, {
+ height: "100%",
+ overflow: "hidden"
+ } );
+
+ var rootStyles = {
+ position: "absolute",
+ transformOrigin: "top left",
+ transition: "all 0s ease-in-out",
+ transformStyle: "preserve-3d"
+ };
+
+ css( root, rootStyles );
+ css( root, {
+ top: "50%",
+ left: "50%",
+ perspective: ( config.perspective / windowScale ) + "px",
+ transform: scale( windowScale )
+ } );
+ css( canvas, rootStyles );
+
+ body.classList.remove( "impress-disabled" );
+ body.classList.add( "impress-enabled" );
+
+ // Get and init steps
+ initAllSteps();
+
+ // Set a default initial state of the canvas
+ currentState = {
+ translate: { x: 0, y: 0, z: 0 },
+ rotate: { x: 0, y: 0, z: 0, order: "xyz" },
+ scale: 1
+ };
+
+ initialized = true;
+
+ lib.util.triggerEvent( root, "impress:init",
+ { api: roots[ "impress-root-" + rootId ] } );
+ };
+
+ // `getStep` is a helper function that returns a step element defined by parameter.
+ // If a number is given, step with index given by the number is returned, if a string
+ // is given step element with such id is returned, if DOM element is given it is returned
+ // if it is a correct step element.
+ var getStep = function( step ) {
+ if ( typeof step === "number" ) {
+ step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
+ } else if ( typeof step === "string" ) {
+ step = lib.util.byId( step );
+ }
+ return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
+ };
+
+ // Used to reset timeout for `impress:stepenter` event
+ var stepEnterTimeout = null;
+
+ // `goto` API function that moves to step given as `el` parameter (by index, id or element).
+ // `duration` optionally given as second parameter, is the transition duration in css.
+ // `reason` is the string "next", "prev" or "goto" (default) and will be made available to
+ // preStepLeave plugins.
+ // `origEvent` may contain event that caused the call to goto, such as a key press event
+ var goto = function( el, duration, reason, origEvent ) {
+ reason = reason || "goto";
+ origEvent = origEvent || null;
+
+ if ( !initialized ) {
+ return false;
+ }
+
+ // Re-execute initAllSteps for each transition. This allows to edit step attributes
+ // dynamically, such as change their coordinates, or even remove or add steps, and have
+ // that change apply when goto() is called.
+ initAllSteps();
+
+ if ( !( el = getStep( el ) ) ) {
+ return false;
+ }
+
+ // Sometimes it's possible to trigger focus on first link with some keyboard action.
+ // Browser in such a case tries to scroll the page to make this element visible
+ // (even that body overflow is set to hidden) and it breaks our careful positioning.
+ //
+ // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
+ // whenever slide is selected
+ //
+ // If you are reading this and know any better way to handle it, I'll be glad to hear
+ // about it!
+ window.scrollTo( 0, 0 );
+
+ var step = stepsData[ "impress-" + el.id ];
+ duration = ( duration !== undefined ? duration : step.transitionDuration );
+
+ // If we are in fact moving to another step, start with executing the registered
+ // preStepLeave plugins.
+ if ( activeStep && activeStep !== el ) {
+ var event = { target: activeStep, detail: {} };
+ event.detail.next = el;
+ event.detail.transitionDuration = duration;
+ event.detail.reason = reason;
+ if ( origEvent ) {
+ event.origEvent = origEvent;
+ }
+
+ if ( execPreStepLeavePlugins( event ) === false ) {
+
+ // PreStepLeave plugins are allowed to abort the transition altogether, by
+ // returning false.
+ // see stop and substep plugins for an example of doing just that
+ return false;
+ }
+
+ // Plugins are allowed to change the detail values
+ el = event.detail.next;
+ step = stepsData[ "impress-" + el.id ];
+ duration = event.detail.transitionDuration;
+ }
+
+ if ( activeStep ) {
+ activeStep.classList.remove( "active" );
+ body.classList.remove( "impress-on-" + activeStep.id );
+ }
+ el.classList.add( "active" );
+
+ body.classList.add( "impress-on-" + el.id );
+
+ // Compute target state of the canvas based on given step
+ var target = {
+ rotate: {
+ x: -step.rotate.x,
+ y: -step.rotate.y,
+ z: -step.rotate.z,
+ order: step.rotate.order
+ },
+ translate: {
+ x: -step.translate.x,
+ y: -step.translate.y,
+ z: -step.translate.z
+ },
+ scale: 1 / step.scale
+ };
+
+ // Check if the transition is zooming in or not.
+ //
+ // This information is used to alter the transition style:
+ // when we are zooming in - we start with move and rotate transition
+ // and the scaling is delayed, but when we are zooming out we start
+ // with scaling down and move and rotation are delayed.
+ var zoomin = target.scale >= currentState.scale;
+
+ duration = lib.util.toNumber( duration, config.transitionDuration );
+ var delay = ( duration / 2 );
+
+ // If the same step is re-selected, force computing window scaling,
+ // because it is likely to be caused by window resize
+ if ( el === activeStep ) {
+ windowScale = computeWindowScale( config );
+ }
+
+ var targetScale = target.scale * windowScale;
+
+ // Trigger leave of currently active element (if it's not the same step again)
+ if ( activeStep && activeStep !== el ) {
+ onStepLeave( activeStep, el );
+ }
+
+ // Now we alter transforms of `root` and `canvas` to trigger transitions.
+ //
+ // And here is why there are two elements: `root` and `canvas` - they are
+ // being animated separately:
+ // `root` is used for scaling and `canvas` for translate and rotations.
+ // Transitions on them are triggered with different delays (to make
+ // visually nice and "natural" looking transitions), so we need to know
+ // that both of them are finished.
+ css( root, {
+
+ // To keep the perspective look similar for different scales
+ // we need to "scale" the perspective, too
+ // For IE 11 support we must specify perspective independent
+ // of transform.
+ perspective: ( config.perspective / targetScale ) + "px",
+ transform: scale( targetScale ),
+ transitionDuration: duration + "ms",
+ transitionDelay: ( zoomin ? delay : 0 ) + "ms"
+ } );
+
+ css( canvas, {
+ transform: rotate( target.rotate, true ) + translate( target.translate ),
+ transitionDuration: duration + "ms",
+ transitionDelay: ( zoomin ? 0 : delay ) + "ms"
+ } );
+
+ // Here is a tricky part...
+ //
+ // If there is no change in scale or no change in rotation and translation, it means
+ // there was actually no delay - because there was no transition on `root` or `canvas`
+ // elements. We want to trigger `impress:stepenter` event in the correct moment, so
+ // here we compare the current and target values to check if delay should be taken into
+ // account.
+ //
+ // I know that this `if` statement looks scary, but it's pretty simple when you know
+ // what is going on - it's simply comparing all the values.
+ if ( currentState.scale === target.scale ||
+ ( currentState.rotate.x === target.rotate.x &&
+ currentState.rotate.y === target.rotate.y &&
+ currentState.rotate.z === target.rotate.z &&
+ currentState.translate.x === target.translate.x &&
+ currentState.translate.y === target.translate.y &&
+ currentState.translate.z === target.translate.z ) ) {
+ delay = 0;
+ }
+
+ // Store current state
+ currentState = target;
+ activeStep = el;
+
+ // And here is where we trigger `impress:stepenter` event.
+ // We simply set up a timeout to fire it taking transition duration (and possible delay)
+ // into account.
+ //
+ // I really wanted to make it in more elegant way. The `transitionend` event seemed to
+ // be the best way to do it, but the fact that I'm using transitions on two separate
+ // elements and that the `transitionend` event is only triggered when there was a
+ // transition (change in the values) caused some bugs and made the code really
+ // complicated, cause I had to handle all the conditions separately. And it still
+ // needed a `setTimeout` fallback for the situations when there is no transition at all.
+ // So I decided that I'd rather make the code simpler than use shiny new
+ // `transitionend`.
+ //
+ // If you want learn something interesting and see how it was done with `transitionend`
+ // go back to version 0.5.2 of impress.js:
+ // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
+ window.clearTimeout( stepEnterTimeout );
+ stepEnterTimeout = window.setTimeout( function() {
+ onStepEnter( activeStep );
+ }, duration + delay );
+
+ return el;
+ };
+
+ // `prev` API function goes to previous step (in document order)
+ // `event` is optional, may contain the event that caused the need to call prev()
+ var prev = function( origEvent ) {
+ var prev = steps.indexOf( activeStep ) - 1;
+ prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
+
+ return goto( prev, undefined, "prev", origEvent );
+ };
+
+ // `next` API function goes to next step (in document order)
+ // `event` is optional, may contain the event that caused the need to call next()
+ var next = function( origEvent ) {
+ var next = steps.indexOf( activeStep ) + 1;
+ next = next < steps.length ? steps[ next ] : steps[ 0 ];
+
+ return goto( next, undefined, "next", origEvent );
+ };
+
+ // Swipe for touch devices by @and3rson.
+ // Below we extend the api to control the animation between the currently
+ // active step and a presumed next/prev step. See touch plugin for
+ // an example of using this api.
+
+ // Helper function
+ var interpolate = function( a, b, k ) {
+ return a + ( b - a ) * k;
+ };
+
+ // Animate a swipe.
+ //
+ // Pct is a value between -1.0 and +1.0, designating the current length
+ // of the swipe.
+ //
+ // If pct is negative, swipe towards the next() step, if positive,
+ // towards the prev() step.
+ //
+ // Note that pre-stepleave plugins such as goto can mess with what is a
+ // next() and prev() step, so we need to trigger the pre-stepleave event
+ // here, even if a swipe doesn't guarantee that the transition will
+ // actually happen.
+ //
+ // Calling swipe(), with any value of pct, won't in itself cause a
+ // transition to happen, this is just to animate the swipe. Once the
+ // transition is committed - such as at a touchend event - caller is
+ // responsible for also calling prev()/next() as appropriate.
+ //
+ // Note: For now, this function is made available to be used by the swipe plugin (which
+ // is the UI counterpart to this). It is a semi-internal API and intentionally not
+ // documented in DOCUMENTATION.md.
+ var swipe = function( pct ) {
+ if ( Math.abs( pct ) > 1 ) {
+ return;
+ }
+
+ // Prepare & execute the preStepLeave event
+ var event = { target: activeStep, detail: {} };
+ event.detail.swipe = pct;
+
+ // Will be ignored within swipe animation, but just in case a plugin wants to read this,
+ // humor them
+ event.detail.transitionDuration = config.transitionDuration;
+ var idx; // Needed by jshint
+ if ( pct < 0 ) {
+ idx = steps.indexOf( activeStep ) + 1;
+ event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
+ event.detail.reason = "next";
+ } else if ( pct > 0 ) {
+ idx = steps.indexOf( activeStep ) - 1;
+ event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
+ event.detail.reason = "prev";
+ } else {
+
+ // No move
+ return;
+ }
+ if ( execPreStepLeavePlugins( event ) === false ) {
+
+ // If a preStepLeave plugin wants to abort the transition, don't animate a swipe
+ // For stop, this is probably ok. For substep, the plugin it self might want to do
+ // some animation, but that's not the current implementation.
+ return false;
+ }
+ var nextElement = event.detail.next;
+
+ var nextStep = stepsData[ "impress-" + nextElement.id ];
+
+ // If the same step is re-selected, force computing window scaling,
+ var nextScale = nextStep.scale * windowScale;
+ var k = Math.abs( pct );
+
+ var interpolatedStep = {
+ translate: {
+ x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
+ y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
+ z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
+ },
+ rotate: {
+ x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
+ y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
+ z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
+
+ // Unfortunately there's a discontinuity if rotation order changes. Nothing I
+ // can do about it?
+ order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
+ },
+ scale: interpolate( currentState.scale * windowScale, nextScale, k )
+ };
+
+ css( root, {
+
+ // To keep the perspective look similar for different scales
+ // we need to 'scale' the perspective, too
+ perspective: config.perspective / interpolatedStep.scale + "px",
+ transform: scale( interpolatedStep.scale ),
+ transitionDuration: "0ms",
+ transitionDelay: "0ms"
+ } );
+
+ css( canvas, {
+ transform: rotate( interpolatedStep.rotate, true ) +
+ translate( interpolatedStep.translate ),
+ transitionDuration: "0ms",
+ transitionDelay: "0ms"
+ } );
+ };
+
+ // Teardown impress
+ // Resets the DOM to the state it was before impress().init() was called.
+ // (If you called impress(rootId).init() for multiple different rootId's, then you must
+ // also call tear() once for each of them.)
+ var tear = function() {
+ lib.gc.teardown();
+ delete roots[ "impress-root-" + rootId ];
+ };
+
+ // Adding some useful classes to step elements.
+ //
+ // All the steps that have not been shown yet are given `future` class.
+ // When the step is entered the `future` class is removed and the `present`
+ // class is given. When the step is left `present` class is replaced with
+ // `past` class.
+ //
+ // So every step element is always in one of three possible states:
+ // `future`, `present` and `past`.
+ //
+ // There classes can be used in CSS to style different types of steps.
+ // For example the `present` class can be used to trigger some custom
+ // animations when step is shown.
+ lib.gc.addEventListener( root, "impress:init", function() {
+
+ // STEP CLASSES
+ steps.forEach( function( step ) {
+ step.classList.add( "future" );
+ } );
+
+ lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
+ event.target.classList.remove( "past" );
+ event.target.classList.remove( "future" );
+ event.target.classList.add( "present" );
+ }, false );
+
+ lib.gc.addEventListener( root, "impress:stepleave", function( event ) {
+ event.target.classList.remove( "present" );
+ event.target.classList.add( "past" );
+ }, false );
+
+ }, false );
+
+ // Adding hash change support.
+ lib.gc.addEventListener( root, "impress:init", function() {
+
+ // Last hash detected
+ var lastHash = "";
+
+ // `#/step-id` is used instead of `#step-id` to prevent default browser
+ // scrolling to element in hash.
+ //
+ // And it has to be set after animation finishes, because in Chrome it
+ // makes transition laggy.
+ // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
+ lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
+ window.location.hash = lastHash = "#/" + event.target.id;
+ }, false );
+
+ lib.gc.addEventListener( window, "hashchange", function() {
+
+ // When the step is entered hash in the location is updated
+ // (just few lines above from here), so the hash change is
+ // triggered and we would call `goto` again on the same element.
+ //
+ // To avoid this we store last entered hash and compare.
+ if ( window.location.hash !== lastHash ) {
+ goto( lib.util.getElementFromHash() );
+ }
+ }, false );
+
+ // START
+ // by selecting step defined in url or first step of the presentation
+ goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );
+ }, false );
+
+ body.classList.add( "impress-disabled" );
+
+ // Store and return API for given impress.js root element
+ return ( roots[ "impress-root-" + rootId ] = {
+ init: init,
+ goto: goto,
+ next: next,
+ prev: prev,
+ swipe: swipe,
+ tear: tear,
+ lib: lib
+ } );
+
+ };
+
+ // Flag that can be used in JS to check if browser have passed the support test
+ impress.supported = impressSupported;
+
+ // ADD and INIT LIBRARIES
+ // Library factories are defined in src/lib/*.js, and register themselves by calling
+ // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment
+ // the API with library functions when client calls impress(rootId).
+ // See src/lib/README.md for clearer example.
+ // (Advanced usage: For different values of rootId, a different instance of the libaries are
+ // generated, in case they need to hold different state for different root elements.)
+ var libraryFactories = {};
+ impress.addLibraryFactory = function( obj ) {
+ for ( var libname in obj ) {
+ if ( obj.hasOwnProperty( libname ) ) {
+ libraryFactories[ libname ] = obj[ libname ];
+ }
+ }
+ };
+
+ // Call each library factory, and return the lib object that is added to the api.
+ var initLibraries = function( rootId ) { //jshint ignore:line
+ var lib = {};
+ for ( var libname in libraryFactories ) {
+ if ( libraryFactories.hasOwnProperty( libname ) ) {
+ if ( lib[ libname ] !== undefined ) {
+ throw "impress.js ERROR: Two libraries both tried to use libname: " + libname;
+ }
+ lib[ libname ] = libraryFactories[ libname ]( rootId );
+ }
+ }
+ return lib;
+ };
+
+ // `addPreInitPlugin` allows plugins to register a function that should
+ // be run (synchronously) at the beginning of init, before
+ // impress().init() itself executes.
+ impress.addPreInitPlugin = function( plugin, weight ) {
+ weight = parseInt( weight ) || 10;
+ if ( weight <= 0 ) {
+ throw "addPreInitPlugin: weight must be a positive integer";
+ }
+
+ if ( preInitPlugins[ weight ] === undefined ) {
+ preInitPlugins[ weight ] = [];
+ }
+ preInitPlugins[ weight ].push( plugin );
+ };
+
+ // Called at beginning of init, to execute all pre-init plugins.
+ var execPreInitPlugins = function( root ) { //jshint ignore:line
+ for ( var i = 0; i < preInitPlugins.length; i++ ) {
+ var thisLevel = preInitPlugins[ i ];
+ if ( thisLevel !== undefined ) {
+ for ( var j = 0; j < thisLevel.length; j++ ) {
+ thisLevel[ j ]( root, roots[ "impress-root-" + root.id ] );
+ }
+ }
+ }
+ };
+
+ // `addPreStepLeavePlugin` allows plugins to register a function that should
+ // be run (synchronously) at the beginning of goto()
+ impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line
+ weight = parseInt( weight ) || 10;
+ if ( weight <= 0 ) {
+ throw "addPreStepLeavePlugin: weight must be a positive integer";
+ }
+
+ if ( preStepLeavePlugins[ weight ] === undefined ) {
+ preStepLeavePlugins[ weight ] = [];
+ }
+ preStepLeavePlugins[ weight ].push( plugin );
+ };
+
+ impress.getConfig = function() {
+ return config;
+ };
+
+ // Called at beginning of goto(), to execute all preStepLeave plugins.
+ var execPreStepLeavePlugins = function( event ) { //jshint ignore:line
+ for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {
+ var thisLevel = preStepLeavePlugins[ i ];
+ if ( thisLevel !== undefined ) {
+ for ( var j = 0; j < thisLevel.length; j++ ) {
+ if ( thisLevel[ j ]( event ) === false ) {
+
+ // If a plugin returns false, the stepleave event (and related transition)
+ // is aborted
+ return false;
+ }
+ }
+ }
+ }
+ };
+
+} )( document, window );
+
+// THAT'S ALL FOLKS!
+//
+// Thanks for reading it all.
+// Or thanks for scrolling down and reading the last part.
+//
+// I've learnt a lot when building impress.js and I hope this code and comments
+// will help somebody learn at least some part of it.
+
+/**
+ * Garbage collection utility
+ *
+ * This library allows plugins to add elements and event listeners they add to the DOM. The user
+ * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that
+ * the document is in the state it was before calling `impress().init()`.
+ *
+ * In addition to just adding elements and event listeners to the garbage collector, plugins
+ * can also register callback functions to do arbitrary cleanup upon teardown.
+ *
+ * Henrik Ingo (c) 2016
+ * MIT License
+ */
+
+( function( document, window ) {
+ "use strict";
+ var roots = [];
+ var rootsCount = 0;
+ var startingState = { roots: [] };
+
+ var libraryFactory = function( rootId ) {
+ if ( roots[ rootId ] ) {
+ return roots[ rootId ];
+ }
+
+ // Per root global variables (instance variables?)
+ var elementList = [];
+ var eventListenerList = [];
+ var callbackList = [];
+
+ recordStartingState( rootId );
+
+ // LIBRARY FUNCTIONS
+ // Definitions of the library functions we return as an object at the end
+
+ // `pushElement` adds a DOM element to the gc stack
+ var pushElement = function( element ) {
+ elementList.push( element );
+ };
+
+ // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement
+ var appendChild = function( parent, element ) {
+ parent.appendChild( element );
+ pushElement( element );
+ };
+
+ // `pushEventListener` adds an event listener to the gc stack
+ var pushEventListener = function( target, type, listenerFunction ) {
+ eventListenerList.push( { target:target, type:type, listener:listenerFunction } );
+ };
+
+ // `addEventListener` combines DOM addEventListener with gc.pushEventListener
+ var addEventListener = function( target, type, listenerFunction ) {
+ target.addEventListener( type, listenerFunction );
+ pushEventListener( target, type, listenerFunction );
+ };
+
+ // `pushCallback` If the above utilities are not enough, plugins can add their own callback
+ // function to do arbitrary things.
+ var pushCallback = function( callback ) {
+ callbackList.push( callback );
+ };
+ pushCallback( function( rootId ) { resetStartingState( rootId ); } );
+
+ // `teardown` will
+ // - execute all callbacks in LIFO order
+ // - call `removeChild` on all DOM elements in LIFO order
+ // - call `removeEventListener` on all event listeners in LIFO order
+ // The goal of a teardown is to return to the same state that the DOM was before
+ // `impress().init()` was called.
+ var teardown = function() {
+
+ // Execute the callbacks in LIFO order
+ var i; // Needed by jshint
+ for ( i = callbackList.length - 1; i >= 0; i-- ) {
+ callbackList[ i ]( rootId );
+ }
+ callbackList = [];
+ for ( i = 0; i < elementList.length; i++ ) {
+ elementList[ i ].parentElement.removeChild( elementList[ i ] );
+ }
+ elementList = [];
+ for ( i = 0; i < eventListenerList.length; i++ ) {
+ var target = eventListenerList[ i ].target;
+ var type = eventListenerList[ i ].type;
+ var listener = eventListenerList[ i ].listener;
+ target.removeEventListener( type, listener );
+ }
+ };
+
+ var lib = {
+ pushElement: pushElement,
+ appendChild: appendChild,
+ pushEventListener: pushEventListener,
+ addEventListener: addEventListener,
+ pushCallback: pushCallback,
+ teardown: teardown
+ };
+ roots[ rootId ] = lib;
+ rootsCount++;
+ return lib;
+ };
+
+ // Let impress core know about the existence of this library
+ window.impress.addLibraryFactory( { gc: libraryFactory } );
+
+ // CORE INIT
+ // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()
+ // For the purposes of teardown(), we can use this as an opportunity to save the state
+ // of a few things in the DOM in their virgin state, before impress().init() did anything.
+ // Note: These could also be recorded by the code in impress.js core as these values
+ // are changed, but in an effort to not deviate too much from upstream, I'm adding
+ // them here rather than the core itself.
+ var recordStartingState = function( rootId ) {
+ startingState.roots[ rootId ] = {};
+ startingState.roots[ rootId ].steps = [];
+
+ // Record whether the steps have an id or not
+ var steps = document.getElementById( rootId ).querySelectorAll( ".step" );
+ for ( var i = 0; i < steps.length; i++ ) {
+ var el = steps[ i ];
+ startingState.roots[ rootId ].steps.push( {
+ el: el,
+ id: el.getAttribute( "id" )
+ } );
+ }
+
+ // In the rare case of multiple roots, the following is changed on first init() and
+ // reset at last tear().
+ if ( rootsCount === 0 ) {
+ startingState.body = {};
+
+ // It is customary for authors to set body.class="impress-not-supported" as a starting
+ // value, which can then be removed by impress().init(). But it is not required.
+ // Remember whether it was there or not.
+ if ( document.body.classList.contains( "impress-not-supported" ) ) {
+ startingState.body.impressNotSupported = true;
+ } else {
+ startingState.body.impressNotSupported = false;
+ }
+
+ // If there's a element, its contents will be overwritten by init
+ var metas = document.head.querySelectorAll( "meta" );
+ for ( i = 0; i < metas.length; i++ ) {
+ var m = metas[ i ];
+ if ( m.name === "viewport" ) {
+ startingState.meta = m.content;
+ }
+ }
+ }
+ };
+
+ // CORE TEARDOWN
+ var resetStartingState = function( rootId ) {
+
+ // Reset body element
+ document.body.classList.remove( "impress-enabled" );
+ document.body.classList.remove( "impress-disabled" );
+
+ var root = document.getElementById( rootId );
+ var activeId = root.querySelector( ".active" ).id;
+ document.body.classList.remove( "impress-on-" + activeId );
+
+ document.documentElement.style.height = "";
+ document.body.style.height = "";
+ document.body.style.overflow = "";
+
+ // Remove style values from the root and step elements
+ // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original
+ // values. A more sophisticated implementation could keep track of original values and then
+ // reset those.
+ var steps = root.querySelectorAll( ".step" );
+ for ( var i = 0; i < steps.length; i++ ) {
+ steps[ i ].classList.remove( "future" );
+ steps[ i ].classList.remove( "past" );
+ steps[ i ].classList.remove( "present" );
+ steps[ i ].classList.remove( "active" );
+ steps[ i ].style.position = "";
+ steps[ i ].style.transform = "";
+ steps[ i ].style[ "transform-style" ] = "";
+ }
+ root.style.position = "";
+ root.style[ "transform-origin" ] = "";
+ root.style.transition = "";
+ root.style[ "transform-style" ] = "";
+ root.style.top = "";
+ root.style.left = "";
+ root.style.transform = "";
+
+ // Reset id of steps ("step-1" id's are auto generated)
+ steps = startingState.roots[ rootId ].steps;
+ var step;
+ while ( step = steps.pop() ) {
+ if ( step.id === null ) {
+ step.el.removeAttribute( "id" );
+ } else {
+ step.el.setAttribute( "id", step.id );
+ }
+ }
+ delete startingState.roots[ rootId ];
+
+ // Move step div elements away from canvas, then delete canvas
+ // Note: There's an implicit assumption here that the canvas div is the only child element
+ // of the root div. If there would be something else, it's gonna be lost.
+ var canvas = root.firstChild;
+ var canvasHTML = canvas.innerHTML;
+ root.innerHTML = canvasHTML;
+
+ if ( roots[ rootId ] !== undefined ) {
+ delete roots[ rootId ];
+ rootsCount--;
+ }
+ if ( rootsCount === 0 ) {
+
+ // In the rare case that more than one impress root elements were initialized, these
+ // are only reset when all are uninitialized.
+ document.body.classList.remove( "impress-supported" );
+ if ( startingState.body.impressNotSupported ) {
+ document.body.classList.add( "impress-not-supported" );
+ }
+
+ // We need to remove or reset the meta element inserted by impress.js
+ var metas = document.head.querySelectorAll( "meta" );
+ for ( i = 0; i < metas.length; i++ ) {
+ var m = metas[ i ];
+ if ( m.name === "viewport" ) {
+ if ( startingState.meta !== undefined ) {
+ m.content = startingState.meta;
+ } else {
+ m.parentElement.removeChild( m );
+ }
+ }
+ }
+ }
+
+ };
+
+} )( document, window );
+
+/**
+ * Common utility functions
+ *
+ * Copyright 2011-2012 Bartek Szopka (@bartaz)
+ * Henrik Ingo (c) 2016
+ * MIT License
+ */
+
+( function( document, window ) {
+ "use strict";
+ var roots = [];
+
+ var libraryFactory = function( rootId ) {
+ if ( roots[ rootId ] ) {
+ return roots[ rootId ];
+ }
+
+ // `$` returns first element for given CSS `selector` in the `context` of
+ // the given element or whole document.
+ var $ = function( selector, context ) {
+ context = context || document;
+ return context.querySelector( selector );
+ };
+
+ // `$$` return an array of elements for given CSS `selector` in the `context` of
+ // the given element or whole document.
+ var $$ = function( selector, context ) {
+ context = context || document;
+ return arrayify( context.querySelectorAll( selector ) );
+ };
+
+ // `arrayify` takes an array-like object and turns it into real Array
+ // to make all the Array.prototype goodness available.
+ var arrayify = function( a ) {
+ return [].slice.call( a );
+ };
+
+ // `byId` returns element with given `id` - you probably have guessed that ;)
+ var byId = function( id ) {
+ return document.getElementById( id );
+ };
+
+ // `getElementFromHash` returns an element located by id from hash part of
+ // window location.
+ var getElementFromHash = function() {
+
+ // Get id from url # by removing `#` or `#/` from the beginning,
+ // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
+ var encoded = window.location.hash.replace( /^#\/?/, "" );
+ return byId( decodeURIComponent( encoded ) );
+ };
+
+ // `getUrlParamValue` return a given URL parameter value if it exists
+ // `undefined` if it doesn't exist
+ var getUrlParamValue = function( parameter ) {
+ var chunk = window.location.search.split( parameter + "=" )[ 1 ];
+ var value = chunk && chunk.split( "&" )[ 0 ];
+
+ if ( value !== "" ) {
+ return value;
+ }
+ };
+
+ // Throttling function calls, by Remy Sharp
+ // http://remysharp.com/2010/07/21/throttling-function-calls/
+ var throttle = function( fn, delay ) {
+ var timer = null;
+ return function() {
+ var context = this, args = arguments;
+ window.clearTimeout( timer );
+ timer = window.setTimeout( function() {
+ fn.apply( context, args );
+ }, delay );
+ };
+ };
+
+ // `toNumber` takes a value given as `numeric` parameter and tries to turn
+ // it into a number. If it is not possible it returns 0 (or other value
+ // given as `fallback`).
+ var toNumber = function( numeric, fallback ) {
+ return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
+ };
+
+ /**
+ * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
+ *
+ * Returns the computed value in pixels with w/h postfix removed.
+ */
+ var toNumberAdvanced = function( numeric, fallback ) {
+ if ( typeof numeric !== "string" ) {
+ return toNumber( numeric, fallback );
+ }
+ var ratio = numeric.match( /^([+-]*[\d\.]+)([wh])$/ );
+ if ( ratio == null ) {
+ return toNumber( numeric, fallback );
+ } else {
+ var value = parseFloat( ratio[ 1 ] );
+ var config = window.impress.getConfig();
+ var multiplier = ratio[ 2 ] === "w" ? config.width : config.height;
+ return value * multiplier;
+ }
+ };
+
+ // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
+ // and triggers it on element given as `el`.
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( "CustomEvent" );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var lib = {
+ $: $,
+ $$: $$,
+ arrayify: arrayify,
+ byId: byId,
+ getElementFromHash: getElementFromHash,
+ throttle: throttle,
+ toNumber: toNumber,
+ toNumberAdvanced: toNumberAdvanced,
+ triggerEvent: triggerEvent,
+ getUrlParamValue: getUrlParamValue
+ };
+ roots[ rootId ] = lib;
+ return lib;
+ };
+
+ // Let impress core know about the existence of this library
+ window.impress.addLibraryFactory( { util: libraryFactory } );
+
+} )( document, window );
+
+/**
+ * Helper functions for rotation.
+ *
+ * Tommy Tam (c) 2021
+ * MIT License
+ */
+( function( document, window ) {
+ "use strict";
+
+ // Singleton library variables
+ var roots = [];
+
+ var libraryFactory = function( rootId ) {
+ if ( roots[ "impress-root-" + rootId ] ) {
+ return roots[ "impress-root-" + rootId ];
+ }
+
+ /**
+ * Round the number to 2 decimals, it's enough for use
+ */
+ var roundNumber = function( num ) {
+ return Math.round( ( num + Number.EPSILON ) * 100 ) / 100;
+ };
+
+ /**
+ * Get the length/norm of a vector.
+ *
+ * https://en.wikipedia.org/wiki/Norm_(mathematics)
+ */
+ var vectorLength = function( vec ) {
+ return Math.sqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z );
+ };
+
+ /**
+ * Dot product of two vectors.
+ *
+ * https://en.wikipedia.org/wiki/Dot_product
+ */
+ var vectorDotProd = function( vec1, vec2 ) {
+ return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
+ };
+
+ /**
+ * Cross product of two vectors.
+ *
+ * https://en.wikipedia.org/wiki/Cross_product
+ */
+ var vectorCrossProd = function( vec1, vec2 ) {
+ return {
+ x: vec1.y * vec2.z - vec1.z * vec2.y,
+ y: vec1.z * vec2.x - vec1.x * vec2.z,
+ z: vec1.x * vec2.y - vec1.y * vec2.x
+ };
+ };
+
+ /**
+ * Determine wheter a vector is a zero vector
+ */
+ var isZeroVector = function( vec ) {
+ return !roundNumber( vec.x ) && !roundNumber( vec.y ) && !roundNumber( vec.z );
+ };
+
+ /**
+ * Scalar triple product of three vectors.
+ *
+ * It can be used to determine the handness of vectors.
+ *
+ * https://en.wikipedia.org/wiki/Triple_product#Scalar_triple_product
+ */
+ var tripleProduct = function( vec1, vec2, vec3 ) {
+ return vectorDotProd( vectorCrossProd( vec1, vec2 ), vec3 );
+ };
+
+ /**
+ * The world/absolute unit coordinates.
+ *
+ * This coordinate is used by browser to position objects.
+ * It will not be affected by object rotations.
+ * All relative positions will finally be converted to this
+ * coordinate to be used.
+ */
+ var worldUnitCoordinate = {
+ x: { x:1, y:0, z:0 },
+ y: { x:0, y:1, z:0 },
+ z: { x:0, y:0, z:1 }
+ };
+
+ /**
+ * Make quaternion from rotation axis and angle.
+ *
+ * q = [ cos(½θ), sin(½θ) axis ]
+ *
+ * If the angle is zero, returns the corresponded quaternion
+ * of axis.
+ *
+ * If the angle is not zero, returns the rotating quaternion
+ * which corresponds to rotation about the axis, by the angle θ.
+ *
+ * https://en.wikipedia.org/wiki/Quaternion
+ * https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation
+ */
+ var makeQuaternion = function( axis, theta = 0 ) {
+ var r = 0;
+ var t = 1;
+
+ if ( theta ) {
+ var radians = theta * Math.PI / 180;
+ r = Math.cos( radians / 2 );
+ t = Math.sin( radians / 2 ) / vectorLength( axis );
+ }
+
+ var q = [ r, axis.x * t, axis.y * t, axis.z * t ];
+
+ return q;
+ };
+
+ /**
+ * Extract vector from quaternion
+ */
+ var quaternionToVector = function( quaternion ) {
+ return {
+ x: roundNumber( quaternion[ 1 ] ),
+ y: roundNumber( quaternion[ 2 ] ),
+ z: roundNumber( quaternion[ 3 ] )
+ };
+ };
+
+ /**
+ * Returns the conjugate quaternion of a quaternion
+ *
+ * https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal
+ */
+ var conjugateQuaternion = function( quaternion ) {
+ return [ quaternion[ 0 ], -quaternion[ 1 ], -quaternion[ 2 ], -quaternion[ 3 ] ];
+ };
+
+ /**
+ * Left multiple two quaternion.
+ *
+ * Is's used to combine two rotating quaternion into one.
+ */
+ var leftMulQuaternion = function( q1, q2 ) {
+ return [
+ ( q1[ 0 ] * q2[ 0 ] - q1[ 1 ] * q2[ 1 ] - q1[ 2 ] * q2[ 2 ] - q1[ 3 ] * q2[ 3 ] ),
+ ( q1[ 1 ] * q2[ 0 ] + q1[ 0 ] * q2[ 1 ] - q1[ 3 ] * q2[ 2 ] + q1[ 2 ] * q2[ 3 ] ),
+ ( q1[ 2 ] * q2[ 0 ] + q1[ 3 ] * q2[ 1 ] + q1[ 0 ] * q2[ 2 ] - q1[ 1 ] * q2[ 3 ] ),
+ ( q1[ 3 ] * q2[ 0 ] - q1[ 2 ] * q2[ 1 ] + q1[ 1 ] * q2[ 2 ] + q1[ 0 ] * q2[ 3 ] )
+ ];
+ };
+
+ /**
+ * Convert a rotation into a quaternion
+ */
+ var rotationToQuaternion = function( baseCoordinate, rotation ) {
+ var order = rotation.order ? rotation.order : "xyz";
+ var axes = order.split( "" );
+ var result = [ 1, 0, 0, 0 ];
+
+ for ( var i = 0; i < axes.length; i++ ) {
+ var deg = rotation[ axes[ i ] ];
+ if ( !deg || ( Math.abs( deg ) < 0.0001 ) ) {
+ continue;
+ }
+
+ // All CSS rotation is based on the rotated coordinate
+ // So we need to calculate the rotated coordinate first
+ var coordinate = baseCoordinate;
+ if ( i > 0 ) {
+ coordinate = {
+ x: rotateByQuaternion( baseCoordinate.x, result ),
+ y: rotateByQuaternion( baseCoordinate.y, result ),
+ z: rotateByQuaternion( baseCoordinate.z, result )
+ };
+ }
+
+ result = leftMulQuaternion(
+ makeQuaternion( coordinate[ axes[ i ] ], deg ),
+ result );
+
+ }
+
+ return result;
+ };
+
+ /**
+ * Rotate a vector by a quaternion.
+ */
+ var rotateByQuaternion = function( vec, quaternion ) {
+ var q = makeQuaternion( vec );
+
+ q = leftMulQuaternion(
+ leftMulQuaternion( quaternion, q ),
+ conjugateQuaternion( quaternion ) );
+
+ return quaternionToVector( q );
+ };
+
+ /**
+ * Rotate a vector by rotaion sequence.
+ */
+ var rotateVector = function( baseCoordinate, vec, rotation ) {
+ var quaternion = rotationToQuaternion( baseCoordinate, rotation );
+
+ return rotateByQuaternion( vec, quaternion );
+ };
+
+ /**
+ * Given a rotation, return the rotationed coordinate
+ */
+ var rotateCoordinate = function( coordinate, rotation ) {
+ var quaternion = rotationToQuaternion( coordinate, rotation );
+
+ return {
+ x: rotateByQuaternion( coordinate.x, quaternion ),
+ y: rotateByQuaternion( coordinate.y, quaternion ),
+ z: rotateByQuaternion( coordinate.z, quaternion )
+ };
+ };
+
+ /**
+ * Return the angle between two vector.
+ *
+ * The axis is used to determine the rotation direction.
+ */
+ var angleBetweenTwoVector = function( axis, vec1, vec2 ) {
+ var vecLen1 = vectorLength( vec1 );
+ var vecLen2 = vectorLength( vec2 );
+
+ if ( !vecLen1 || !vecLen2 ) {
+ return 0;
+ }
+
+ var cos = vectorDotProd( vec1, vec2 ) / vecLen1 / vecLen2 ;
+ var angle = Math.acos( cos ) * 180 / Math.PI;
+
+ if ( tripleProduct( vec1, vec2, axis ) > 0 ) {
+ return angle;
+ } else {
+ return -angle;
+ }
+ };
+
+ /**
+ * Return the angle between a vector and a plane.
+ *
+ * The plane is determined by an axis and a vector on the plane.
+ */
+ var angleBetweenPlaneAndVector = function( axis, planeVec, rotatedVec ) {
+ var norm = vectorCrossProd( axis, planeVec );
+
+ if ( isZeroVector( norm ) ) {
+ return 0;
+ }
+
+ return 90 - angleBetweenTwoVector( axis, rotatedVec, norm );
+ };
+
+ /**
+ * Calculated a order specified rotation sequence to
+ * transform from the world coordinate to required coordinate.
+ */
+ var coordinateToOrderedRotation = function( coordinate, order ) {
+ var axis0 = order[ 0 ];
+ var axis1 = order[ 1 ];
+ var axis2 = order[ 2 ];
+ var reversedOrder = order.split( "" ).reverse().join( "" );
+
+ var rotate2 = angleBetweenPlaneAndVector(
+ coordinate[ axis2 ],
+ worldUnitCoordinate[ axis0 ],
+ coordinate[ axis0 ] );
+
+ // The r2 is the reverse of rotate for axis2
+ // The coordinate1 is the coordinate before rotate of axis2
+ var r2 = { order: reversedOrder };
+ r2[ axis2 ] = -rotate2;
+
+ var coordinate1 = rotateCoordinate( coordinate, r2 );
+
+ // Calculate the rotation for axis1
+ var rotate1 = angleBetweenTwoVector(
+ coordinate1[ axis1 ],
+ worldUnitCoordinate[ axis0 ],
+ coordinate1[ axis0 ] );
+
+ // Calculate the rotation for axis0
+ var rotate0 = angleBetweenTwoVector(
+ worldUnitCoordinate[ axis0 ],
+ worldUnitCoordinate[ axis1 ],
+ coordinate1[ axis1 ] );
+
+ var rotation = { };
+ rotation.order = order;
+ rotation[ axis0 ] = roundNumber( rotate0 );
+ rotation[ axis1 ] = roundNumber( rotate1 );
+ rotation[ axis2 ] = roundNumber( rotate2 );
+
+ return rotation;
+ };
+
+ /**
+ * Returns the possible rotations from unit coordinate
+ * to specified coordinate.
+ */
+ var possibleRotations = function( coordinate ) {
+ var orders = [ "xyz", "xzy", "yxz", "yzx", "zxy", "zyx" ];
+ var rotations = [ ];
+
+ for ( var i = 0; i < orders.length; ++i ) {
+ rotations.push(
+ coordinateToOrderedRotation( coordinate, orders[ i ] )
+ );
+ }
+
+ return rotations;
+ };
+
+ /**
+ * Calculate a degree which in range (-180, 180] of baseDeg
+ */
+ var nearestAngle = function( baseDeg, deg ) {
+ while ( deg > baseDeg + 180 ) {
+ deg -= 360;
+ }
+
+ while ( deg < baseDeg - 180 ) {
+ deg += 360;
+ }
+
+ return deg;
+ };
+
+ /**
+ * Given a base rotation and multiple rotations, return the best one.
+ *
+ * The best one is the one has least rotate from base.
+ */
+ var bestRotation = function( baseRotate, rotations ) {
+ var bestScore;
+ var bestRotation;
+
+ for ( var i = 0; i < rotations.length; ++i ) {
+ var rotation = {
+ order: rotations[ i ].order,
+ x: nearestAngle( baseRotate.x, rotations[ i ].x ),
+ y: nearestAngle( baseRotate.y, rotations[ i ].y ),
+ z: nearestAngle( baseRotate.z, rotations[ i ].z )
+ };
+
+ var score = Math.abs( rotation.x - baseRotate.x ) +
+ Math.abs( rotation.y - baseRotate.y ) +
+ Math.abs( rotation.z - baseRotate.z );
+
+ if ( !i || ( score < bestScore ) ) {
+ bestScore = score;
+ bestRotation = rotation;
+ }
+ }
+
+ return bestRotation;
+ };
+
+ /**
+ * Given a coordinate, return the best rotation to achieve it.
+ *
+ * The baseRotate is used to select the near rotation from it.
+ */
+ var coordinateToRotation = function( baseRotate, coordinate ) {
+ var rotations = possibleRotations( coordinate );
+
+ return bestRotation( baseRotate, rotations );
+ };
+
+ /**
+ * Apply a relative rotation to the base rotation.
+ *
+ * Calculate the coordinate after the rotation on each axis,
+ * and finally find out a one step rotation has the effect
+ * of two rotation.
+ *
+ * If there're multiple way to accomplish, select the one
+ * that is nearest to the base.
+ *
+ * Return one rotation has the same effect.
+ */
+ var combineRotations = function( rotations ) {
+
+ // No rotation
+ if ( rotations.length <= 0 ) {
+ return { x:0, y:0, z:0, order:"xyz" };
+ }
+
+ // Find out the base coordinate
+ var coordinate = worldUnitCoordinate;
+
+ // One by one apply rotations in order
+ for ( var i = 0; i < rotations.length; i++ ) {
+ coordinate = rotateCoordinate( coordinate, rotations[ i ] );
+ }
+
+ // Calculate one rotation from unit coordinate to rotated
+ // coordinate. Because there're multiple possibles,
+ // select the one nearest to the base
+ var rotate = coordinateToRotation( rotations[ 0 ], coordinate );
+
+ return rotate;
+ };
+
+ var translateRelative = function( relative, prevRotation ) {
+ var result = rotateVector(
+ worldUnitCoordinate, relative, prevRotation );
+ result.rotate = combineRotations(
+ [ prevRotation, relative.rotate ] );
+
+ return result;
+ };
+
+ var lib = {
+ translateRelative: translateRelative
+ };
+
+ roots[ "impress-root-" + rootId ] = lib;
+ return lib;
+ };
+
+ // Let impress core know about the existence of this library
+ window.impress.addLibraryFactory( { rotation: libraryFactory } );
+
+} )( document, window );
+
+/**
+ * Autoplay plugin - Automatically advance slideshow after N seconds
+ *
+ * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
+ * Released under the MIT license.
+ */
+/* global clearTimeout, setTimeout, document */
+
+( function( document ) {
+ "use strict";
+
+ var autoplayDefault = 0;
+ var currentStepTimeout = 0;
+ var api = null;
+ var timeoutHandle = null;
+ var root = null;
+ var util;
+
+ // On impress:init, check whether there is a default setting, as well as
+ // handle step-1.
+ document.addEventListener( "impress:init", function( event ) {
+ util = event.detail.api.lib.util;
+
+ // Getting API from event data instead of global impress().init().
+ // You don't even need to know what is the id of the root element
+ // or anything. `impress:init` event data gives you everything you
+ // need to control the presentation that was just initialized.
+ api = event.detail.api;
+ root = event.target;
+
+ // Element attributes starting with "data-", become available under
+ // element.dataset. In addition hyphenized words become camelCased.
+ var data = root.dataset;
+ var autoplay = util.getUrlParamValue( "impress-autoplay" ) || data.autoplay;
+
+ if ( autoplay ) {
+ autoplayDefault = util.toNumber( autoplay, 0 );
+ }
+
+ var toolbar = document.querySelector( "#impress-toolbar" );
+ if ( toolbar ) {
+ addToolbarButton( toolbar );
+ }
+
+ api.lib.gc.pushCallback( function() {
+ clearTimeout( timeoutHandle );
+ } );
+
+ // Note that right after impress:init event, also impress:stepenter is
+ // triggered for the first slide, so that's where code flow continues.
+ }, false );
+
+ document.addEventListener( "impress:autoplay:pause", function( event ) {
+ status = "paused";
+ reloadTimeout( event );
+ }, false );
+
+ document.addEventListener( "impress:autoplay:play", function( event ) {
+ status = "playing";
+ reloadTimeout( event );
+ }, false );
+
+ // If default autoplay time was defined in the presentation root, or
+ // in this step, set timeout.
+ var reloadTimeout = function( event ) {
+ var step = event.target;
+ currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );
+ if ( status === "paused" ) {
+ setAutoplayTimeout( 0 );
+ } else {
+ setAutoplayTimeout( currentStepTimeout );
+ }
+ };
+
+ document.addEventListener( "impress:stepenter", function( event ) {
+ reloadTimeout( event );
+ }, false );
+
+ document.addEventListener( "impress:substep:enter", function( event ) {
+ reloadTimeout( event );
+ }, false );
+
+ /**
+ * Set timeout after which we move to next() step.
+ */
+ var setAutoplayTimeout = function( timeout ) {
+ if ( timeoutHandle ) {
+ clearTimeout( timeoutHandle );
+ }
+
+ if ( timeout > 0 ) {
+ timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );
+ }
+ setButtonText();
+ };
+
+ /*** Toolbar plugin integration *******************************************/
+ var status = "not clicked";
+ var toolbarButton = null;
+
+ var makeDomElement = function( html ) {
+ var tempDiv = document.createElement( "div" );
+ tempDiv.innerHTML = html;
+ return tempDiv.firstChild;
+ };
+
+ var toggleStatus = function() {
+ if ( currentStepTimeout > 0 && status !== "paused" ) {
+ status = "paused";
+ } else {
+ status = "playing";
+ }
+ };
+
+ var getButtonText = function() {
+ if ( currentStepTimeout > 0 && status !== "paused" ) {
+ return "||"; // Pause
+ } else {
+ return "▶"; // Play
+ }
+ };
+
+ var setButtonText = function() {
+ if ( toolbarButton ) {
+
+ // Keep button size the same even if label content is changing
+ var buttonWidth = toolbarButton.offsetWidth;
+ var buttonHeight = toolbarButton.offsetHeight;
+ toolbarButton.innerHTML = getButtonText();
+ if ( !toolbarButton.style.width ) {
+ toolbarButton.style.width = buttonWidth + "px";
+ }
+ if ( !toolbarButton.style.height ) {
+ toolbarButton.style.height = buttonHeight + "px";
+ }
+ }
+ };
+
+ var addToolbarButton = function( toolbar ) {
+ var html = '"; // jshint ignore:line
+ toolbarButton = makeDomElement( html );
+ toolbarButton.addEventListener( "click", function() {
+ toggleStatus();
+ if ( status === "playing" ) {
+ if ( autoplayDefault === 0 ) {
+ autoplayDefault = 7;
+ }
+ if ( currentStepTimeout === 0 ) {
+ currentStepTimeout = autoplayDefault;
+ }
+ setAutoplayTimeout( currentStepTimeout );
+ } else if ( status === "paused" ) {
+ setAutoplayTimeout( 0 );
+ }
+ } );
+
+ util.triggerEvent( toolbar, "impress:toolbar:appendChild",
+ { group: 10, element: toolbarButton } );
+ };
+
+} )( document );
+
+/**
+ * Blackout plugin
+ *
+ * Press b or . to hide all slides, and b or . again to show them.
+ * Also navigating to a different slide will show them again (impress:stepleave).
+ *
+ * Copyright 2014 @Strikeskids
+ * Released under the MIT license.
+ */
+/* global document */
+
+( function( document ) {
+ "use strict";
+
+ var canvas = null;
+ var blackedOut = false;
+ var util = null;
+ var root = null;
+ var api = null;
+
+ // While waiting for a shared library of utilities, copying these 2 from main impress.js
+ var css = function( el, props ) {
+ var key, pkey;
+ for ( key in props ) {
+ if ( props.hasOwnProperty( key ) ) {
+ pkey = pfx( key );
+ if ( pkey !== null ) {
+ el.style[ pkey ] = props[ key ];
+ }
+ }
+ }
+ return el;
+ };
+
+ var pfx = ( function() {
+
+ var style = document.createElement( "dummy" ).style,
+ prefixes = "Webkit Moz O ms Khtml".split( " " ),
+ memory = {};
+
+ return function( prop ) {
+ if ( typeof memory[ prop ] === "undefined" ) {
+
+ var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+ props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
+
+ memory[ prop ] = null;
+ for ( var i in props ) {
+ if ( style[ props[ i ] ] !== undefined ) {
+ memory[ prop ] = props[ i ];
+ break;
+ }
+ }
+
+ }
+
+ return memory[ prop ];
+ };
+
+ } )();
+
+ var removeBlackout = function() {
+ if ( blackedOut ) {
+ css( canvas, {
+ display: "block"
+ } );
+ blackedOut = false;
+ util.triggerEvent( root, "impress:autoplay:play", {} );
+ }
+ };
+
+ var blackout = function() {
+ if ( blackedOut ) {
+ removeBlackout();
+ } else {
+ css( canvas, {
+ display: ( blackedOut = !blackedOut ) ? "none" : "block"
+ } );
+ blackedOut = true;
+ util.triggerEvent( root, "impress:autoplay:pause", {} );
+ }
+ };
+
+ // Wait for impress.js to be initialized
+ document.addEventListener( "impress:init", function( event ) {
+ api = event.detail.api;
+ util = api.lib.util;
+ root = event.target;
+ canvas = root.firstElementChild;
+ var gc = api.lib.gc;
+
+ gc.addEventListener( document, "keydown", function( event ) {
+
+ // Accept b or . -> . is sent by presentation remote controllers
+ if ( event.keyCode === 66 || event.keyCode === 190 ) {
+ event.preventDefault();
+ if ( !blackedOut ) {
+ blackout();
+ } else {
+ removeBlackout();
+ }
+ }
+ }, false );
+
+ gc.addEventListener( document, "keyup", function( event ) {
+
+ // Accept b or . -> . is sent by presentation remote controllers
+ if ( event.keyCode === 66 || event.keyCode === 190 ) {
+ event.preventDefault();
+ }
+ }, false );
+
+ }, false );
+
+ document.addEventListener( "impress:stepleave", function() {
+ removeBlackout();
+ }, false );
+
+} )( document );
+
+
+/**
+ * Extras Plugin
+ *
+ * This plugin performs initialization (like calling mermaid.initialize())
+ * for the extras/ plugins if they are loaded into a presentation.
+ *
+ * See README.md for details.
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global markdown, marked, hljs, mermaid, impress */
+
+( function( document, window ) {
+ "use strict";
+
+ const SLIDE_SEPARATOR = /^-----$/m;
+
+ const getMarkdownParser = function( ) {
+ if ( window.hasOwnProperty( "marked" ) ) {
+
+ // Using marked
+ return function( elem, src ) {
+ return marked.parse( src );
+ };
+ } else if ( window.hasOwnProperty( "markdown" ) ) {
+
+ // Using builtin markdown engine
+ return function( elem, src ) {
+ var dialect = elem.dataset.markdownDialect;
+ return markdown.toHTML( src, dialect );
+ };
+ }
+
+ return null;
+ };
+
+ const getMarkdownSlides = function( elem ) {
+ var text = elem.textContent;
+
+ // Using first not blank line to detect leading whitespaces.
+ // can't properly handle the mixing of space and tabs
+ var m = text.match( /^([ \t]*)\S/m );
+ if ( m !== null ) {
+ text = text.replace( new RegExp( "^" + m[ 1 ], "mg" ), "" );
+ }
+
+ return text.split( SLIDE_SEPARATOR );
+ };
+
+ const convertMarkdowns = function( selector ) {
+
+ // Detect markdown engine
+ var parseMarkdown = getMarkdownParser();
+ if ( !parseMarkdown ) {
+ return;
+ }
+
+ for ( var elem of document.querySelectorAll( selector ) ) {
+ var id = null;
+ if ( elem.id ) {
+ id = elem.id;
+ elem.id = "";
+ }
+
+ var origTitle = null;
+ if ( elem.title ) {
+ origTitle = elem.title;
+ elem.title = "";
+ }
+
+ var slides = getMarkdownSlides( elem );
+ var slideElems = [ elem ];
+
+ for ( var j = 1; j < slides.length; ++j ) {
+ var newElem = elem.cloneNode( false );
+ newElem.id = "";
+ elem.parentNode.insertBefore( newElem, slideElems[ 0 ] );
+ slideElems.splice( 0, 0, newElem );
+ }
+
+ if ( id ) {
+ slideElems[ 0 ].id = id;
+ }
+
+ for ( var i = 0; i < slides.length; ++i ) {
+ slideElems[ i ].innerHTML =
+ parseMarkdown( slideElems[ i ], slides[ i ] );
+
+ if ( origTitle && ( i === 0 ) ) {
+ slideElems[ i ].title = origTitle;
+ }
+ }
+ }
+ };
+
+ var preInit = function() {
+
+ // Query all .markdown elements and translate to HTML
+ convertMarkdowns( ".markdown" );
+
+ if ( window.hljs ) {
+ hljs.initHighlightingOnLoad();
+ }
+
+ if ( window.mermaid ) {
+ mermaid.initialize( { startOnLoad:true } );
+ }
+ };
+
+ // Register the plugin to be called in pre-init phase
+ // Note: Markdown.js should run early/first, because it creates new div elements.
+ // So add this with a lower-than-default weight.
+ impress.addPreInitPlugin( preInit, 1 );
+
+} )( document, window );
+
+
+/**
+ * Form support
+ *
+ * Functionality to better support use of input, textarea, button... elements in a presentation.
+ *
+ * This plugin does two things:
+ *
+ * Set stopPropagation on any element that might take text input. This allows users to type, for
+ * example, the letter 'P' into a form field, without causing the presenter console to spring up.
+ *
+ * On impress:stepleave, de-focus any potentially active
+ * element. This is to prevent the focus from being left in a form element that is no longer visible
+ * in the window, and user therefore typing garbage into the form.
+ *
+ * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
+ * in particular the navigation plugin, unfortunately must fully take control of the tab key,
+ * otherwise a user could cause the browser to scroll to a link or button that's not on the current
+ * step. However, it could be possible to allow tab navigation between form elements, as long as
+ * they are on the active step. This is a topic for further study.
+ *
+ * Copyright 2016 Henrik Ingo
+ * MIT License
+ */
+/* global document */
+( function( document ) {
+ "use strict";
+ var root;
+ var api;
+
+ document.addEventListener( "impress:init", function( event ) {
+ root = event.target;
+ api = event.detail.api;
+ var gc = api.lib.gc;
+
+ var selectors = [ "input", "textarea", "select", "[contenteditable=true]" ];
+ for ( var selector of selectors ) {
+ var elements = document.querySelectorAll( selector );
+ if ( !elements ) {
+ continue;
+ }
+
+ for ( var i = 0; i < elements.length; i++ ) {
+ var e = elements[ i ];
+ gc.addEventListener( e, "keydown", function( event ) {
+ event.stopPropagation();
+ } );
+ gc.addEventListener( e, "keyup", function( event ) {
+ event.stopPropagation();
+ } );
+ }
+ }
+ }, false );
+
+ document.addEventListener( "impress:stepleave", function() {
+ document.activeElement.blur();
+ }, false );
+
+} )( document );
+
+
+/**
+ * Fullscreen plugin
+ *
+ * Press F5 to enter fullscreen and ESC to exit fullscreen mode.
+ *
+ * Copyright 2019 @giflw
+ * Released under the MIT license.
+ */
+/* global document */
+
+( function( document ) {
+ "use strict";
+
+ function enterFullscreen() {
+ var elem = document.documentElement;
+ if ( !document.fullscreenElement ) {
+ elem.requestFullscreen();
+ }
+ }
+
+ function exitFullscreen() {
+ if ( document.fullscreenElement ) {
+ document.exitFullscreen();
+ }
+ }
+
+ // Wait for impress.js to be initialized
+ document.addEventListener( "impress:init", function( event ) {
+ var api = event.detail.api;
+ var root = event.target;
+ var gc = api.lib.gc;
+ var util = api.lib.util;
+
+ gc.addEventListener( document, "keydown", function( event ) {
+
+ // 116 (F5) is sent by presentation remote controllers
+ if ( event.code === "F5" ) {
+ event.preventDefault();
+ enterFullscreen();
+ util.triggerEvent( root.querySelector( ".active" ), "impress:steprefresh" );
+ }
+
+ // 27 (Escape) is sent by presentation remote controllers
+ if ( event.key === "Escape" || event.key === "F5" ) {
+ event.preventDefault();
+ exitFullscreen();
+ util.triggerEvent( root.querySelector( ".active" ), "impress:steprefresh" );
+ }
+ }, false );
+
+ util.triggerEvent( document, "impress:help:add",
+ { command: "F5 / ESC", text: "Fullscreen: Enter / Exit", row: 200 } );
+
+ }, false );
+
+} )( document );
+
+
+/**
+ * Goto Plugin
+ *
+ * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,
+ * and will alter the destination where to transition next.
+ *
+ * Example:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table
+ * of what strings to use for each key.
+ *
+ * Copyright 2016-2017 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document, impress */
+
+( function( document, window ) {
+ "use strict";
+ var lib;
+
+ document.addEventListener( "impress:init", function( event ) {
+ lib = event.detail.api.lib;
+ }, false );
+
+ var isNumber = function( numeric ) {
+ return !isNaN( numeric );
+ };
+
+ var goto = function( event ) {
+ if ( ( !event ) || ( !event.target ) ) {
+ return;
+ }
+
+ var data = event.target.dataset;
+ var steps = document.querySelectorAll( ".step" );
+
+ // Data-goto-key-list="" & data-goto-next-list="" //////////////////////////////////////////
+ if ( data.gotoKeyList !== undefined &&
+ data.gotoNextList !== undefined &&
+ event.origEvent !== undefined &&
+ event.origEvent.key !== undefined ) {
+ var keylist = data.gotoKeyList.split( " " );
+ var nextlist = data.gotoNextList.split( " " );
+
+ if ( keylist.length !== nextlist.length ) {
+ window.console.log(
+ "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:"
+ );
+ window.console.log( keylist );
+ window.console.log( nextlist );
+
+ // Don't return, allow the other categories to work despite this error
+ } else {
+ var index = keylist.indexOf( event.origEvent.key );
+ if ( index >= 0 ) {
+ var next = nextlist[ index ];
+ if ( isNumber( next ) ) {
+ event.detail.next = steps[ next ];
+
+ // If the new next element has its own transitionDuration, we're responsible
+ // for setting that on the event as well
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration,
+ event.detail.transitionDuration
+ );
+ return;
+ } else {
+ var newTarget = document.getElementById( next );
+ if ( newTarget && newTarget.classList.contains( "step" ) ) {
+ event.detail.next = newTarget;
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration,
+ event.detail.transitionDuration
+ );
+ return;
+ } else {
+ window.console.log( "impress goto plugin: " + next +
+ " is not a step in this impress presentation." );
+ }
+ }
+ }
+ }
+ }
+
+ // Data-goto-next="" & data-goto-prev="" ///////////////////////////////////////////////////
+
+ // Handle event.target data-goto-next attribute
+ if ( isNumber( data.gotoNext ) && event.detail.reason === "next" ) {
+ event.detail.next = steps[ data.gotoNext ];
+
+ // If the new next element has its own transitionDuration, we're responsible for setting
+ // that on the event as well
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+ );
+ return;
+ }
+ if ( data.gotoNext && event.detail.reason === "next" ) {
+ var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line
+ if ( newTarget && newTarget.classList.contains( "step" ) ) {
+ event.detail.next = newTarget;
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration,
+ event.detail.transitionDuration
+ );
+ return;
+ } else {
+ window.console.log( "impress goto plugin: " + data.gotoNext +
+ " is not a step in this impress presentation." );
+ }
+ }
+
+ // Handle event.target data-goto-prev attribute
+ if ( isNumber( data.gotoPrev ) && event.detail.reason === "prev" ) {
+ event.detail.next = steps[ data.gotoPrev ];
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+ );
+ return;
+ }
+ if ( data.gotoPrev && event.detail.reason === "prev" ) {
+ var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line
+ if ( newTarget && newTarget.classList.contains( "step" ) ) {
+ event.detail.next = newTarget;
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+ );
+ return;
+ } else {
+ window.console.log( "impress goto plugin: " + data.gotoPrev +
+ " is not a step in this impress presentation." );
+ }
+ }
+
+ // Data-goto="" ///////////////////////////////////////////////////////////////////////////
+
+ // Handle event.target data-goto attribute
+ if ( isNumber( data.goto ) ) {
+ event.detail.next = steps[ data.goto ];
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+ );
+ return;
+ }
+ if ( data.goto ) {
+ var newTarget = document.getElementById( data.goto ); // jshint ignore:line
+ if ( newTarget && newTarget.classList.contains( "step" ) ) {
+ event.detail.next = newTarget;
+ event.detail.transitionDuration = lib.util.toNumber(
+ event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
+ );
+ return;
+ } else {
+ window.console.log( "impress goto plugin: " + data.goto +
+ " is not a step in this impress presentation." );
+ }
+ }
+ };
+
+ // Register the plugin to be called in pre-stepleave phase
+ impress.addPreStepLeavePlugin( goto );
+
+} )( document, window );
+
+
+/**
+ * Help popup plugin
+ *
+ * Example:
+ *
+ *
+ *
+ *
+ * For developers:
+ *
+ * Typical use for this plugin, is for plugins that support some keypress, to add a line
+ * to the help popup produced by this plugin. For example "P: Presenter console".
+ *
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ * Released under the MIT license.
+ */
+/* global window, document */
+
+( function( document, window ) {
+ "use strict";
+ var rows = [];
+ var timeoutHandle;
+
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( "CustomEvent" );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ var renderHelpDiv = function() {
+ var helpDiv = document.getElementById( "impress-help" );
+ if ( helpDiv ) {
+ var html = [];
+ for ( var row in rows ) {
+ for ( var arrayItem in row ) {
+ html.push( rows[ row ][ arrayItem ] );
+ }
+ }
+ if ( html ) {
+ helpDiv.innerHTML = "
\n" + html.join( "\n" ) + "
\n";
+ }
+ }
+ };
+
+ var toggleHelp = function() {
+ var helpDiv = document.getElementById( "impress-help" );
+ if ( !helpDiv ) {
+ return;
+ }
+
+ if ( helpDiv.style.display === "block" ) {
+ helpDiv.style.display = "none";
+ } else {
+ helpDiv.style.display = "block";
+ window.clearTimeout( timeoutHandle );
+ }
+ };
+
+ document.addEventListener( "keyup", function( event ) {
+
+ if ( event.keyCode === 72 || event.keyCode === 191 ) { // "h" || "?"
+ event.preventDefault();
+ toggleHelp();
+ }
+ }, false );
+
+ // API
+ // Other plugins can add help texts, typically if they support an action on a keypress.
+ /**
+ * Add a help text to the help popup.
+ *
+ * :param: e.detail.command Example: "H"
+ * :param: e.detail.text Example: "Show this help."
+ * :param: e.detail.row Row index from 0 to 9 where to place this help text. Example: 0
+ */
+ document.addEventListener( "impress:help:add", function( e ) {
+
+ // The idea is for the sender of the event to supply a unique row index, used for sorting.
+ // But just in case two plugins would ever use the same row index, we wrap each row into
+ // its own array. If there are more than one entry for the same index, they are shown in
+ // first come, first serve ordering.
+ var rowIndex = e.detail.row;
+ if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) {
+ rows[ rowIndex ] = [];
+ }
+ rows[ e.detail.row ].push( "
" + e.detail.command + "
" +
+ e.detail.text + "
" );
+ renderHelpDiv();
+ } );
+
+ document.addEventListener( "impress:init", function( e ) {
+ renderHelpDiv();
+
+ // At start, show the help for 7 seconds.
+ var helpDiv = document.getElementById( "impress-help" );
+ if ( helpDiv ) {
+ helpDiv.style.display = "block";
+ timeoutHandle = window.setTimeout( function() {
+ var helpDiv = document.getElementById( "impress-help" );
+ helpDiv.style.display = "none";
+ }, 7000 );
+
+ // Regster callback to empty the help div on teardown
+ var api = e.detail.api;
+ api.lib.gc.pushCallback( function() {
+ window.clearTimeout( timeoutHandle );
+ helpDiv.style.display = "";
+ helpDiv.innerHTML = "";
+ rows = [];
+ } );
+ }
+
+ // Use our own API to register the help text for "h"
+ triggerEvent( document, "impress:help:add",
+ { command: "H", text: "Show this help", row: 0 } );
+ } );
+
+} )( document, window );
+
+
+/**
+ * Adds a presenter console to impress.js
+ *
+ * MIT Licensed, see license.txt.
+ *
+ * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
+ *
+ * version: 1.3-dev
+ *
+ */
+
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global navigator, top, setInterval, clearInterval, document, window */
+
+( function( document, window ) {
+ 'use strict';
+
+ // TODO: Move this to src/lib/util.js
+ var triggerEvent = function( el, eventName, detail ) {
+ var event = document.createEvent( 'CustomEvent' );
+ event.initCustomEvent( eventName, true, true, detail );
+ el.dispatchEvent( event );
+ };
+
+ // Create Language object depending on browsers language setting
+ var lang;
+ switch ( navigator.language ) {
+ case 'de':
+ lang = {
+ 'noNotes': '
Keine Notizen hierzu
',
+ 'restart': 'Neustart',
+ 'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',
+ 'prev': 'zurück',
+ 'next': 'weiter',
+ 'loading': 'initalisiere',
+ 'ready': 'Bereit',
+ 'moving': 'in Bewegung',
+ 'useAMPM': false
+ };
+ break;
+ case 'zh-CN':
+ case 'zh-cn':
+ lang = {
+ 'noNotes': '
+ if ( root.dataset.consoleAutolaunch === 'true' ) {
+ open();
+ }
+ };
+
+ var init = function( cssConsole, cssIframe ) {
+ if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&
+ ( cssIframe === undefined || cssIframe === cssFileIframeOldDefault ) ) {
+ window.console.log( 'impressConsole().init() is deprecated. ' +
+ 'impressConsole is now initialized automatically when you ' +
+ 'call impress().init().' );
+ }
+ _init( cssConsole, cssIframe );
+ };
+
+ // New API for impress.js plugins is based on using events
+ root.addEventListener( 'impress:console:open', function() {
+ open();
+ } );
+
+ /**
+ * Register a key code to an event handler
+ *
+ * :param: event.detail.keyCodes List of key codes
+ * :param: event.detail.handler A function registered as the event handler
+ * :param: event.detail.window The console window to register the keycode in
+ */
+ root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {
+ registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );
+ } );
+
+ // Return the object
+ allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,
+ registerKeyEvent: registerKeyEvent, _init: _init };
+ return allConsoles[ rootId ];
+
+ };
+
+ // This initializes impressConsole automatically when initializing impress itself
+ document.addEventListener( 'impress:init', function( event ) {
+
+ // Note: impressConsole wants the id string, not the DOM element directly
+ impressConsole( event.target.id )._init();
+
+ // Add 'P' to the help popup
+ triggerEvent( document, 'impress:help:add',
+ { command: 'P', text: 'Presenter console', row: 10 } );
+ } );
+
+ // Returns a string to be used inline as a css `;
+ };
+
+} )( document, window );
+
+/**
+ * Media Plugin
+ *
+ * This plugin will do the following things:
+ *
+ * - Add a special class when playing (body.impress-media-video-playing
+ * and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused
+ * and body.impress-media-audio-paused) (removing them when ending).
+ * This can be useful for example for darkening the background or fading out other elements
+ * while a video is playing.
+ * Only media at the current step are taken into account. All classes are removed when leaving
+ * a step.
+ *
+ * - Introduce the following new data attributes:
+ *
+ * - data-media-autoplay="true": Autostart media when entering its step.
+ * - data-media-autostop="true": Stop media (= pause and reset to start), when leaving its
+ * step.
+ * - data-media-autopause="true": Pause media but keep current time when leaving its step.
+ *
+ * When these attributes are added to a step they are inherited by all media on this step.
+ * Of course this setting can be overwritten by adding different attributes to inidvidual
+ * media.
+ *
+ * The same rule applies when this attributes is added to the root element. Settings can be
+ * overwritten for individual steps and media.
+ *
+ * Examples:
+ * - data-media-autoplay="true" data-media-autostop="true": start media on enter, stop on
+ * leave, restart from beginning when re-entering the step.
+ *
+ * - data-media-autoplay="true" data-media-autopause="true": start media on enter, pause on
+ * leave, resume on re-enter
+ *
+ * - data-media-autoplay="true" data-media-autostop="true" data-media-autopause="true": start
+ * media on enter, stop on leave (stop overwrites pause).
+ *
+ * - data-media-autoplay="true" data-media-autopause="false": let media start automatically
+ * when entering a step and let it play when leaving the step.
+ *
+ * -
...
+ * All media is startet automatically on all steps except the one that has the
+ * data-media-autoplay="false" attribute.
+ *
+ * - Pro tip: Use