Samo Penic
2019-10-23 0d52a3fb8927cb46709001600afb7ffa2474471f
commit | author | age
c43c7b 1 // This file was automatically generated from files in src/ directory.
SP 2
3 /**
4  * impress.js
5  *
6  * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
7  * in modern browsers and inspired by the idea behind prezi.com.
8  *
9  *
10  * Copyright 2011-2012 Bartek Szopka (@bartaz), 2016-2018 Henrik Ingo (@henrikingo)
11  *
12  * Released under the MIT and GPL Licenses.
13  *
14  * ------------------------------------------------
15  *  author:  Bartek Szopka, Henrik Ingo
16  *  version: 1.0.0
17  *  url:     http://impress.js.org
18  *  source:  http://github.com/impress/impress.js/
19  */
20
21 // You are one of those who like to know how things work inside?
22 // Let me show you the cogs that make impress.js run...
23 ( function( document, window ) {
24     "use strict";
25     var lib;
26
27     // HELPER FUNCTIONS
28
29     // `pfx` is a function that takes a standard CSS property name as a parameter
30     // and returns it's prefixed version valid for current browser it runs in.
31     // The code is heavily inspired by Modernizr http://www.modernizr.com/
32     var pfx = ( function() {
33
34         var style = document.createElement( "dummy" ).style,
35             prefixes = "Webkit Moz O ms Khtml".split( " " ),
36             memory = {};
37
38         return function( prop ) {
39             if ( typeof memory[ prop ] === "undefined" ) {
40
41                 var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
42                     props   = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
43
44                 memory[ prop ] = null;
45                 for ( var i in props ) {
46                     if ( style[ props[ i ] ] !== undefined ) {
47                         memory[ prop ] = props[ i ];
48                         break;
49                     }
50                 }
51
52             }
53
54             return memory[ prop ];
55         };
56
57     } )();
58
59     var validateOrder = function( order, fallback ) {
60         var validChars = "xyz";
61         var returnStr = "";
62         if ( typeof order === "string" ) {
63             for ( var i in order.split( "" ) ) {
64                 if ( validChars.indexOf( order[ i ] ) >= 0 ) {
65                     returnStr += order[ i ];
66
67                     // Each of x,y,z can be used only once.
68                     validChars = validChars.split( order[ i ] ).join( "" );
69                 }
70             }
71         }
72         if ( returnStr ) {
73             return returnStr;
74         } else if ( fallback !== undefined ) {
75             return fallback;
76         } else {
77             return "xyz";
78         }
79     };
80
81     // `css` function applies the styles given in `props` object to the element
82     // given as `el`. It runs all property names through `pfx` function to make
83     // sure proper prefixed version of the property is used.
84     var css = function( el, props ) {
85         var key, pkey;
86         for ( key in props ) {
87             if ( props.hasOwnProperty( key ) ) {
88                 pkey = pfx( key );
89                 if ( pkey !== null ) {
90                     el.style[ pkey ] = props[ key ];
91                 }
92             }
93         }
94         return el;
95     };
96
97     // `translate` builds a translate transform string for given data.
98     var translate = function( t ) {
99         return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
100     };
101
102     // `rotate` builds a rotate transform string for given data.
103     // By default the rotations are in X Y Z order that can be reverted by passing `true`
104     // as second parameter.
105     var rotate = function( r, revert ) {
106         var order = r.order ? r.order : "xyz";
107         var css = "";
108         var axes = order.split( "" );
109         if ( revert ) {
110             axes = axes.reverse();
111         }
112
113         for ( var i = 0; i < axes.length; i++ ) {
114             css += " rotate" + axes[ i ].toUpperCase() + "(" + r[ axes[ i ] ] + "deg)";
115         }
116         return css;
117     };
118
119     // `scale` builds a scale transform string for given data.
120     var scale = function( s ) {
121         return " scale(" + s + ") ";
122     };
123
124     // `computeWindowScale` counts the scale factor between window size and size
125     // defined for the presentation in the config.
126     var computeWindowScale = function( config ) {
127         var hScale = window.innerHeight / config.height,
128             wScale = window.innerWidth / config.width,
129             scale = hScale > wScale ? wScale : hScale;
130
131         if ( config.maxScale && scale > config.maxScale ) {
132             scale = config.maxScale;
133         }
134
135         if ( config.minScale && scale < config.minScale ) {
136             scale = config.minScale;
137         }
138
139         return scale;
140     };
141
142     // CHECK SUPPORT
143     var body = document.body;
144     var impressSupported =
145
146                           // Browser should support CSS 3D transtorms
147                            ( pfx( "perspective" ) !== null ) &&
148
149                           // And `classList` and `dataset` APIs
150                            ( body.classList ) &&
151                            ( body.dataset );
152
153     if ( !impressSupported ) {
154
155         // We can't be sure that `classList` is supported
156         body.className += " impress-not-supported ";
157     }
158
159     // GLOBALS AND DEFAULTS
160
161     // This is where the root elements of all impress.js instances will be kept.
162     // Yes, this means you can have more than one instance on a page, but I'm not
163     // sure if it makes any sense in practice ;)
164     var roots = {};
165
166     var preInitPlugins = [];
167     var preStepLeavePlugins = [];
168
169     // Some default config values.
170     var defaults = {
171         width: 1024,
172         height: 768,
173         maxScale: 1,
174         minScale: 0,
175
176         perspective: 1000,
177
178         transitionDuration: 1000
179     };
180
181     // It's just an empty function ... and a useless comment.
182     var empty = function() { return false; };
183
184     // IMPRESS.JS API
185
186     // And that's where interesting things will start to happen.
187     // It's the core `impress` function that returns the impress.js API
188     // for a presentation based on the element with given id ("impress"
189     // by default).
190     var impress = window.impress = function( rootId ) {
191
192         // If impress.js is not supported by the browser return a dummy API
193         // it may not be a perfect solution but we return early and avoid
194         // running code that may use features not implemented in the browser.
195         if ( !impressSupported ) {
196             return {
197                 init: empty,
198                 goto: empty,
199                 prev: empty,
200                 next: empty,
201                 swipe: empty,
202                 tear: empty,
203                 lib: {}
204             };
205         }
206
207         rootId = rootId || "impress";
208
209         // If given root is already initialized just return the API
210         if ( roots[ "impress-root-" + rootId ] ) {
211             return roots[ "impress-root-" + rootId ];
212         }
213
214         // The gc library depends on being initialized before we do any changes to DOM.
215         lib = initLibraries( rootId );
216
217         body.classList.remove( "impress-not-supported" );
218         body.classList.add( "impress-supported" );
219
220         // Data of all presentation steps
221         var stepsData = {};
222
223         // Element of currently active step
224         var activeStep = null;
225
226         // Current state (position, rotation and scale) of the presentation
227         var currentState = null;
228
229         // Array of step elements
230         var steps = null;
231
232         // Configuration options
233         var config = null;
234
235         // Scale factor of the browser window
236         var windowScale = null;
237
238         // Root presentation elements
239         var root = lib.util.byId( rootId );
240         var canvas = document.createElement( "div" );
241
242         var initialized = false;
243
244         // STEP EVENTS
245         //
246         // There are currently two step events triggered by impress.js
247         // `impress:stepenter` is triggered when the step is shown on the
248         // screen (the transition from the previous one is finished) and
249         // `impress:stepleave` is triggered when the step is left (the
250         // transition to next step just starts).
251
252         // Reference to last entered step
253         var lastEntered = null;
254
255         // `onStepEnter` is called whenever the step element is entered
256         // but the event is triggered only if the step is different than
257         // last entered step.
258         // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as
259         // after screen resize. In this case - more precisely, in any case - we trigger a
260         // `impress:steprefresh` event.
261         var onStepEnter = function( step ) {
262             if ( lastEntered !== step ) {
263                 lib.util.triggerEvent( step, "impress:stepenter" );
264                 lastEntered = step;
265             }
266             lib.util.triggerEvent( step, "impress:steprefresh" );
267         };
268
269         // `onStepLeave` is called whenever the currentStep element is left
270         // but the event is triggered only if the currentStep is the same as
271         // lastEntered step.
272         var onStepLeave = function( currentStep, nextStep ) {
273             if ( lastEntered === currentStep ) {
274                 lib.util.triggerEvent( currentStep, "impress:stepleave", { next: nextStep } );
275                 lastEntered = null;
276             }
277         };
278
279         // `initStep` initializes given step element by reading data from its
280         // data attributes and setting correct styles.
281         var initStep = function( el, idx ) {
282             var data = el.dataset,
283                 step = {
284                     translate: {
285                         x: lib.util.toNumber( data.x ),
286                         y: lib.util.toNumber( data.y ),
287                         z: lib.util.toNumber( data.z )
288                     },
289                     rotate: {
290                         x: lib.util.toNumber( data.rotateX ),
291                         y: lib.util.toNumber( data.rotateY ),
292                         z: lib.util.toNumber( data.rotateZ || data.rotate ),
293                         order: validateOrder( data.rotateOrder )
294                     },
295                     scale: lib.util.toNumber( data.scale, 1 ),
296                     transitionDuration: lib.util.toNumber(
297                         data.transitionDuration, config.transitionDuration
298                     ),
299                     el: el
300                 };
301
302             if ( !el.id ) {
303                 el.id = "step-" + ( idx + 1 );
304             }
305
306             stepsData[ "impress-" + el.id ] = step;
307
308             css( el, {
309                 position: "absolute",
310                 transform: "translate(-50%,-50%)" +
311                            translate( step.translate ) +
312                            rotate( step.rotate ) +
313                            scale( step.scale ),
314                 transformStyle: "preserve-3d"
315             } );
316         };
317
318         // Initialize all steps.
319         // Read the data-* attributes, store in internal stepsData, and render with CSS.
320         var initAllSteps = function() {
321             steps = lib.util.$$( ".step", root );
322             steps.forEach( initStep );
323         };
324
325         // `init` API function that initializes (and runs) the presentation.
326         var init = function() {
327             if ( initialized ) { return; }
328             execPreInitPlugins( root );
329
330             // First we set up the viewport for mobile devices.
331             // For some reason iPad goes nuts when it is not done properly.
332             var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" );
333             meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
334             if ( meta.parentNode !== document.head ) {
335                 meta.name = "viewport";
336                 document.head.appendChild( meta );
337             }
338
339             // Initialize configuration object
340             var rootData = root.dataset;
341             config = {
342                 width: lib.util.toNumber( rootData.width, defaults.width ),
343                 height: lib.util.toNumber( rootData.height, defaults.height ),
344                 maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),
345                 minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),
346                 perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),
347                 transitionDuration: lib.util.toNumber(
348                     rootData.transitionDuration, defaults.transitionDuration
349                 )
350             };
351
352             windowScale = computeWindowScale( config );
353
354             // Wrap steps with "canvas" element
355             lib.util.arrayify( root.childNodes ).forEach( function( el ) {
356                 canvas.appendChild( el );
357             } );
358             root.appendChild( canvas );
359
360             // Set initial styles
361             document.documentElement.style.height = "100%";
362
363             css( body, {
364                 height: "100%",
365                 overflow: "hidden"
366             } );
367
368             var rootStyles = {
369                 position: "absolute",
370                 transformOrigin: "top left",
371                 transition: "all 0s ease-in-out",
372                 transformStyle: "preserve-3d"
373             };
374
375             css( root, rootStyles );
376             css( root, {
377                 top: "50%",
378                 left: "50%",
379                 perspective: ( config.perspective / windowScale ) + "px",
380                 transform: scale( windowScale )
381             } );
382             css( canvas, rootStyles );
383
384             body.classList.remove( "impress-disabled" );
385             body.classList.add( "impress-enabled" );
386
387             // Get and init steps
388             initAllSteps();
389
390             // Set a default initial state of the canvas
391             currentState = {
392                 translate: { x: 0, y: 0, z: 0 },
393                 rotate:    { x: 0, y: 0, z: 0, order: "xyz" },
394                 scale:     1
395             };
396
397             initialized = true;
398
399             lib.util.triggerEvent( root, "impress:init",
400                                    { api: roots[ "impress-root-" + rootId ] } );
401         };
402
403         // `getStep` is a helper function that returns a step element defined by parameter.
404         // If a number is given, step with index given by the number is returned, if a string
405         // is given step element with such id is returned, if DOM element is given it is returned
406         // if it is a correct step element.
407         var getStep = function( step ) {
408             if ( typeof step === "number" ) {
409                 step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
410             } else if ( typeof step === "string" ) {
411                 step = lib.util.byId( step );
412             }
413             return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
414         };
415
416         // Used to reset timeout for `impress:stepenter` event
417         var stepEnterTimeout = null;
418
419         // `goto` API function that moves to step given as `el` parameter (by index, id or element).
420         // `duration` optionally given as second parameter, is the transition duration in css.
421         // `reason` is the string "next", "prev" or "goto" (default) and will be made available to
422         // preStepLeave plugins.
423         // `origEvent` may contain event that caused the call to goto, such as a key press event
424         var goto = function( el, duration, reason, origEvent ) {
425             reason = reason || "goto";
426             origEvent = origEvent || null;
427
428             if ( !initialized ) {
429                 return false;
430             }
431
432             // Re-execute initAllSteps for each transition. This allows to edit step attributes
433             // dynamically, such as change their coordinates, or even remove or add steps, and have
434             // that change apply when goto() is called.
435             initAllSteps();
436
437             if ( !( el = getStep( el ) ) ) {
438                 return false;
439             }
440
441             // Sometimes it's possible to trigger focus on first link with some keyboard action.
442             // Browser in such a case tries to scroll the page to make this element visible
443             // (even that body overflow is set to hidden) and it breaks our careful positioning.
444             //
445             // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
446             // whenever slide is selected
447             //
448             // If you are reading this and know any better way to handle it, I'll be glad to hear
449             // about it!
450             window.scrollTo( 0, 0 );
451
452             var step = stepsData[ "impress-" + el.id ];
453             duration = ( duration !== undefined ? duration : step.transitionDuration );
454
455             // If we are in fact moving to another step, start with executing the registered
456             // preStepLeave plugins.
457             if ( activeStep && activeStep !== el ) {
458                 var event = { target: activeStep, detail: {} };
459                 event.detail.next = el;
460                 event.detail.transitionDuration = duration;
461                 event.detail.reason = reason;
462                 if ( origEvent ) {
463                     event.origEvent = origEvent;
464                 }
465
466                 if ( execPreStepLeavePlugins( event ) === false ) {
467
468                     // PreStepLeave plugins are allowed to abort the transition altogether, by
469                     // returning false.
470                     // see stop and substep plugins for an example of doing just that
471                     return false;
472                 }
473
474                 // Plugins are allowed to change the detail values
475                 el = event.detail.next;
476                 step = stepsData[ "impress-" + el.id ];
477                 duration = event.detail.transitionDuration;
478             }
479
480             if ( activeStep ) {
481                 activeStep.classList.remove( "active" );
482                 body.classList.remove( "impress-on-" + activeStep.id );
483             }
484             el.classList.add( "active" );
485
486             body.classList.add( "impress-on-" + el.id );
487
488             // Compute target state of the canvas based on given step
489             var target = {
490                 rotate: {
491                     x: -step.rotate.x,
492                     y: -step.rotate.y,
493                     z: -step.rotate.z,
494                     order: step.rotate.order
495                 },
496                 translate: {
497                     x: -step.translate.x,
498                     y: -step.translate.y,
499                     z: -step.translate.z
500                 },
501                 scale: 1 / step.scale
502             };
503
504             // Check if the transition is zooming in or not.
505             //
506             // This information is used to alter the transition style:
507             // when we are zooming in - we start with move and rotate transition
508             // and the scaling is delayed, but when we are zooming out we start
509             // with scaling down and move and rotation are delayed.
510             var zoomin = target.scale >= currentState.scale;
511
512             duration = lib.util.toNumber( duration, config.transitionDuration );
513             var delay = ( duration / 2 );
514
515             // If the same step is re-selected, force computing window scaling,
516             // because it is likely to be caused by window resize
517             if ( el === activeStep ) {
518                 windowScale = computeWindowScale( config );
519             }
520
521             var targetScale = target.scale * windowScale;
522
523             // Trigger leave of currently active element (if it's not the same step again)
524             if ( activeStep && activeStep !== el ) {
525                 onStepLeave( activeStep, el );
526             }
527
528             // Now we alter transforms of `root` and `canvas` to trigger transitions.
529             //
530             // And here is why there are two elements: `root` and `canvas` - they are
531             // being animated separately:
532             // `root` is used for scaling and `canvas` for translate and rotations.
533             // Transitions on them are triggered with different delays (to make
534             // visually nice and "natural" looking transitions), so we need to know
535             // that both of them are finished.
536             css( root, {
537
538                 // To keep the perspective look similar for different scales
539                 // we need to "scale" the perspective, too
540                 // For IE 11 support we must specify perspective independent
541                 // of transform.
542                 perspective: ( config.perspective / targetScale ) + "px",
543                 transform: scale( targetScale ),
544                 transitionDuration: duration + "ms",
545                 transitionDelay: ( zoomin ? delay : 0 ) + "ms"
546             } );
547
548             css( canvas, {
549                 transform: rotate( target.rotate, true ) + translate( target.translate ),
550                 transitionDuration: duration + "ms",
551                 transitionDelay: ( zoomin ? 0 : delay ) + "ms"
552             } );
553
554             // Here is a tricky part...
555             //
556             // If there is no change in scale or no change in rotation and translation, it means
557             // there was actually no delay - because there was no transition on `root` or `canvas`
558             // elements. We want to trigger `impress:stepenter` event in the correct moment, so
559             // here we compare the current and target values to check if delay should be taken into
560             // account.
561             //
562             // I know that this `if` statement looks scary, but it's pretty simple when you know
563             // what is going on - it's simply comparing all the values.
564             if ( currentState.scale === target.scale ||
565                 ( currentState.rotate.x === target.rotate.x &&
566                   currentState.rotate.y === target.rotate.y &&
567                   currentState.rotate.z === target.rotate.z &&
568                   currentState.translate.x === target.translate.x &&
569                   currentState.translate.y === target.translate.y &&
570                   currentState.translate.z === target.translate.z ) ) {
571                 delay = 0;
572             }
573
574             // Store current state
575             currentState = target;
576             activeStep = el;
577
578             // And here is where we trigger `impress:stepenter` event.
579             // We simply set up a timeout to fire it taking transition duration (and possible delay)
580             // into account.
581             //
582             // I really wanted to make it in more elegant way. The `transitionend` event seemed to
583             // be the best way to do it, but the fact that I'm using transitions on two separate
584             // elements and that the `transitionend` event is only triggered when there was a
585             // transition (change in the values) caused some bugs and made the code really
586             // complicated, cause I had to handle all the conditions separately. And it still
587             // needed a `setTimeout` fallback for the situations when there is no transition at all.
588             // So I decided that I'd rather make the code simpler than use shiny new
589             // `transitionend`.
590             //
591             // If you want learn something interesting and see how it was done with `transitionend`
592             // go back to version 0.5.2 of impress.js:
593             // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
594             window.clearTimeout( stepEnterTimeout );
595             stepEnterTimeout = window.setTimeout( function() {
596                 onStepEnter( activeStep );
597             }, duration + delay );
598
599             return el;
600         };
601
602         // `prev` API function goes to previous step (in document order)
603         // `event` is optional, may contain the event that caused the need to call prev()
604         var prev = function( origEvent ) {
605             var prev = steps.indexOf( activeStep ) - 1;
606             prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
607
608             return goto( prev, undefined, "prev", origEvent );
609         };
610
611         // `next` API function goes to next step (in document order)
612         // `event` is optional, may contain the event that caused the need to call next()
613         var next = function( origEvent ) {
614             var next = steps.indexOf( activeStep ) + 1;
615             next = next < steps.length ? steps[ next ] : steps[ 0 ];
616
617             return goto( next, undefined, "next", origEvent );
618         };
619
620         // Swipe for touch devices by @and3rson.
621         // Below we extend the api to control the animation between the currently
622         // active step and a presumed next/prev step. See touch plugin for
623         // an example of using this api.
624
625         // Helper function
626         var interpolate = function( a, b, k ) {
627             return a + ( b - a ) * k;
628         };
629
630         // Animate a swipe.
631         //
632         // Pct is a value between -1.0 and +1.0, designating the current length
633         // of the swipe.
634         //
635         // If pct is negative, swipe towards the next() step, if positive,
636         // towards the prev() step.
637         //
638         // Note that pre-stepleave plugins such as goto can mess with what is a
639         // next() and prev() step, so we need to trigger the pre-stepleave event
640         // here, even if a swipe doesn't guarantee that the transition will
641         // actually happen.
642         //
643         // Calling swipe(), with any value of pct, won't in itself cause a
644         // transition to happen, this is just to animate the swipe. Once the
645         // transition is committed - such as at a touchend event - caller is
646         // responsible for also calling prev()/next() as appropriate.
647         //
648         // Note: For now, this function is made available to be used by the swipe plugin (which
649         // is the UI counterpart to this). It is a semi-internal API and intentionally not
650         // documented in DOCUMENTATION.md.
651         var swipe = function( pct ) {
652             if ( Math.abs( pct ) > 1 ) {
653                 return;
654             }
655
656             // Prepare & execute the preStepLeave event
657             var event = { target: activeStep, detail: {} };
658             event.detail.swipe = pct;
659
660             // Will be ignored within swipe animation, but just in case a plugin wants to read this,
661             // humor them
662             event.detail.transitionDuration = config.transitionDuration;
663             var idx; // Needed by jshint
664             if ( pct < 0 ) {
665                 idx = steps.indexOf( activeStep ) + 1;
666                 event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
667                 event.detail.reason = "next";
668             } else if ( pct > 0 ) {
669                 idx = steps.indexOf( activeStep ) - 1;
670                 event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
671                 event.detail.reason = "prev";
672             } else {
673
674                 // No move
675                 return;
676             }
677             if ( execPreStepLeavePlugins( event ) === false ) {
678
679                 // If a preStepLeave plugin wants to abort the transition, don't animate a swipe
680                 // For stop, this is probably ok. For substep, the plugin it self might want to do
681                 // some animation, but that's not the current implementation.
682                 return false;
683             }
684             var nextElement = event.detail.next;
685
686             var nextStep = stepsData[ "impress-" + nextElement.id ];
687
688             // If the same step is re-selected, force computing window scaling,
689             var nextScale = nextStep.scale * windowScale;
690             var k = Math.abs( pct );
691
692             var interpolatedStep = {
693                 translate: {
694                     x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
695                     y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
696                     z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
697                 },
698                 rotate: {
699                     x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
700                     y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
701                     z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
702
703                     // Unfortunately there's a discontinuity if rotation order changes. Nothing I
704                     // can do about it?
705                     order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
706                 },
707                 scale: interpolate( currentState.scale, nextScale, k )
708             };
709
710             css( root, {
711
712                 // To keep the perspective look similar for different scales
713                 // we need to 'scale' the perspective, too
714                 perspective: config.perspective / interpolatedStep.scale + "px",
715                 transform: scale( interpolatedStep.scale ),
716                 transitionDuration: "0ms",
717                 transitionDelay: "0ms"
718             } );
719
720             css( canvas, {
721                 transform: rotate( interpolatedStep.rotate, true ) +
722                            translate( interpolatedStep.translate ),
723                 transitionDuration: "0ms",
724                 transitionDelay: "0ms"
725             } );
726         };
727
728         // Teardown impress
729         // Resets the DOM to the state it was before impress().init() was called.
730         // (If you called impress(rootId).init() for multiple different rootId's, then you must
731         // also call tear() once for each of them.)
732         var tear = function() {
733             lib.gc.teardown();
734             delete roots[ "impress-root-" + rootId ];
735         };
736
737         // Adding some useful classes to step elements.
738         //
739         // All the steps that have not been shown yet are given `future` class.
740         // When the step is entered the `future` class is removed and the `present`
741         // class is given. When the step is left `present` class is replaced with
742         // `past` class.
743         //
744         // So every step element is always in one of three possible states:
745         // `future`, `present` and `past`.
746         //
747         // There classes can be used in CSS to style different types of steps.
748         // For example the `present` class can be used to trigger some custom
749         // animations when step is shown.
750         lib.gc.addEventListener( root, "impress:init", function() {
751
752             // STEP CLASSES
753             steps.forEach( function( step ) {
754                 step.classList.add( "future" );
755             } );
756
757             lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
758                 event.target.classList.remove( "past" );
759                 event.target.classList.remove( "future" );
760                 event.target.classList.add( "present" );
761             }, false );
762
763             lib.gc.addEventListener( root, "impress:stepleave", function( event ) {
764                 event.target.classList.remove( "present" );
765                 event.target.classList.add( "past" );
766             }, false );
767
768         }, false );
769
770         // Adding hash change support.
771         lib.gc.addEventListener( root, "impress:init", function() {
772
773             // Last hash detected
774             var lastHash = "";
775
776             // `#/step-id` is used instead of `#step-id` to prevent default browser
777             // scrolling to element in hash.
778             //
779             // And it has to be set after animation finishes, because in Chrome it
780             // makes transtion laggy.
781             // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
782             lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
783                 window.location.hash = lastHash = "#/" + event.target.id;
784             }, false );
785
786             lib.gc.addEventListener( window, "hashchange", function() {
787
788                 // When the step is entered hash in the location is updated
789                 // (just few lines above from here), so the hash change is
790                 // triggered and we would call `goto` again on the same element.
791                 //
792                 // To avoid this we store last entered hash and compare.
793                 if ( window.location.hash !== lastHash ) {
794                     goto( lib.util.getElementFromHash() );
795                 }
796             }, false );
797
798             // START
799             // by selecting step defined in url or first step of the presentation
800             goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );
801         }, false );
802
803         body.classList.add( "impress-disabled" );
804
805         // Store and return API for given impress.js root element
806         return ( roots[ "impress-root-" + rootId ] = {
807             init: init,
808             goto: goto,
809             next: next,
810             prev: prev,
811             swipe: swipe,
812             tear: tear,
813             lib: lib
814         } );
815
816     };
817
818     // Flag that can be used in JS to check if browser have passed the support test
819     impress.supported = impressSupported;
820
821     // ADD and INIT LIBRARIES
822     // Library factories are defined in src/lib/*.js, and register themselves by calling
823     // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment
824     // the API with library functions when client calls impress(rootId).
825     // See src/lib/README.md for clearer example.
826     // (Advanced usage: For different values of rootId, a different instance of the libaries are
827     // generated, in case they need to hold different state for different root elements.)
828     var libraryFactories = {};
829     impress.addLibraryFactory = function( obj ) {
830         for ( var libname in obj ) {
831             if ( obj.hasOwnProperty( libname ) ) {
832                 libraryFactories[ libname ] = obj[ libname ];
833             }
834         }
835     };
836
837     // Call each library factory, and return the lib object that is added to the api.
838     var initLibraries = function( rootId ) { //jshint ignore:line
839         var lib = {};
840         for ( var libname in libraryFactories ) {
841             if ( libraryFactories.hasOwnProperty( libname ) ) {
842                 if ( lib[ libname ] !== undefined ) {
843                     throw "impress.js ERROR: Two libraries both tried to use libname: " +  libname;
844                 }
845                 lib[ libname ] = libraryFactories[ libname ]( rootId );
846             }
847         }
848         return lib;
849     };
850
851     // `addPreInitPlugin` allows plugins to register a function that should
852     // be run (synchronously) at the beginning of init, before
853     // impress().init() itself executes.
854     impress.addPreInitPlugin = function( plugin, weight ) {
855         weight = parseInt( weight ) || 10;
856         if ( weight <= 0 ) {
857             throw "addPreInitPlugin: weight must be a positive integer";
858         }
859
860         if ( preInitPlugins[ weight ] === undefined ) {
861             preInitPlugins[ weight ] = [];
862         }
863         preInitPlugins[ weight ].push( plugin );
864     };
865
866     // Called at beginning of init, to execute all pre-init plugins.
867     var execPreInitPlugins = function( root ) { //jshint ignore:line
868         for ( var i = 0; i < preInitPlugins.length; i++ ) {
869             var thisLevel = preInitPlugins[ i ];
870             if ( thisLevel !== undefined ) {
871                 for ( var j = 0; j < thisLevel.length; j++ ) {
872                     thisLevel[ j ]( root );
873                 }
874             }
875         }
876     };
877
878     // `addPreStepLeavePlugin` allows plugins to register a function that should
879     // be run (synchronously) at the beginning of goto()
880     impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line
881         weight = parseInt( weight ) || 10;
882         if ( weight <= 0 ) {
883             throw "addPreStepLeavePlugin: weight must be a positive integer";
884         }
885
886         if ( preStepLeavePlugins[ weight ] === undefined ) {
887             preStepLeavePlugins[ weight ] = [];
888         }
889         preStepLeavePlugins[ weight ].push( plugin );
890     };
891
892     // Called at beginning of goto(), to execute all preStepLeave plugins.
893     var execPreStepLeavePlugins = function( event ) { //jshint ignore:line
894         for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {
895             var thisLevel = preStepLeavePlugins[ i ];
896             if ( thisLevel !== undefined ) {
897                 for ( var j = 0; j < thisLevel.length; j++ ) {
898                     if ( thisLevel[ j ]( event ) === false ) {
899
900                         // If a plugin returns false, the stepleave event (and related transition)
901                         // is aborted
902                         return false;
903                     }
904                 }
905             }
906         }
907     };
908
909 } )( document, window );
910
911 // THAT'S ALL FOLKS!
912 //
913 // Thanks for reading it all.
914 // Or thanks for scrolling down and reading the last part.
915 //
916 // I've learnt a lot when building impress.js and I hope this code and comments
917 // will help somebody learn at least some part of it.
918
919 /**
920  * Garbage collection utility
921  *
922  * This library allows plugins to add elements and event listeners they add to the DOM. The user
923  * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that
924  * the document is in the state it was before calling `impress().init()`.
925  *
926  * In addition to just adding elements and event listeners to the garbage collector, plugins
927  * can also register callback functions to do arbitrary cleanup upon teardown.
928  *
929  * Henrik Ingo (c) 2016
930  * MIT License
931  */
932
933 ( function( document, window ) {
934     "use strict";
935     var roots = [];
936     var rootsCount = 0;
937     var startingState = { roots: [] };
938
939     var libraryFactory = function( rootId ) {
940         if ( roots[ rootId ] ) {
941             return roots[ rootId ];
942         }
943
944         // Per root global variables (instance variables?)
945         var elementList = [];
946         var eventListenerList = [];
947         var callbackList = [];
948
949         recordStartingState( rootId );
950
951         // LIBRARY FUNCTIONS
952         // Definitions of the library functions we return as an object at the end
953
954         // `pushElement` adds a DOM element to the gc stack
955         var pushElement = function( element ) {
956             elementList.push( element );
957         };
958
959         // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement
960         var appendChild = function( parent, element ) {
961             parent.appendChild( element );
962             pushElement( element );
963         };
964
965         // `pushEventListener` adds an event listener to the gc stack
966         var pushEventListener = function( target, type, listenerFunction ) {
967             eventListenerList.push( { target:target, type:type, listener:listenerFunction } );
968         };
969
970         // `addEventListener` combines DOM addEventListener with gc.pushEventListener
971         var addEventListener = function( target, type, listenerFunction ) {
972             target.addEventListener( type, listenerFunction );
973             pushEventListener( target, type, listenerFunction );
974         };
975
976         // `pushCallback` If the above utilities are not enough, plugins can add their own callback
977         // function to do arbitrary things.
978         var pushCallback = function( callback ) {
979             callbackList.push( callback );
980         };
981         pushCallback( function( rootId ) { resetStartingState( rootId ); } );
982
983         // `teardown` will
984         // - execute all callbacks in LIFO order
985         // - call `removeChild` on all DOM elements in LIFO order
986         // - call `removeEventListener` on all event listeners in LIFO order
987         // The goal of a teardown is to return to the same state that the DOM was before
988         // `impress().init()` was called.
989         var teardown = function() {
990
991             // Execute the callbacks in LIFO order
992             var i; // Needed by jshint
993             for ( i = callbackList.length - 1; i >= 0; i-- ) {
994                 callbackList[ i ]( rootId );
995             }
996             callbackList = [];
997             for ( i = 0; i < elementList.length; i++ ) {
998                 elementList[ i ].parentElement.removeChild( elementList[ i ] );
999             }
1000             elementList = [];
1001             for ( i = 0; i < eventListenerList.length; i++ ) {
1002                 var target   = eventListenerList[ i ].target;
1003                 var type     = eventListenerList[ i ].type;
1004                 var listener = eventListenerList[ i ].listener;
1005                 target.removeEventListener( type, listener );
1006             }
1007         };
1008
1009         var lib = {
1010             pushElement: pushElement,
1011             appendChild: appendChild,
1012             pushEventListener: pushEventListener,
1013             addEventListener: addEventListener,
1014             pushCallback: pushCallback,
1015             teardown: teardown
1016         };
1017         roots[ rootId ] = lib;
1018         rootsCount++;
1019         return lib;
1020     };
1021
1022     // Let impress core know about the existence of this library
1023     window.impress.addLibraryFactory( { gc: libraryFactory } );
1024
1025     // CORE INIT
1026     // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()
1027     // For the purposes of teardown(), we can use this as an opportunity to save the state
1028     // of a few things in the DOM in their virgin state, before impress().init() did anything.
1029     // Note: These could also be recorded by the code in impress.js core as these values
1030     // are changed, but in an effort to not deviate too much from upstream, I'm adding
1031     // them here rather than the core itself.
1032     var recordStartingState = function( rootId ) {
1033         startingState.roots[ rootId ] = {};
1034         startingState.roots[ rootId ].steps = [];
1035
1036         // Record whether the steps have an id or not
1037         var steps = document.getElementById( rootId ).querySelectorAll( ".step" );
1038         for ( var i = 0; i < steps.length; i++ ) {
1039             var el = steps[ i ];
1040             startingState.roots[ rootId ].steps.push( {
1041                 el: el,
1042                 id: el.getAttribute( "id" )
1043             } );
1044         }
1045
1046         // In the rare case of multiple roots, the following is changed on first init() and
1047         // reset at last tear().
1048         if ( rootsCount === 0 ) {
1049             startingState.body = {};
1050
1051             // It is customary for authors to set body.class="impress-not-supported" as a starting
1052             // value, which can then be removed by impress().init(). But it is not required.
1053             // Remember whether it was there or not.
1054             if ( document.body.classList.contains( "impress-not-supported" ) ) {
1055                 startingState.body.impressNotSupported = true;
1056             } else {
1057                 startingState.body.impressNotSupported = false;
1058             }
1059
1060             // If there's a <meta name="viewport"> element, its contents will be overwritten by init
1061             var metas = document.head.querySelectorAll( "meta" );
1062             for ( i = 0; i < metas.length; i++ ) {
1063                 var m = metas[ i ];
1064                 if ( m.name === "viewport" ) {
1065                     startingState.meta = m.content;
1066                 }
1067             }
1068         }
1069     };
1070
1071     // CORE TEARDOWN
1072     var resetStartingState = function( rootId ) {
1073
1074         // Reset body element
1075         document.body.classList.remove( "impress-enabled" );
1076         document.body.classList.remove( "impress-disabled" );
1077
1078         var root = document.getElementById( rootId );
1079         var activeId = root.querySelector( ".active" ).id;
1080         document.body.classList.remove( "impress-on-" + activeId );
1081
1082         document.documentElement.style.height = "";
1083         document.body.style.height = "";
1084         document.body.style.overflow = "";
1085
1086         // Remove style values from the root and step elements
1087         // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original
1088         // values. A more sophisticated implementation could keep track of original values and then
1089         // reset those.
1090         var steps = root.querySelectorAll( ".step" );
1091         for ( var i = 0; i < steps.length; i++ ) {
1092             steps[ i ].classList.remove( "future" );
1093             steps[ i ].classList.remove( "past" );
1094             steps[ i ].classList.remove( "present" );
1095             steps[ i ].classList.remove( "active" );
1096             steps[ i ].style.position = "";
1097             steps[ i ].style.transform = "";
1098             steps[ i ].style[ "transform-style" ] = "";
1099         }
1100         root.style.position = "";
1101         root.style[ "transform-origin" ] = "";
1102         root.style.transition = "";
1103         root.style[ "transform-style" ] = "";
1104         root.style.top = "";
1105         root.style.left = "";
1106         root.style.transform = "";
1107
1108         // Reset id of steps ("step-1" id's are auto generated)
1109         steps = startingState.roots[ rootId ].steps;
1110         var step;
1111         while ( step = steps.pop() ) {
1112             if ( step.id === null ) {
1113                 step.el.removeAttribute( "id" );
1114             } else {
1115                 step.el.setAttribute( "id", step.id );
1116             }
1117         }
1118         delete startingState.roots[ rootId ];
1119
1120         // Move step div elements away from canvas, then delete canvas
1121         // Note: There's an implicit assumption here that the canvas div is the only child element
1122         // of the root div. If there would be something else, it's gonna be lost.
1123         var canvas = root.firstChild;
1124         var canvasHTML = canvas.innerHTML;
1125         root.innerHTML = canvasHTML;
1126
1127         if ( roots[ rootId ] !== undefined ) {
1128             delete roots[ rootId ];
1129             rootsCount--;
1130         }
1131         if ( rootsCount === 0 ) {
1132
1133             // In the rare case that more than one impress root elements were initialized, these
1134             // are only reset when all are uninitialized.
1135             document.body.classList.remove( "impress-supported" );
1136             if ( startingState.body.impressNotSupported ) {
1137                 document.body.classList.add( "impress-not-supported" );
1138             }
1139
1140             // We need to remove or reset the meta element inserted by impress.js
1141             var metas = document.head.querySelectorAll( "meta" );
1142             for ( i = 0; i < metas.length; i++ ) {
1143                 var m = metas[ i ];
1144                 if ( m.name === "viewport" ) {
1145                     if ( startingState.meta !== undefined ) {
1146                         m.content = startingState.meta;
1147                     } else {
1148                         m.parentElement.removeChild( m );
1149                     }
1150                 }
1151             }
1152         }
1153
1154     };
1155
1156 } )( document, window );
1157
1158 /**
1159  * Common utility functions
1160  *
1161  * Copyright 2011-2012 Bartek Szopka (@bartaz)
1162  * Henrik Ingo (c) 2016
1163  * MIT License
1164  */
1165
1166 ( function( document, window ) {
1167     "use strict";
1168     var roots = [];
1169
1170     var libraryFactory = function( rootId ) {
1171         if ( roots[ rootId ] ) {
1172             return roots[ rootId ];
1173         }
1174
1175         // `$` returns first element for given CSS `selector` in the `context` of
1176         // the given element or whole document.
1177         var $ = function( selector, context ) {
1178             context = context || document;
1179             return context.querySelector( selector );
1180         };
1181
1182         // `$$` return an array of elements for given CSS `selector` in the `context` of
1183         // the given element or whole document.
1184         var $$ = function( selector, context ) {
1185             context = context || document;
1186             return arrayify( context.querySelectorAll( selector ) );
1187         };
1188
1189         // `arrayify` takes an array-like object and turns it into real Array
1190         // to make all the Array.prototype goodness available.
1191         var arrayify = function( a ) {
1192             return [].slice.call( a );
1193         };
1194
1195         // `byId` returns element with given `id` - you probably have guessed that ;)
1196         var byId = function( id ) {
1197             return document.getElementById( id );
1198         };
1199
1200         // `getElementFromHash` returns an element located by id from hash part of
1201         // window location.
1202         var getElementFromHash = function() {
1203
1204             // Get id from url # by removing `#` or `#/` from the beginning,
1205             // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
1206             return byId( window.location.hash.replace( /^#\/?/, "" ) );
1207         };
1208
1209         // Throttling function calls, by Remy Sharp
1210         // http://remysharp.com/2010/07/21/throttling-function-calls/
1211         var throttle = function( fn, delay ) {
1212             var timer = null;
1213             return function() {
1214                 var context = this, args = arguments;
1215                 window.clearTimeout( timer );
1216                 timer = window.setTimeout( function() {
1217                     fn.apply( context, args );
1218                 }, delay );
1219             };
1220         };
1221
1222         // `toNumber` takes a value given as `numeric` parameter and tries to turn
1223         // it into a number. If it is not possible it returns 0 (or other value
1224         // given as `fallback`).
1225         var toNumber = function( numeric, fallback ) {
1226             return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
1227         };
1228
1229         // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
1230         // and triggers it on element given as `el`.
1231         var triggerEvent = function( el, eventName, detail ) {
1232             var event = document.createEvent( "CustomEvent" );
1233             event.initCustomEvent( eventName, true, true, detail );
1234             el.dispatchEvent( event );
1235         };
1236
1237         var lib = {
1238             $: $,
1239             $$: $$,
1240             arrayify: arrayify,
1241             byId: byId,
1242             getElementFromHash: getElementFromHash,
1243             throttle: throttle,
1244             toNumber: toNumber,
1245             triggerEvent: triggerEvent
1246         };
1247         roots[ rootId ] = lib;
1248         return lib;
1249     };
1250
1251     // Let impress core know about the existence of this library
1252     window.impress.addLibraryFactory( { util: libraryFactory } );
1253
1254 } )( document, window );
1255
1256 /**
1257  * Autoplay plugin - Automatically advance slideshow after N seconds
1258  *
1259  * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
1260  * Released under the MIT license.
1261  */
1262 /* global clearTimeout, setTimeout, document */
1263
1264 ( function( document ) {
1265     "use strict";
1266
1267     var autoplayDefault = 0;
1268     var currentStepTimeout = 0;
1269     var api = null;
1270     var timeoutHandle = null;
1271     var root = null;
1272     var util;
1273
1274     // On impress:init, check whether there is a default setting, as well as
1275     // handle step-1.
1276     document.addEventListener( "impress:init", function( event ) {
1277         util = event.detail.api.lib.util;
1278
1279         // Getting API from event data instead of global impress().init().
1280         // You don't even need to know what is the id of the root element
1281         // or anything. `impress:init` event data gives you everything you
1282         // need to control the presentation that was just initialized.
1283         api = event.detail.api;
1284         root = event.target;
1285
1286         // Element attributes starting with "data-", become available under
1287         // element.dataset. In addition hyphenized words become camelCased.
1288         var data = root.dataset;
1289
1290         if ( data.autoplay ) {
1291             autoplayDefault = util.toNumber( data.autoplay, 0 );
1292         }
1293
1294         var toolbar = document.querySelector( "#impress-toolbar" );
1295         if ( toolbar ) {
1296             addToolbarButton( toolbar );
1297         }
1298
1299         api.lib.gc.pushCallback( function() {
1300             clearTimeout( timeoutHandle );
1301         } );
1302
1303         // Note that right after impress:init event, also impress:stepenter is
1304         // triggered for the first slide, so that's where code flow continues.
1305     }, false );
1306
1307     // If default autoplay time was defined in the presentation root, or
1308     // in this step, set timeout.
1309     var reloadTimeout = function( event ) {
1310         var step = event.target;
1311         currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );
1312         if ( status === "paused" ) {
1313             setAutoplayTimeout( 0 );
1314         } else {
1315             setAutoplayTimeout( currentStepTimeout );
1316         }
1317     };
1318
1319     document.addEventListener( "impress:stepenter", function( event ) {
1320         reloadTimeout( event );
1321     }, false );
1322
1323     document.addEventListener( "impress:substep:stepleaveaborted", function( event ) {
1324         reloadTimeout( event );
1325     }, false );
1326
1327     /**
1328      * Set timeout after which we move to next() step.
1329      */
1330     var setAutoplayTimeout = function( timeout ) {
1331         if ( timeoutHandle ) {
1332             clearTimeout( timeoutHandle );
1333         }
1334
1335         if ( timeout > 0 ) {
1336             timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );
1337         }
1338         setButtonText();
1339     };
1340
1341     /*** Toolbar plugin integration *******************************************/
1342     var status = "not clicked";
1343     var toolbarButton = null;
1344
1345     // Copied from core impress.js. Good candidate for moving to a utilities collection.
1346     var triggerEvent = function( el, eventName, detail ) {
1347         var event = document.createEvent( "CustomEvent" );
1348         event.initCustomEvent( eventName, true, true, detail );
1349         el.dispatchEvent( event );
1350     };
1351
1352     var makeDomElement = function( html ) {
1353         var tempDiv = document.createElement( "div" );
1354         tempDiv.innerHTML = html;
1355         return tempDiv.firstChild;
1356     };
1357
1358     var toggleStatus = function() {
1359         if ( currentStepTimeout > 0 && status !== "paused" ) {
1360             status = "paused";
1361         } else {
1362             status = "playing";
1363         }
1364     };
1365
1366     var getButtonText = function() {
1367         if ( currentStepTimeout > 0 && status !== "paused" ) {
1368             return "||"; // Pause
1369         } else {
1370             return "&#9654;"; // Play
1371         }
1372     };
1373
1374     var setButtonText = function() {
1375         if ( toolbarButton ) {
1376
1377             // Keep button size the same even if label content is changing
1378             var buttonWidth = toolbarButton.offsetWidth;
1379             var buttonHeight = toolbarButton.offsetHeight;
1380             toolbarButton.innerHTML = getButtonText();
1381             if ( !toolbarButton.style.width ) {
1382                 toolbarButton.style.width = buttonWidth + "px";
1383             }
1384             if ( !toolbarButton.style.height ) {
1385                 toolbarButton.style.height = buttonHeight + "px";
1386             }
1387         }
1388     };
1389
1390     var addToolbarButton = function( toolbar ) {
1391         var html = '<button id="impress-autoplay-playpause" ' + // jshint ignore:line
1392                    'title="Autoplay" class="impress-autoplay">' + // jshint ignore:line
1393                    getButtonText() + "</button>"; // jshint ignore:line
1394         toolbarButton = makeDomElement( html );
1395         toolbarButton.addEventListener( "click", function() {
1396             toggleStatus();
1397             if ( status === "playing" ) {
1398                 if ( autoplayDefault === 0 ) {
1399                     autoplayDefault = 7;
1400                 }
1401                 if ( currentStepTimeout === 0 ) {
1402                     currentStepTimeout = autoplayDefault;
1403                 }
1404                 setAutoplayTimeout( currentStepTimeout );
1405             } else if ( status === "paused" ) {
1406                 setAutoplayTimeout( 0 );
1407             }
1408         } );
1409
1410         triggerEvent( toolbar, "impress:toolbar:appendChild",
1411                       { group: 10, element: toolbarButton } );
1412     };
1413
1414 } )( document );
1415
1416 /**
1417  * Blackout plugin
1418  *
1419  * Press Ctrl+b to hide all slides, and Ctrl+b again to show them.
1420  * Also navigating to a different slide will show them again (impress:stepleave).
1421  *
1422  * Copyright 2014 @Strikeskids
1423  * Released under the MIT license.
1424  */
1425 /* global document */
1426
1427 ( function( document ) {
1428     "use strict";
1429
1430     var canvas = null;
1431     var blackedOut = false;
1432
1433     // While waiting for a shared library of utilities, copying these 2 from main impress.js
1434     var css = function( el, props ) {
1435         var key, pkey;
1436         for ( key in props ) {
1437             if ( props.hasOwnProperty( key ) ) {
1438                 pkey = pfx( key );
1439                 if ( pkey !== null ) {
1440                     el.style[ pkey ] = props[ key ];
1441                 }
1442             }
1443         }
1444         return el;
1445     };
1446
1447     var pfx = ( function() {
1448
1449         var style = document.createElement( "dummy" ).style,
1450             prefixes = "Webkit Moz O ms Khtml".split( " " ),
1451             memory = {};
1452
1453         return function( prop ) {
1454             if ( typeof memory[ prop ] === "undefined" ) {
1455
1456                 var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
1457                     props   = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
1458
1459                 memory[ prop ] = null;
1460                 for ( var i in props ) {
1461                     if ( style[ props[ i ] ] !== undefined ) {
1462                         memory[ prop ] = props[ i ];
1463                         break;
1464                     }
1465                 }
1466
1467             }
1468
1469             return memory[ prop ];
1470         };
1471
1472     } )();
1473
1474     var removeBlackout = function() {
1475         if ( blackedOut ) {
1476             css( canvas, {
1477                 display: "block"
1478             } );
1479             blackedOut = false;
1480         }
1481     };
1482
1483     var blackout = function() {
1484         if ( blackedOut ) {
1485             removeBlackout();
1486         } else {
1487             css( canvas, {
1488                 display: ( blackedOut = !blackedOut ) ? "none" : "block"
1489             } );
1490             blackedOut = true;
1491         }
1492     };
1493
1494     // Wait for impress.js to be initialized
1495     document.addEventListener( "impress:init", function( event ) {
1496         var api = event.detail.api;
1497         var root = event.target;
1498         canvas = root.firstElementChild;
1499         var gc = api.lib.gc;
1500
1501         gc.addEventListener( document, "keydown", function( event ) {
1502             if ( event.keyCode === 66 ) {
1503                 event.preventDefault();
1504                 if ( !blackedOut ) {
1505                     blackout();
1506                 } else {
1507                     removeBlackout();
1508                 }
1509             }
1510         }, false );
1511
1512         gc.addEventListener( document, "keyup", function( event ) {
1513             if ( event.keyCode === 66 ) {
1514                 event.preventDefault();
1515             }
1516         }, false );
1517
1518     }, false );
1519
1520     document.addEventListener( "impress:stepleave", function() {
1521         removeBlackout();
1522     }, false );
1523
1524 } )( document );
1525
1526
1527 /**
1528  * Extras Plugin
1529  *
1530  * This plugin performs initialization (like calling mermaid.initialize())
1531  * for the extras/ plugins if they are loaded into a presentation.
1532  *
1533  * See README.md for details.
1534  *
1535  * Copyright 2016 Henrik Ingo (@henrikingo)
1536  * Released under the MIT license.
1537  */
1538 /* global markdown, hljs, mermaid, impress, document, window */
1539
1540 ( function( document, window ) {
1541     "use strict";
1542
1543     var preInit = function() {
1544         if ( window.markdown ) {
1545
1546             // Unlike the other extras, Markdown.js doesn't by default do anything in
1547             // particular. We do it ourselves here.
1548             // In addition, we use "-----" as a delimiter for new slide.
1549
1550             // Query all .markdown elements and translate to HTML
1551             var markdownDivs = document.querySelectorAll( ".markdown" );
1552             for ( var idx = 0; idx < markdownDivs.length; idx++ ) {
1553               var element = markdownDivs[ idx ];
1554
1555               var slides = element.textContent.split( /^-----$/m );
1556               var i = slides.length - 1;
1557               element.innerHTML = markdown.toHTML( slides[ i ] );
1558
1559               // If there's an id, unset it for last, and all other, elements,
1560               // and then set it for the first.
1561               var id = null;
1562               if ( element.id ) {
1563                 id = element.id;
1564                 element.id = "";
1565               }
1566               i--;
1567               while ( i >= 0 ) {
1568                 var newElement = element.cloneNode( false );
1569                 newElement.innerHTML = markdown.toHTML( slides[ i ] );
1570                 element.parentNode.insertBefore( newElement, element );
1571                 element = newElement;
1572                 i--;
1573               }
1574               if ( id !== null ) {
1575                 element.id = id;
1576               }
1577             }
1578         } // Markdown
1579
1580         if ( window.hljs ) {
1581             hljs.initHighlightingOnLoad();
1582         }
1583
1584         if ( window.mermaid ) {
1585             mermaid.initialize( { startOnLoad:true } );
1586         }
1587     };
1588
1589     // Register the plugin to be called in pre-init phase
1590     // Note: Markdown.js should run early/first, because it creates new div elements.
1591     // So add this with a lower-than-default weight.
1592     impress.addPreInitPlugin( preInit, 1 );
1593
1594 } )( document, window );
1595
1596
1597 /**
1598  * Form support
1599  *
1600  * Functionality to better support use of input, textarea, button... elements in a presentation.
1601  *
1602  * This plugin does two things:
1603  *
1604  * Set stopPropagation on any element that might take text input. This allows users to type, for
1605  * example, the letter 'P' into a form field, without causing the presenter console to spring up.
1606  *
1607  * On impress:stepleave, de-focus any potentially active
1608  * element. This is to prevent the focus from being left in a form element that is no longer visible
1609  * in the window, and user therefore typing garbage into the form.
1610  *
1611  * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
1612  * in particular the navigation plugin, unfortunately must fully take control of the tab key,
1613  * otherwise a user could cause the browser to scroll to a link or button that's not on the current
1614  * step. However, it could be possible to allow tab navigation between form elements, as long as
1615  * they are on the active step. This is a topic for further study.
1616  *
1617  * Copyright 2016 Henrik Ingo
1618  * MIT License
1619  */
1620 /* global document */
1621 ( function( document ) {
1622     "use strict";
1623     var root;
1624     var api;
1625
1626     document.addEventListener( "impress:init", function( event ) {
1627         root = event.target;
1628         api = event.detail.api;
1629         var gc = api.lib.gc;
1630
1631         var selectors = [ "input[type=text]", "textarea", "select", "[contenteditable=true]" ];
1632         for ( var selector of selectors ) {
1633             var elements = document.querySelectorAll( selector );
1634             if ( !elements ) {
1635                 continue;
1636             }
1637
1638             for ( var i = 0; i < elements.length; i++ ) {
1639                 var e = elements[ i ];
1640                 gc.addEventListener( e, "keydown", function( event ) {
1641                     event.stopPropagation();
1642                 } );
1643                 gc.addEventListener( e, "keyup", function( event ) {
1644                     event.stopPropagation();
1645                 } );
1646             }
1647         }
1648     }, false );
1649
1650     document.addEventListener( "impress:stepleave", function() {
1651         document.activeElement.blur();
1652     }, false );
1653
1654 } )( document );
1655
1656
1657 /**
1658  * Goto Plugin
1659  *
1660  * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,
1661  * and will alter the destination where to transition next.
1662  *
1663  * Example:
1664  *
1665  *         <!-- When leaving this step, go directly to "step-5" -->
1666  *         <div class="step" data-goto="step-5">
1667  *
1668  *         <!-- When leaving this step with next(), go directly to "step-5", instead of next step.
1669  *              If moving backwards to previous step - e.g. prev() instead of next() -
1670  *              then go to "step-1". -->
1671  *         <div class="step" data-goto-next="step-5" data-goto-prev="step-1">
1672  *
1673  *        <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear
1674  *             navigation. -->
1675  *        <div class="step"
1676  *             data-goto-key-list="ArrowUp ArrowDown ArrowRight ArrowLeft"
1677  *             data-goto-next-list="step-4 step-3 step-2 step-5">
1678  *
1679  * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table
1680  * of what strings to use for each key.
1681  *
1682  * Copyright 2016-2017 Henrik Ingo (@henrikingo)
1683  * Released under the MIT license.
1684  */
1685 /* global window, document, impress */
1686
1687 ( function( document, window ) {
1688     "use strict";
1689     var lib;
1690
1691     document.addEventListener( "impress:init", function( event ) {
1692         lib = event.detail.api.lib;
1693     }, false );
1694
1695     var isNumber = function( numeric ) {
1696         return !isNaN( numeric );
1697     };
1698
1699     var goto = function( event ) {
1700         if ( ( !event ) || ( !event.target ) ) {
1701             return;
1702         }
1703
1704         var data = event.target.dataset;
1705         var steps = document.querySelectorAll( ".step" );
1706
1707         // Data-goto-key-list="" & data-goto-next-list="" //////////////////////////////////////////
1708         if ( data.gotoKeyList !== undefined &&
1709              data.gotoNextList !== undefined &&
1710              event.origEvent !== undefined &&
1711              event.origEvent.key !== undefined ) {
1712             var keylist = data.gotoKeyList.split( " " );
1713             var nextlist = data.gotoNextList.split( " " );
1714
1715             if ( keylist.length !== nextlist.length ) {
1716                 window.console.log(
1717                     "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:"
1718                 );
1719                 window.console.log( keylist );
1720                 window.console.log( nextlist );
1721
1722                 // Don't return, allow the other categories to work despite this error
1723             } else {
1724                 var index = keylist.indexOf( event.origEvent.key );
1725                 if ( index >= 0 ) {
1726                     var next = nextlist[ index ];
1727                     if ( isNumber( next ) ) {
1728                         event.detail.next = steps[ next ];
1729
1730                         // If the new next element has its own transitionDuration, we're responsible
1731                         // for setting that on the event as well
1732                         event.detail.transitionDuration = lib.util.toNumber(
1733                             event.detail.next.dataset.transitionDuration,
1734                             event.detail.transitionDuration
1735                         );
1736                         return;
1737                     } else {
1738                         var newTarget = document.getElementById( next );
1739                         if ( newTarget && newTarget.classList.contains( "step" ) ) {
1740                             event.detail.next = newTarget;
1741                             event.detail.transitionDuration = lib.util.toNumber(
1742                                 event.detail.next.dataset.transitionDuration,
1743                                 event.detail.transitionDuration
1744                             );
1745                             return;
1746                         } else {
1747                             window.console.log( "impress goto plugin: " + next +
1748                                                 " is not a step in this impress presentation." );
1749                         }
1750                     }
1751                 }
1752             }
1753         }
1754
1755         // Data-goto-next="" & data-goto-prev="" ///////////////////////////////////////////////////
1756
1757         // Handle event.target data-goto-next attribute
1758         if ( isNumber( data.gotoNext ) && event.detail.reason === "next" ) {
1759             event.detail.next = steps[ data.gotoNext ];
1760
1761             // If the new next element has its own transitionDuration, we're responsible for setting
1762             // that on the event as well
1763             event.detail.transitionDuration = lib.util.toNumber(
1764                 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1765             );
1766             return;
1767         }
1768         if ( data.gotoNext && event.detail.reason === "next" ) {
1769             var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line
1770             if ( newTarget && newTarget.classList.contains( "step" ) ) {
1771                 event.detail.next = newTarget;
1772                 event.detail.transitionDuration = lib.util.toNumber(
1773                     event.detail.next.dataset.transitionDuration,
1774                     event.detail.transitionDuration
1775                 );
1776                 return;
1777             } else {
1778                 window.console.log( "impress goto plugin: " + data.gotoNext +
1779                                     " is not a step in this impress presentation." );
1780             }
1781         }
1782
1783         // Handle event.target data-goto-prev attribute
1784         if ( isNumber( data.gotoPrev ) && event.detail.reason === "prev" ) {
1785             event.detail.next = steps[ data.gotoPrev ];
1786             event.detail.transitionDuration = lib.util.toNumber(
1787                 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1788             );
1789             return;
1790         }
1791         if ( data.gotoPrev && event.detail.reason === "prev" ) {
1792             var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line
1793             if ( newTarget && newTarget.classList.contains( "step" ) ) {
1794                 event.detail.next = newTarget;
1795                 event.detail.transitionDuration = lib.util.toNumber(
1796                     event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1797                 );
1798                 return;
1799             } else {
1800                 window.console.log( "impress goto plugin: " + data.gotoPrev +
1801                                     " is not a step in this impress presentation." );
1802             }
1803         }
1804
1805         // Data-goto="" ///////////////////////////////////////////////////////////////////////////
1806
1807         // Handle event.target data-goto attribute
1808         if ( isNumber( data.goto ) ) {
1809             event.detail.next = steps[ data.goto ];
1810             event.detail.transitionDuration = lib.util.toNumber(
1811                 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1812             );
1813             return;
1814         }
1815         if ( data.goto ) {
1816             var newTarget = document.getElementById( data.goto ); // jshint ignore:line
1817             if ( newTarget && newTarget.classList.contains( "step" ) ) {
1818                 event.detail.next = newTarget;
1819                 event.detail.transitionDuration = lib.util.toNumber(
1820                     event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
1821                 );
1822                 return;
1823             } else {
1824                 window.console.log( "impress goto plugin: " + data.goto +
1825                                     " is not a step in this impress presentation." );
1826             }
1827         }
1828     };
1829
1830     // Register the plugin to be called in pre-stepleave phase
1831     impress.addPreStepLeavePlugin( goto );
1832
1833 } )( document, window );
1834
1835
1836 /**
1837  * Help popup plugin
1838  *
1839  * Example:
1840  *
1841  *     <!-- Show a help popup at start, or if user presses "H" -->
1842  *     <div id="impress-help"></div>
1843  *
1844  * For developers:
1845  *
1846  * Typical use for this plugin, is for plugins that support some keypress, to add a line
1847  * to the help popup produced by this plugin. For example "P: Presenter console".
1848  *
1849  * Copyright 2016 Henrik Ingo (@henrikingo)
1850  * Released under the MIT license.
1851  */
1852 /* global window, document */
1853
1854 ( function( document, window ) {
1855     "use strict";
1856     var rows = [];
1857     var timeoutHandle;
1858
1859     var triggerEvent = function( el, eventName, detail ) {
1860         var event = document.createEvent( "CustomEvent" );
1861         event.initCustomEvent( eventName, true, true, detail );
1862         el.dispatchEvent( event );
1863     };
1864
1865     var renderHelpDiv = function() {
1866         var helpDiv = document.getElementById( "impress-help" );
1867         if ( helpDiv ) {
1868             var html = [];
1869             for ( var row in rows ) {
1870                 for ( var arrayItem in row ) {
1871                     html.push( rows[ row ][ arrayItem ] );
1872                 }
1873             }
1874             if ( html ) {
1875                 helpDiv.innerHTML = "<table>\n" + html.join( "\n" ) + "</table>\n";
1876             }
1877         }
1878     };
1879
1880     var toggleHelp = function() {
1881         var helpDiv = document.getElementById( "impress-help" );
1882         if ( !helpDiv ) {
1883             return;
1884         }
1885
1886         if ( helpDiv.style.display === "block" ) {
1887             helpDiv.style.display = "none";
1888         } else {
1889             helpDiv.style.display = "block";
1890             window.clearTimeout( timeoutHandle );
1891         }
1892     };
1893
1894     document.addEventListener( "keyup", function( event ) {
1895
1896         if ( event.keyCode === 72 ) { // "h"
1897             event.preventDefault();
1898             toggleHelp();
1899         }
1900     }, false );
1901
1902     // API
1903     // Other plugins can add help texts, typically if they support an action on a keypress.
1904     /**
1905      * Add a help text to the help popup.
1906      *
1907      * :param: e.detail.command  Example: "H"
1908      * :param: e.detail.text     Example: "Show this help."
1909      * :param: e.detail.row      Row index from 0 to 9 where to place this help text. Example: 0
1910      */
1911     document.addEventListener( "impress:help:add", function( e ) {
1912
1913         // The idea is for the sender of the event to supply a unique row index, used for sorting.
1914         // But just in case two plugins would ever use the same row index, we wrap each row into
1915         // its own array. If there are more than one entry for the same index, they are shown in
1916         // first come, first serve ordering.
1917         var rowIndex = e.detail.row;
1918         if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) {
1919             rows[ rowIndex ] = [];
1920         }
1921         rows[ e.detail.row ].push( "<tr><td><strong>" + e.detail.command + "</strong></td><td>" +
1922                                    e.detail.text + "</td></tr>" );
1923         renderHelpDiv();
1924     } );
1925
1926     document.addEventListener( "impress:init", function( e ) {
1927         renderHelpDiv();
1928
1929         // At start, show the help for 7 seconds.
1930         var helpDiv = document.getElementById( "impress-help" );
1931         if ( helpDiv ) {
1932             helpDiv.style.display = "block";
1933             timeoutHandle = window.setTimeout( function() {
1934                 var helpDiv = document.getElementById( "impress-help" );
1935                 helpDiv.style.display = "none";
1936             }, 7000 );
1937
1938             // Regster callback to empty the help div on teardown
1939             var api = e.detail.api;
1940             api.lib.gc.pushCallback( function() {
1941                 window.clearTimeout( timeoutHandle );
1942                 helpDiv.style.display = "";
1943                 helpDiv.innerHTML = "";
1944                 rows = [];
1945             } );
1946         }
1947
1948         // Use our own API to register the help text for "h"
1949         triggerEvent( document, "impress:help:add",
1950                       { command: "H", text: "Show this help", row: 0 } );
1951     } );
1952
1953 } )( document, window );
1954
1955
1956 /**
1957  * Adds a presenter console to impress.js
1958  *
1959  * MIT Licensed, see license.txt.
1960  *
1961  * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
1962  *
1963  * version: 1.3-dev
1964  *
1965  */
1966
1967 // This file contains so much HTML, that we will just respectfully disagree about js
1968 /* jshint quotmark:single */
1969 /* global navigator, top, setInterval, clearInterval, document, window */
1970
1971 ( function( document, window ) {
1972     'use strict';
1973
1974     // TODO: Move this to src/lib/util.js
1975     var triggerEvent = function( el, eventName, detail ) {
1976         var event = document.createEvent( 'CustomEvent' );
1977         event.initCustomEvent( eventName, true, true, detail );
1978         el.dispatchEvent( event );
1979     };
1980
1981     // Create Language object depending on browsers language setting
1982     var lang;
1983     switch ( navigator.language ) {
1984     case 'de':
1985         lang = {
1986             'noNotes': '<div class="noNotes">Keine Notizen hierzu</div>',
1987             'restart': 'Neustart',
1988             'clickToOpen': 'Klicken um Sprecherkonsole zu Ã¶ffnen',
1989             'prev': 'zurück',
1990             'next': 'weiter',
1991             'loading': 'initalisiere',
1992             'ready': 'Bereit',
1993             'moving': 'in Bewegung',
1994             'useAMPM': false
1995         };
1996         break;
1997     case 'en': // jshint ignore:line
1998     default : // jshint ignore:line
1999         lang = {
2000             'noNotes': '<div class="noNotes">No notes for this step</div>',
2001             'restart': 'Restart',
2002             'clickToOpen': 'Click to open speaker console',
2003             'prev': 'Prev',
2004             'next': 'Next',
2005             'loading': 'Loading',
2006             'ready': 'Ready',
2007             'moving': 'Moving',
2008             'useAMPM': false
2009         };
2010         break;
2011     }
2012
2013     // Settings to set iframe in speaker console
2014     const preViewDefaultFactor = 0.7;
2015     const preViewMinimumFactor = 0.5;
2016     const preViewGap    = 4;
2017
2018     // This is the default template for the speaker console window
2019     const consoleTemplate = '<!DOCTYPE html>' +
2020         '<html id="impressconsole"><head>' +
2021
2022           // Order is important: If user provides a cssFile, those will win, because they're later
2023           '{{cssStyle}}' +
2024           '{{cssLink}}' +
2025         '</head><body>' +
2026         '<div id="console">' +
2027           '<div id="views">' +
2028             '<iframe id="slideView" scrolling="no"></iframe>' +
2029             '<iframe id="preView" scrolling="no"></iframe>' +
2030             '<div id="blocker"></div>' +
2031           '</div>' +
2032           '<div id="notes"></div>' +
2033         '</div>' +
2034         '<div id="controls"> ' +
2035           '<div id="prev"><a  href="#" onclick="impress().prev(); return false;" />' +
2036             '{{prev}}</a></div>' +
2037           '<div id="next"><a  href="#" onclick="impress().next(); return false;" />' +
2038             '{{next}}</a></div>' +
2039           '<div id="clock">--:--</div>' +
2040           '<div id="timer" onclick="timerReset()">00m 00s</div>' +
2041           '<div id="status">{{loading}}</div>' +
2042         '</div>' +
2043         '</body></html>';
2044
2045     // Default css location
2046     var cssFileOldDefault = 'css/impressConsole.css';
2047     var cssFile = undefined; // jshint ignore:line
2048
2049     // Css for styling iframs on the console
2050     var cssFileIframeOldDefault = 'css/iframe.css';
2051     var cssFileIframe = undefined; // jshint ignore:line
2052
2053     // All console windows, so that you can call impressConsole() repeatedly.
2054     var allConsoles = {};
2055
2056     // Zero padding helper function:
2057     var zeroPad = function( i ) {
2058         return ( i < 10 ? '0' : '' ) + i;
2059     };
2060
2061     // The console object
2062     var impressConsole = window.impressConsole = function( rootId ) {
2063
2064         rootId = rootId || 'impress';
2065
2066         if ( allConsoles[ rootId ] ) {
2067             return allConsoles[ rootId ];
2068         }
2069
2070         // Root presentation elements
2071         var root = document.getElementById( rootId );
2072
2073         var consoleWindow = null;
2074
2075         var nextStep = function() {
2076             var classes = '';
2077             var nextElement = document.querySelector( '.active' );
2078
2079             // Return to parents as long as there is no next sibling
2080             while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
2081                 nextElement = nextElement.parentNode;
2082             }
2083             nextElement = nextElement.nextElementSibling;
2084             while ( nextElement ) {
2085                 classes = nextElement.attributes[ 'class' ];
2086                 if ( classes && classes.value.indexOf( 'step' ) !== -1 ) {
2087                     consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next;
2088                     return nextElement;
2089                 }
2090
2091                 if ( nextElement.firstElementChild ) { // First go into deep
2092                     nextElement = nextElement.firstElementChild;
2093                 } else {
2094
2095                     // Go to next sibling or through parents until there is a next sibling
2096                     while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
2097                         nextElement = nextElement.parentNode;
2098                     }
2099                     nextElement = nextElement.nextElementSibling;
2100                 }
2101             }
2102
2103             // No next element. Pick the first
2104             consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart;
2105             return document.querySelector( '.step' );
2106         };
2107
2108         // Sync the notes to the step
2109         var onStepLeave = function() {
2110             if ( consoleWindow ) {
2111
2112                 // Set notes to next steps notes.
2113                 var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
2114                 if ( newNotes ) {
2115                     newNotes = newNotes.innerHTML;
2116                 } else {
2117                     newNotes = lang.noNotes;
2118                 }
2119                 consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes;
2120
2121                 // Set the views
2122                 var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
2123                 var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
2124                 var preSrc = baseURL + '#' + nextStep().id;
2125                 var slideView = consoleWindow.document.getElementById( 'slideView' );
2126
2127                 // Setting them when they are already set causes glithes in Firefox, so check first:
2128                 if ( slideView.src !== slideSrc ) {
2129                     slideView.src = slideSrc;
2130                 }
2131                 var preView = consoleWindow.document.getElementById( 'preView' );
2132                 if ( preView.src !== preSrc ) {
2133                     preView.src = preSrc;
2134                 }
2135
2136                 consoleWindow.document.getElementById( 'status' ).innerHTML =
2137                     '<span class="moving">' + lang.moving + '</span>';
2138             }
2139         };
2140
2141         // Sync the previews to the step
2142         var onStepEnter = function() {
2143             if ( consoleWindow ) {
2144
2145                 // We do everything here again, because if you stopped the previos step to
2146                 // early, the onstepleave trigger is not called for that step, so
2147                 // we need this to sync things.
2148                 var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
2149                 if ( newNotes ) {
2150                     newNotes = newNotes.innerHTML;
2151                 } else {
2152                     newNotes = lang.noNotes;
2153                 }
2154                 var notes = consoleWindow.document.getElementById( 'notes' );
2155                 notes.innerHTML = newNotes;
2156                 notes.scrollTop = 0;
2157
2158                 // Set the views
2159                 var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
2160                 var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
2161                 var preSrc = baseURL + '#' + nextStep().id;
2162                 var slideView = consoleWindow.document.getElementById( 'slideView' );
2163
2164                 // Setting them when they are already set causes glithes in Firefox, so check first:
2165                 if ( slideView.src !== slideSrc ) {
2166                     slideView.src = slideSrc;
2167                 }
2168                 var preView = consoleWindow.document.getElementById( 'preView' );
2169                 if ( preView.src !== preSrc ) {
2170                     preView.src = preSrc;
2171                 }
2172
2173                 consoleWindow.document.getElementById( 'status' ).innerHTML =
2174                     '<span  class="ready">' + lang.ready + '</span>';
2175             }
2176         };
2177
2178         // Sync substeps
2179         var onSubstep = function( event ) {
2180             if ( consoleWindow ) {
2181                 if ( event.detail.reason === 'next' ) {
2182                     onSubstepShow();
2183                 }
2184                 if ( event.detail.reason === 'prev' ) {
2185                     onSubstepHide();
2186                 }
2187             }
2188         };
2189
2190         var onSubstepShow = function() {
2191             var slideView = consoleWindow.document.getElementById( 'slideView' );
2192             triggerEventInView( slideView, 'impress:substep:show' );
2193         };
2194
2195         var onSubstepHide = function() {
2196             var slideView = consoleWindow.document.getElementById( 'slideView' );
2197             triggerEventInView( slideView, 'impress:substep:hide' );
2198         };
2199
2200         var triggerEventInView = function( frame, eventName, detail ) {
2201
2202             // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't
2203             // work. This does work on Firefox, and should work if viewing the presentation on a
2204             // http:// URL on Chrome.
2205             var event = frame.contentDocument.createEvent( 'CustomEvent' );
2206             event.initCustomEvent( eventName, true, true, detail );
2207             frame.contentDocument.dispatchEvent( event );
2208         };
2209
2210         var spaceHandler = function() {
2211             var notes = consoleWindow.document.getElementById( 'notes' );
2212             if ( notes.scrollTopMax - notes.scrollTop > 20 ) {
2213                notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;
2214             } else {
2215                window.impress().next();
2216             }
2217         };
2218
2219         var timerReset = function() {
2220             consoleWindow.timerStart = new Date();
2221         };
2222
2223         // Show a clock
2224         var clockTick = function() {
2225             var now = new Date();
2226             var hours = now.getHours();
2227             var minutes = now.getMinutes();
2228             var seconds = now.getSeconds();
2229             var ampm = '';
2230
2231             if ( lang.useAMPM ) {
2232                 ampm = ( hours < 12 ) ? 'AM' : 'PM';
2233                 hours = ( hours > 12 ) ? hours - 12 : hours;
2234                 hours = ( hours === 0 ) ? 12 : hours;
2235             }
2236
2237             // Clock
2238             var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) +
2239                            ' ' + ampm;
2240             consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr;
2241
2242             // Timer
2243             seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 );
2244             minutes = Math.floor( seconds / 60 );
2245             seconds = Math.floor( seconds % 60 );
2246             consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue =
2247                 zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's';
2248
2249             if ( !consoleWindow.initialized ) {
2250
2251                 // Nudge the slide windows after load, or they will scrolled wrong on Firefox.
2252                 consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 );
2253                 consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 );
2254                 consoleWindow.initialized = true;
2255             }
2256         };
2257
2258         var registerKeyEvent = function( keyCodes, handler, window ) {
2259             if ( window === undefined ) {
2260                 window = consoleWindow;
2261             }
2262
2263             // Prevent default keydown action when one of supported key is pressed
2264             window.document.addEventListener( 'keydown', function( event ) {
2265                 if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
2266                      keyCodes.indexOf( event.keyCode ) !== -1 ) {
2267                     event.preventDefault();
2268                 }
2269             }, false );
2270
2271             // Trigger impress action on keyup
2272             window.document.addEventListener( 'keyup', function( event ) {
2273                 if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
2274                      keyCodes.indexOf( event.keyCode ) !== -1 ) {
2275                         handler();
2276                         event.preventDefault();
2277                 }
2278             }, false );
2279         };
2280
2281         var consoleOnLoad = function() {
2282                 var slideView = consoleWindow.document.getElementById( 'slideView' );
2283                 var preView = consoleWindow.document.getElementById( 'preView' );
2284
2285                 // Firefox:
2286                 slideView.contentDocument.body.classList.add( 'impress-console' );
2287                 preView.contentDocument.body.classList.add( 'impress-console' );
2288                 if ( cssFileIframe !== undefined ) {
2289                     slideView.contentDocument.head.insertAdjacentHTML(
2290                         'beforeend',
2291                         '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
2292                     );
2293                     preView.contentDocument.head.insertAdjacentHTML(
2294                         'beforeend',
2295                         '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
2296                     );
2297                 }
2298
2299                 // Chrome:
2300                 slideView.addEventListener( 'load', function() {
2301                         slideView.contentDocument.body.classList.add( 'impress-console' );
2302                         if ( cssFileIframe !== undefined ) {
2303                             slideView.contentDocument.head.insertAdjacentHTML(
2304                                 'beforeend',
2305                                 '<link rel="stylesheet" type="text/css" href="' +
2306                                     cssFileIframe + '">'
2307                             );
2308                         }
2309                 } );
2310                 preView.addEventListener( 'load', function() {
2311                         preView.contentDocument.body.classList.add( 'impress-console' );
2312                         if ( cssFileIframe !== undefined ) {
2313                             preView.contentDocument.head.insertAdjacentHTML(
2314                                 'beforeend',
2315                                 '<link rel="stylesheet" type="text/css" href="' +
2316                                     cssFileIframe + '">' );
2317                         }
2318                 } );
2319         };
2320
2321         var open = function() {
2322             if ( top.isconsoleWindow ) {
2323                 return;
2324             }
2325
2326             if ( consoleWindow && !consoleWindow.closed ) {
2327                 consoleWindow.focus();
2328             } else {
2329                 consoleWindow = window.open( '', 'impressConsole' );
2330
2331                 // If opening failes this may be because the browser prevents this from
2332                 // not (or less) interactive JavaScript...
2333                 if ( consoleWindow == null ) {
2334
2335                     // ... so I add a button to klick.
2336                     // workaround on firefox
2337                     var message = document.createElement( 'div' );
2338                     message.id = 'impress-console-button';
2339                     message.style.position = 'fixed';
2340                     message.style.left = 0;
2341                     message.style.top = 0;
2342                     message.style.right = 0;
2343                     message.style.bottom = 0;
2344                     message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
2345                     var clickStr = 'var x = document.getElementById(\'impress-console-button\');' +
2346                                      'x.parentNode.removeChild(x);' +
2347                                      'var r = document.getElementById(\'' + rootId + '\');' +
2348                                      'impress(\'' + rootId +
2349                                      '\').lib.util.triggerEvent(r, \'impress:console:open\', {})';
2350                     var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;';
2351                     message.innerHTML = '<button style="' + styleStr + '" ' +
2352                                                  'onclick="' + clickStr + '">' +
2353                                         lang.clickToOpen +
2354                                         '</button>';
2355                     document.body.appendChild( message );
2356                     return;
2357                 }
2358
2359                 var cssLink = '';
2360                 if ( cssFile !== undefined ) {
2361                     cssLink = '<link rel="stylesheet" type="text/css" media="screen" href="' +
2362                               cssFile + '">';
2363                 }
2364
2365                 // This sets the window location to the main window location, so css can be loaded:
2366                 consoleWindow.document.open();
2367
2368                 // Write the template:
2369                 consoleWindow.document.write(
2370
2371                     // CssStyleStr is lots of inline <style></style> defined at the end of this file
2372                     consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() )
2373                                    .replace( '{{cssLink}}', cssLink )
2374                                    .replace( /{{.*?}}/gi, function( x ) {
2375                                        return lang[ x.substring( 2, x.length - 2 ) ]; }
2376                                    )
2377                 );
2378                 consoleWindow.document.title = 'Speaker Console (' + document.title + ')';
2379                 consoleWindow.impress = window.impress;
2380
2381                 // We set this flag so we can detect it later, to prevent infinite popups.
2382                 consoleWindow.isconsoleWindow = true;
2383
2384                 // Set the onload function:
2385                 consoleWindow.onload = consoleOnLoad;
2386
2387                 // Add clock tick
2388                 consoleWindow.timerStart = new Date();
2389                 consoleWindow.timerReset = timerReset;
2390                 consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 );
2391
2392                 // Keyboard navigation handlers
2393                 // 33: pg up, 37: left, 38: up
2394                 registerKeyEvent( [ 33, 37, 38 ], window.impress().prev );
2395
2396                 // 34: pg down, 39: right, 40: down
2397                 registerKeyEvent( [ 34, 39, 40 ], window.impress().next );
2398
2399                 // 32: space
2400                 registerKeyEvent( [ 32 ], spaceHandler );
2401
2402                 // 82: R
2403                 registerKeyEvent( [ 82 ], timerReset );
2404
2405                 // Cleanup
2406                 consoleWindow.onbeforeunload = function() {
2407
2408                     // I don't know why onunload doesn't work here.
2409                     clearInterval( consoleWindow.clockInterval );
2410                 };
2411
2412                 // It will need a little nudge on Firefox, but only after loading:
2413                 onStepEnter();
2414                 consoleWindow.initialized = false;
2415                 consoleWindow.document.close();
2416
2417                 //Catch any window resize to pass size on
2418                 window.onresize = resize;
2419                 consoleWindow.onresize = resize;
2420
2421                 return consoleWindow;
2422             }
2423         };
2424
2425         var resize = function() {
2426             var slideView = consoleWindow.document.getElementById( 'slideView' );
2427             var preView = consoleWindow.document.getElementById( 'preView' );
2428
2429             // Get ratio of presentation
2430             var ratio = window.innerHeight / window.innerWidth;
2431
2432             // Get size available for views
2433             var views = consoleWindow.document.getElementById( 'views' );
2434
2435             // SlideView may have a border or some padding:
2436             // asuming same border width on both direktions
2437             var delta = slideView.offsetWidth - slideView.clientWidth;
2438
2439             // Set views
2440             var slideViewWidth = ( views.clientWidth - delta );
2441             var slideViewHeight = Math.floor( slideViewWidth * ratio );
2442
2443             var preViewTop = slideViewHeight + preViewGap;
2444
2445             var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor );
2446             var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor );
2447
2448             // Shrink preview to fit into space available
2449             if ( views.clientHeight - delta < preViewTop + preViewHeight ) {
2450                 preViewHeight = views.clientHeight - delta - preViewTop;
2451                 preViewWidth = Math.floor( preViewHeight / ratio );
2452             }
2453
2454             // If preview is not high enough forget ratios!
2455             if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) {
2456                 slideViewWidth = ( views.clientWidth - delta );
2457                 slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) /
2458                                              ( 1 + preViewMinimumFactor ) );
2459
2460                 preViewTop = slideViewHeight + preViewGap;
2461
2462                 preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor );
2463                 preViewHeight = views.clientHeight - delta - preViewTop;
2464             }
2465
2466             // Set the calculated into styles
2467             slideView.style.width = slideViewWidth + 'px';
2468             slideView.style.height = slideViewHeight + 'px';
2469
2470             preView.style.top = preViewTop + 'px';
2471
2472             preView.style.width = preViewWidth + 'px';
2473             preView.style.height = preViewHeight + 'px';
2474         };
2475
2476         var _init = function( cssConsole, cssIframe ) {
2477             if ( cssConsole !== undefined ) {
2478                 cssFile = cssConsole;
2479             }
2480
2481             // You can also specify the css in the presentation root div:
2482             // <div id="impress" data-console-css=..." data-console-css-iframe="...">
2483             else if ( root.dataset.consoleCss !== undefined ) {
2484                 cssFile = root.dataset.consoleCss;
2485             }
2486
2487             if ( cssIframe !== undefined ) {
2488                 cssFileIframe = cssIframe;
2489             } else if ( root.dataset.consoleCssIframe !== undefined ) {
2490                 cssFileIframe = root.dataset.consoleCssIframe;
2491             }
2492
2493             // Register the event
2494             root.addEventListener( 'impress:stepleave', onStepLeave );
2495             root.addEventListener( 'impress:stepenter', onStepEnter );
2496             root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep );
2497             root.addEventListener( 'impress:substep:show', onSubstepShow );
2498             root.addEventListener( 'impress:substep:hide', onSubstepHide );
2499
2500             //When the window closes, clean up after ourselves.
2501             window.onunload = function() {
2502                 if ( consoleWindow && !consoleWindow.closed ) {
2503                     consoleWindow.close();
2504                 }
2505             };
2506
2507             //Open speaker console when they press 'p'
2508             registerKeyEvent( [ 80 ], open, window );
2509
2510             //Btw, you can also launch console automatically:
2511             //<div id="impress" data-console-autolaunch="true">
2512             if ( root.dataset.consoleAutolaunch === 'true' ) {
2513                 open();
2514             }
2515         };
2516
2517         var init = function( cssConsole, cssIframe ) {
2518             if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&
2519                  ( cssIframe === undefined  || cssIframe === cssFileIframeOldDefault ) ) {
2520                 window.console.log( 'impressConsole().init() is deprecated. ' +
2521                                    'impressConsole is now initialized automatically when you ' +
2522                                    'call impress().init().' );
2523             }
2524             _init( cssConsole, cssIframe );
2525         };
2526
2527         // New API for impress.js plugins is based on using events
2528         root.addEventListener( 'impress:console:open', function() {
2529             open();
2530         } );
2531
2532         /**
2533          * Register a key code to an event handler
2534          *
2535          * :param: event.detail.keyCodes    List of key codes
2536          * :param: event.detail.handler     A function registered as the event handler
2537          * :param: event.detail.window      The console window to register the keycode in
2538          */
2539         root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {
2540             registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );
2541         } );
2542
2543         // Return the object
2544         allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,
2545                                registerKeyEvent: registerKeyEvent, _init: _init };
2546         return allConsoles[ rootId ];
2547
2548     };
2549
2550     // This initializes impressConsole automatically when initializing impress itself
2551     document.addEventListener( 'impress:init', function( event ) {
2552
2553         // Note: impressConsole wants the id string, not the DOM element directly
2554         impressConsole( event.target.id )._init();
2555
2556         // Add 'P' to the help popup
2557         triggerEvent( document, 'impress:help:add',
2558                         { command: 'P', text: 'Presenter console', row: 10 } );
2559     } );
2560
2561     // Returns a string to be used inline as a css <style> element in the console window.
2562     // Apologies for length, but hiding it here at the end to keep it away from rest of the code.
2563     var cssStyleStr = function() {
2564         return `<style>
2565             #impressconsole body {
2566                 background-color: rgb(255, 255, 255);
2567                 padding: 0;
2568                 margin: 0;
2569                 font-family: verdana, arial, sans-serif;
2570                 font-size: 2vw;
2571             }
2572
2573             #impressconsole div#console {
2574                 position: absolute;
2575                 top: 0.5vw;
2576                 left: 0.5vw;
2577                 right: 0.5vw;
2578                 bottom: 3vw;
2579                 margin: 0;
2580             }
2581
2582             #impressconsole div#views, #impressconsole div#notes {
2583                 position: absolute;
2584                 top: 0;
2585                 bottom: 0;
2586             }
2587
2588             #impressconsole div#views {
2589                 left: 0;
2590                 right: 50vw;
2591                 overflow: hidden;
2592             }
2593
2594             #impressconsole div#blocker {
2595                 position: absolute;
2596                 right: 0;
2597                 bottom: 0;
2598             }
2599
2600             #impressconsole div#notes {
2601                 left: 50vw;
2602                 right: 0;
2603                 overflow-x: hidden;
2604                 overflow-y: auto;
2605                 padding: 0.3ex;
2606                 background-color: rgb(255, 255, 255);
2607                 border: solid 1px rgb(120, 120, 120);
2608             }
2609
2610             #impressconsole div#notes .noNotes {
2611                 color: rgb(200, 200, 200);
2612             }
2613
2614             #impressconsole div#notes p {
2615                 margin-top: 0;
2616             }
2617
2618             #impressconsole iframe {
2619                 position: absolute;
2620                 margin: 0;
2621                 padding: 0;
2622                 left: 0;
2623                 border: solid 1px rgb(120, 120, 120);
2624             }
2625
2626             #impressconsole iframe#slideView {
2627                 top: 0;
2628                 width: 49vw;
2629                 height: 49vh;
2630             }
2631
2632             #impressconsole iframe#preView {
2633                 opacity: 0.7;
2634                 top: 50vh;
2635                 width: 30vw;
2636                 height: 30vh;
2637             }
2638
2639             #impressconsole div#controls {
2640                 margin: 0;
2641                 position: absolute;
2642                 bottom: 0.25vw;
2643                 left: 0.5vw;
2644                 right: 0.5vw;
2645                 height: 2.5vw;
2646                 background-color: rgb(255, 255, 255);
2647                 background-color: rgba(255, 255, 255, 0.6);
2648             }
2649
2650             #impressconsole div#prev, div#next {
2651             }
2652
2653             #impressconsole div#prev a, #impressconsole div#next a {
2654                 display: block;
2655                 border: solid 1px rgb(70, 70, 70);
2656                 border-radius: 0.5vw;
2657                 font-size: 1.5vw;
2658                 padding: 0.25vw;
2659                 text-decoration: none;
2660                 background-color: rgb(220, 220, 220);
2661                 color: rgb(0, 0, 0);
2662             }
2663
2664             #impressconsole div#prev a:hover, #impressconsole div#next a:hover {
2665                 background-color: rgb(245, 245, 245);
2666             }
2667
2668             #impressconsole div#prev {
2669                 float: left;
2670             }
2671
2672             #impressconsole div#next {
2673                 float: right;
2674             }
2675
2676             #impressconsole div#status {
2677                 margin-left: 2em;
2678                 margin-right: 2em;
2679                 text-align: center;
2680                 float: right;
2681             }
2682
2683             #impressconsole div#clock {
2684                 margin-left: 2em;
2685                 margin-right: 2em;
2686                 text-align: center;
2687                 float: left;
2688             }
2689
2690             #impressconsole div#timer {
2691                 margin-left: 2em;
2692                 margin-right: 2em;
2693                 text-align: center;
2694                 float: left;
2695             }
2696
2697             #impressconsole span.moving {
2698                 color: rgb(255, 0, 0);
2699             }
2700
2701             #impressconsole span.ready {
2702                 color: rgb(0, 128, 0);
2703             }
2704         </style>`;
2705     };
2706
2707 } )( document, window );
2708
2709 /**
2710  * Media Plugin
2711  *
2712  * This plugin will do the following things:
2713  *
2714  *  - Add a special class when playing (body.impress-media-video-playing
2715  *    and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused
2716  *    and body.impress-media-audio-paused) (removing them when ending).
2717  *    This can be useful for example for darkening the background or fading out other elements
2718  *    while a video is playing.
2719  *    Only media at the current step are taken into account. All classes are removed when leaving
2720  *    a step.
2721  *
2722  *  - Introduce the following new data attributes:
2723  *
2724  *    - data-media-autoplay="true": Autostart media when entering its step.
2725  *    - data-media-autostop="true": Stop media (= pause and reset to start), when leaving its
2726  *      step.
2727  *    - data-media-autopause="true": Pause media but keep current time when leaving its step.
2728  *
2729  *    When these attributes are added to a step they are inherited by all media on this step.
2730  *    Of course this setting can be overwritten by adding different attributes to inidvidual
2731  *    media.
2732  *
2733  *    The same rule applies when this attributes is added to the root element. Settings can be
2734  *    overwritten for individual steps and media.
2735  *
2736  *    Examples:
2737  *    - data-media-autostart="true" data-media-autostop="true": start media on enter, stop on
2738  *      leave, restart from beginning when re-entering the step.
2739  *
2740  *    - data-media-autostart="true" data-media-autopause="true": start media on enter, pause on
2741  *      leave, resume on re-enter
2742  *
2743  *    - data-media-autostart="true" data-media-autostop="true" data-media-autopause="true": start
2744  *      media on enter, stop on leave (stop overwrites pause).
2745  *
2746  *    - data-media-autostart="true" data-media-autopause="false": let media start automatically
2747  *      when entering a step and let it play when leaving the step.
2748  *
2749  *    - <div id="impress" data-media-autostart="true"> ... <div class="step"
2750  *      data-media-autostart="false">
2751  *      All media is startet automatically on all steps except the one that has the
2752  *      data-media-autostart="false" attribute.
2753  *
2754  *  - Pro tip: Use <audio onended="impress().next()"> or <video onended="impress().next()"> to
2755  *    proceed to the next step automatically, when the end of the media is reached.
2756  *
2757  *
2758  * Copyright 2018 Holger Teichert (@complanar)
2759  * Released under the MIT license.
2760  */
2761 /* global window, document */
2762
2763 ( function( document, window ) {
2764     "use strict";
2765     var root, api, gc, attributeTracker;
2766
2767     attributeTracker = [];
2768
2769     // Function names
2770     var enhanceMediaNodes,
2771         enhanceMedia,
2772         removeMediaClasses,
2773         onStepenterDetectImpressConsole,
2774         onStepenter,
2775         onStepleave,
2776         onPlay,
2777         onPause,
2778         onEnded,
2779         getMediaAttribute,
2780         teardown;
2781
2782     document.addEventListener( "impress:init", function( event ) {
2783         root = event.target;
2784         api = event.detail.api;
2785         gc = api.lib.gc;
2786
2787         enhanceMedia();
2788
2789         gc.pushCallback( teardown );
2790     }, false );
2791
2792     teardown = function() {
2793         var el, i;
2794         removeMediaClasses();
2795         for ( i = 0; i < attributeTracker.length; i += 1 ) {
2796             el = attributeTracker[ i ];
2797             el.node.removeAttribute( el.attr );
2798         }
2799         attributeTracker = [];
2800     };
2801
2802     getMediaAttribute = function( attributeName, nodes ) {
2803         var attrName, attrValue, i, node;
2804         attrName = "data-media-" + attributeName;
2805
2806         // Look for attributes in all nodes
2807         for ( i = 0; i < nodes.length; i += 1 ) {
2808             node = nodes[ i ];
2809
2810             // First test, if the attribute exists, because some browsers may return
2811             // an empty string for non-existing attributes - specs are not clear at that point
2812             if ( node.hasAttribute( attrName ) ) {
2813
2814                 // Attribute found, return their parsed boolean value, empty strings count as true
2815                 // to enable empty value booleans (common in html5 but not allowed in well formed
2816                 // xml).
2817                 attrValue = node.getAttribute( attrName );
2818                 if ( attrValue === "" || attrValue === "true" ) {
2819                     return true;
2820                 } else {
2821                     return false;
2822                 }
2823             }
2824
2825             // No attribute found at current node, proceed with next round
2826         }
2827
2828         // Last resort: no attribute found - return undefined to distiguish from false
2829         return undefined;
2830     };
2831
2832     onPlay = function( event ) {
2833         var type = event.target.nodeName.toLowerCase();
2834         document.body.classList.add( "impress-media-" + type + "-playing" );
2835         document.body.classList.remove( "impress-media-" + type + "-paused" );
2836     };
2837
2838     onPause = function( event ) {
2839         var type = event.target.nodeName.toLowerCase();
2840         document.body.classList.add( "impress-media-" + type + "-paused" );
2841         document.body.classList.remove( "impress-media-" + type + "-playing" );
2842     };
2843
2844     onEnded = function( event ) {
2845         var type = event.target.nodeName.toLowerCase();
2846         document.body.classList.remove( "impress-media-" + type + "-playing" );
2847         document.body.classList.remove( "impress-media-" + type + "-paused" );
2848     };
2849
2850     removeMediaClasses = function() {
2851         var type, types;
2852         types = [ "video", "audio" ];
2853         for ( type in types ) {
2854             document.body.classList.remove( "impress-media-" + types[ type ] + "-playing" );
2855             document.body.classList.remove( "impress-media-" + types[ type ] + "-paused" );
2856         }
2857     };
2858
2859     enhanceMediaNodes = function() {
2860         var i, id, media, mediaElement, type;
2861
2862         media = root.querySelectorAll( "audio, video" );
2863         for ( i = 0; i < media.length; i += 1 ) {
2864             type = media[ i ].nodeName.toLowerCase();
2865
2866             // Set an id to identify each media node - used e.g. for cross references by
2867             // the consoleMedia plugin
2868             mediaElement = media[ i ];
2869             id = mediaElement.getAttribute( "id" );
2870             if ( id === undefined || id === null ) {
2871                 mediaElement.setAttribute( "id", "media-" + type + "-" + i );
2872                 attributeTracker.push( { "node": mediaElement, "attr": "id" } );
2873             }
2874             gc.addEventListener( mediaElement, "play", onPlay );
2875             gc.addEventListener( mediaElement, "playing", onPlay );
2876             gc.addEventListener( mediaElement, "pause", onPause );
2877             gc.addEventListener( mediaElement, "ended", onEnded );
2878         }
2879     };
2880
2881     enhanceMedia = function() {
2882         var steps, stepElement, i;
2883         enhanceMediaNodes();
2884         steps = document.getElementsByClassName( "step" );
2885         for ( i = 0; i < steps.length; i += 1 ) {
2886             stepElement = steps[ i ];
2887
2888             gc.addEventListener( stepElement, "impress:stepenter", onStepenter );
2889             gc.addEventListener( stepElement, "impress:stepleave", onStepleave );
2890         }
2891     };
2892
2893     onStepenterDetectImpressConsole = function() {
2894         return {
2895             "preview": ( window.frameElement !== null && window.frameElement.id === "preView" ),
2896             "slideView": ( window.frameElement !== null && window.frameElement.id === "slideView" )
2897         };
2898     };
2899
2900     onStepenter = function( event ) {
2901         var stepElement, media, mediaElement, i, onConsole, autoplay;
2902         if ( ( !event ) || ( !event.target ) ) {
2903             return;
2904         }
2905
2906         stepElement = event.target;
2907         removeMediaClasses();
2908
2909         media = stepElement.querySelectorAll( "audio, video" );
2910         for ( i = 0; i < media.length; i += 1 ) {
2911             mediaElement = media[ i ];
2912
2913             // Autoplay when (maybe inherited) autoplay setting is true,
2914             // but only if not on preview of the next step in impressConsole
2915             onConsole = onStepenterDetectImpressConsole();
2916             autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
2917             if ( autoplay && !onConsole.preview ) {
2918                 if ( onConsole.slideView ) {
2919                     mediaElement.muted = true;
2920                 }
2921                 mediaElement.play();
2922             }
2923         }
2924     };
2925
2926     onStepleave = function( event ) {
2927         var stepElement, media, i, mediaElement, autoplay, autopause, autostop;
2928         if ( ( !event || !event.target ) ) {
2929             return;
2930         }
2931
2932         stepElement = event.target;
2933         media = event.target.querySelectorAll( "audio, video" );
2934         for ( i = 0; i < media.length; i += 1 ) {
2935             mediaElement = media[ i ];
2936
2937             autoplay = getMediaAttribute( "autoplay", [ mediaElement, stepElement, root ] );
2938             autopause = getMediaAttribute( "autopause", [ mediaElement, stepElement, root ] );
2939             autostop = getMediaAttribute( "autostop",  [ mediaElement, stepElement, root ] );
2940
2941             // If both autostop and autopause are undefined, set it to the value of autoplay
2942             if ( autostop === undefined && autopause === undefined ) {
2943                 autostop = autoplay;
2944             }
2945
2946             if ( autopause || autostop ) {
2947                 mediaElement.pause();
2948                 if ( autostop ) {
2949                     mediaElement.currentTime = 0;
2950                 }
2951             }
2952         }
2953         removeMediaClasses();
2954     };
2955
2956 } )( document, window );
2957
2958 /**
2959  * Mobile devices support
2960  *
2961  * Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile
2962  * devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.
2963  *
2964  * Note: This plugin does not take into account possible redirections done with skip, goto etc
2965  * plugins. Basically it wouldn't work as intended in such cases, but the active step will at least
2966  * be correct.
2967  *
2968  * Adapted to a plugin from a submission by @Kzeni:
2969  * https://github.com/impress/impress.js/issues/333
2970  */
2971 /* global document, navigator */
2972 ( function( document ) {
2973     "use strict";
2974
2975     var getNextStep = function( el ) {
2976         var steps = document.querySelectorAll( ".step" );
2977         for ( var i = 0; i < steps.length; i++ ) {
2978             if ( steps[ i ] === el ) {
2979                 if ( i + 1 < steps.length ) {
2980                     return steps[ i + 1 ];
2981                 } else {
2982                     return steps[ 0 ];
2983                 }
2984             }
2985         }
2986     };
2987     var getPrevStep = function( el ) {
2988         var steps = document.querySelectorAll( ".step" );
2989         for ( var i = steps.length - 1; i >= 0; i-- ) {
2990             if ( steps[ i ] === el ) {
2991                 if ( i - 1 >= 0 ) {
2992                     return steps[ i - 1 ];
2993                 } else {
2994                     return steps[ steps.length - 1 ];
2995                 }
2996             }
2997         }
2998     };
2999
3000     // Detect mobile browsers & add CSS class as appropriate.
3001     document.addEventListener( "impress:init", function( event ) {
3002         var body = document.body;
3003         if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
3004                  navigator.userAgent
3005              ) ) {
3006             body.classList.add( "impress-mobile" );
3007         }
3008
3009         // Unset all this on teardown
3010         var api = event.detail.api;
3011         api.lib.gc.pushCallback( function() {
3012             document.body.classList.remove( "impress-mobile" );
3013             var prev = document.getElementsByClassName( "prev" )[ 0 ];
3014             var next = document.getElementsByClassName( "next" )[ 0 ];
3015             if ( typeof prev !== "undefined" ) {
3016                 prev.classList.remove( "prev" );
3017             }
3018             if ( typeof next !== "undefined" ) {
3019                 next.classList.remove( "next" );
3020             }
3021         } );
3022     } );
3023
3024     // Add prev and next classes to the siblings of the newly entered active step element
3025     // Remove prev and next classes from their current step elements
3026     // Note: As an exception we break namespacing rules, as these are useful general purpose
3027     // classes. (Naming rules would require us to use css classes mobile-next and mobile-prev,
3028     // based on plugin name.)
3029     document.addEventListener( "impress:stepenter", function( event ) {
3030           var oldprev = document.getElementsByClassName( "prev" )[ 0 ];
3031           var oldnext = document.getElementsByClassName( "next" )[ 0 ];
3032
3033           var prev = getPrevStep( event.target );
3034           prev.classList.add( "prev" );
3035           var next = getNextStep( event.target );
3036           next.classList.add( "next" );
3037
3038           if ( typeof oldprev !== "undefined" ) {
3039               oldprev.classList.remove( "prev" );
3040               }
3041           if ( typeof oldnext !== "undefined" ) {
3042               oldnext.classList.remove( "next" );
3043               }
3044     } );
3045 } )( document );
3046
3047
3048 /**
3049  * Mouse timeout plugin
3050  *
3051  * After 3 seconds of mouse inactivity, add the css class
3052  * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the
3053  * class.
3054  *
3055  * The use case for this plugin is to use CSS to hide elements from the screen
3056  * and only make them visible when the mouse is moved. Examples where this
3057  * might be used are: the toolbar from the toolbar plugin, and the mouse cursor
3058  * itself.
3059  *
3060  * Example CSS:
3061  *
3062  *     body.impress-mouse-timeout {
3063  *         cursor: none;
3064  *     }
3065  *     body.impress-mouse-timeout div#impress-toolbar {
3066  *         display: none;
3067  *     }
3068  *
3069  *
3070  * Copyright 2016 Henrik Ingo (@henrikingo)
3071  * Released under the MIT license.
3072  */
3073 /* global window, document */
3074 ( function( document, window ) {
3075     "use strict";
3076     var timeout = 3;
3077     var timeoutHandle;
3078
3079     var hide = function() {
3080
3081         // Mouse is now inactive
3082         document.body.classList.add( "impress-mouse-timeout" );
3083     };
3084
3085     var show = function() {
3086         if ( timeoutHandle ) {
3087             window.clearTimeout( timeoutHandle );
3088         }
3089
3090         // Mouse is now active
3091         document.body.classList.remove( "impress-mouse-timeout" );
3092
3093         // Then set new timeout after which it is considered inactive again
3094         timeoutHandle = window.setTimeout( hide, timeout * 1000 );
3095     };
3096
3097     document.addEventListener( "impress:init", function( event ) {
3098         var api = event.detail.api;
3099         var gc = api.lib.gc;
3100         gc.addEventListener( document, "mousemove", show );
3101         gc.addEventListener( document, "click", show );
3102         gc.addEventListener( document, "touch", show );
3103
3104         // Set first timeout
3105         show();
3106
3107         // Unset all this on teardown
3108         gc.pushCallback( function() {
3109             window.clearTimeout( timeoutHandle );
3110             document.body.classList.remove( "impress-mouse-timeout" );
3111         } );
3112     }, false );
3113
3114 } )( document, window );
3115
3116 /**
3117  * Navigation events plugin
3118  *
3119  * As you can see this part is separate from the impress.js core code.
3120  * It's because these navigation actions only need what impress.js provides with
3121  * its simple API.
3122  *
3123  * This plugin is what we call an _init plugin_. It's a simple kind of
3124  * impress.js plugin. When loaded, it starts listening to the `impress:init`
3125  * event. That event listener initializes the plugin functionality - in this
3126  * case we listen to some keypress and mouse events. The only dependencies on
3127  * core impress.js functionality is the `impress:init` method, as well as using
3128  * the public api `next(), prev(),` etc when keys are pressed.
3129  *
3130  * Copyright 2011-2012 Bartek Szopka (@bartaz)
3131  * Released under the MIT license.
3132  * ------------------------------------------------
3133  *  author:  Bartek Szopka
3134  *  version: 0.5.3
3135  *  url:     http://bartaz.github.com/impress.js/
3136  *  source:  http://github.com/bartaz/impress.js/
3137  *
3138  */
3139 /* global document */
3140 ( function( document ) {
3141     "use strict";
3142
3143     // Wait for impress.js to be initialized
3144     document.addEventListener( "impress:init", function( event ) {
3145
3146         // Getting API from event data.
3147         // So you don't event need to know what is the id of the root element
3148         // or anything. `impress:init` event data gives you everything you
3149         // need to control the presentation that was just initialized.
3150         var api = event.detail.api;
3151         var gc = api.lib.gc;
3152         var util = api.lib.util;
3153
3154         // Supported keys are:
3155         // [space] - quite common in presentation software to move forward
3156         // [up] [right] / [down] [left] - again common and natural addition,
3157         // [pgdown] / [pgup] - often triggered by remote controllers,
3158         // [tab] - this one is quite controversial, but the reason it ended up on
3159         //   this list is quite an interesting story... Remember that strange part
3160         //   in the impress.js code where window is scrolled to 0,0 on every presentation
3161         //   step, because sometimes browser scrolls viewport because of the focused element?
3162         //   Well, the [tab] key by default navigates around focusable elements, so clicking
3163         //   it very often caused scrolling to focused element and breaking impress.js
3164         //   positioning. I didn't want to just prevent this default action, so I used [tab]
3165         //   as another way to moving to next step... And yes, I know that for the sake of
3166         //   consistency I should add [shift+tab] as opposite action...
3167         var isNavigationEvent = function( event ) {
3168
3169             // Don't trigger navigation for example when user returns to browser window with ALT+TAB
3170             if ( event.altKey || event.ctrlKey || event.metaKey ) {
3171                 return false;
3172             }
3173
3174             // In the case of TAB, we force step navigation always, overriding the browser
3175             // navigation between input elements, buttons and links.
3176             if ( event.keyCode === 9 ) {
3177                 return true;
3178             }
3179
3180             // With the sole exception of TAB, we also ignore keys pressed if shift is down.
3181             if ( event.shiftKey ) {
3182                 return false;
3183             }
3184
3185             if ( ( event.keyCode >= 32 && event.keyCode <= 34 ) ||
3186                  ( event.keyCode >= 37 && event.keyCode <= 40 ) ) {
3187                 return true;
3188             }
3189         };
3190
3191         // KEYBOARD NAVIGATION HANDLERS
3192
3193         // Prevent default keydown action when one of supported key is pressed.
3194         gc.addEventListener( document, "keydown", function( event ) {
3195             if ( isNavigationEvent( event ) ) {
3196                 event.preventDefault();
3197             }
3198         }, false );
3199
3200         // Trigger impress action (next or prev) on keyup.
3201         gc.addEventListener( document, "keyup", function( event ) {
3202             if ( isNavigationEvent( event ) ) {
3203                 if ( event.shiftKey ) {
3204                     switch ( event.keyCode ) {
3205                         case 9: // Shift+tab
3206                         case 32: // Shift+space
3207                             api.prev();
3208                             break;
3209                     }
3210                 } else {
3211                     switch ( event.keyCode ) {
3212                         case 33: // Pg up
3213                         case 37: // Left
3214                         case 38: // Up
3215                                  api.prev( event );
3216                                  break;
3217                         case 9:  // Tab
3218                         case 32: // Space
3219                         case 34: // Pg down
3220                         case 39: // Right
3221                         case 40: // Down
3222                                  api.next( event );
3223                                  break;
3224                     }
3225                 }
3226                 event.preventDefault();
3227             }
3228         }, false );
3229
3230         // Delegated handler for clicking on the links to presentation steps
3231         gc.addEventListener( document, "click", function( event ) {
3232
3233             // Event delegation with "bubbling"
3234             // check if event target (or any of its parents is a link)
3235             var target = event.target;
3236             try {
3237                 while ( ( target.tagName !== "A" ) &&
3238                         ( target !== document.documentElement ) ) {
3239                     target = target.parentNode;
3240                 }
3241
3242                 if ( target.tagName === "A" ) {
3243                     var href = target.getAttribute( "href" );
3244
3245                     // If it's a link to presentation step, target this step
3246                     if ( href && href[ 0 ] === "#" ) {
3247                         target = document.getElementById( href.slice( 1 ) );
3248                     }
3249                 }
3250
3251                 if ( api.goto( target ) ) {
3252                     event.stopImmediatePropagation();
3253                     event.preventDefault();
3254                 }
3255             }
3256             catch ( err ) {
3257
3258                 // For example, when clicking on the button to launch speaker console, the button
3259                 // is immediately deleted from the DOM. In this case target is a DOM element when
3260                 // we get it, but turns out to be null if you try to actually do anything with it.
3261                 if ( err instanceof TypeError &&
3262                      err.message === "target is null" ) {
3263                     return;
3264                 }
3265                 throw err;
3266             }
3267         }, false );
3268
3269         // Delegated handler for clicking on step elements
3270         gc.addEventListener( document, "click", function( event ) {
3271             var target = event.target;
3272             try {
3273
3274                 // Find closest step element that is not active
3275                 while ( !( target.classList.contains( "step" ) &&
3276                         !target.classList.contains( "active" ) ) &&
3277                         ( target !== document.documentElement ) ) {
3278                     target = target.parentNode;
3279                 }
3280
3281                 if ( api.goto( target ) ) {
3282                     event.preventDefault();
3283                 }
3284             }
3285             catch ( err ) {
3286
3287                 // For example, when clicking on the button to launch speaker console, the button
3288                 // is immediately deleted from the DOM. In this case target is a DOM element when
3289                 // we get it, but turns out to be null if you try to actually do anything with it.
3290                 if ( err instanceof TypeError &&
3291                      err.message === "target is null" ) {
3292                     return;
3293                 }
3294                 throw err;
3295             }
3296         }, false );
3297
3298         // Add a line to the help popup
3299         util.triggerEvent( document, "impress:help:add", { command: "Left &amp; Right",
3300                                                            text: "Previous &amp; Next step",
3301                                                            row: 1 } );
3302
3303     }, false );
3304
3305 } )( document );
3306
3307
3308 /**
3309  * Navigation UI plugin
3310  *
3311  * This plugin provides UI elements "back", "forward" and a list to select
3312  * a specific slide number.
3313  *
3314  * The navigation controls are added to the toolbar plugin via DOM events. User must enable the
3315  * toolbar in a presentation to have them visible.
3316  *
3317  * Copyright 2016 Henrik Ingo (@henrikingo)
3318  * Released under the MIT license.
3319  */
3320
3321 // This file contains so much HTML, that we will just respectfully disagree about js
3322 /* jshint quotmark:single */
3323 /* global document */
3324
3325 ( function( document ) {
3326     'use strict';
3327     var toolbar;
3328     var api;
3329     var root;
3330     var steps;
3331     var hideSteps = [];
3332     var prev;
3333     var select;
3334     var next;
3335
3336     var triggerEvent = function( el, eventName, detail ) {
3337         var event = document.createEvent( 'CustomEvent' );
3338         event.initCustomEvent( eventName, true, true, detail );
3339         el.dispatchEvent( event );
3340     };
3341
3342     var makeDomElement = function( html ) {
3343         var tempDiv = document.createElement( 'div' );
3344         tempDiv.innerHTML = html;
3345         return tempDiv.firstChild;
3346     };
3347
3348     var selectOptionsHtml = function() {
3349         var options = '';
3350         for ( var i = 0; i < steps.length; i++ ) {
3351
3352             // Omit steps that are listed as hidden from select widget
3353             if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {
3354                 options = options + '<option value="' + steps[ i ].id + '">' + // jshint ignore:line
3355                                     steps[ i ].id + '</option>' + '\n'; // jshint ignore:line
3356             }
3357         }
3358         return options;
3359     };
3360
3361     var addNavigationControls = function( event ) {
3362         api = event.detail.api;
3363         var gc = api.lib.gc;
3364         root = event.target;
3365         steps = root.querySelectorAll( '.step' );
3366
3367         var prevHtml   = '<button id="impress-navigation-ui-prev" title="Previous" ' +
3368                          'class="impress-navigation-ui">&lt;</button>';
3369         var selectHtml = '<select id="impress-navigation-ui-select" title="Go to" ' +
3370                          'class="impress-navigation-ui">' + '\n' +
3371                            selectOptionsHtml() +
3372                            '</select>';
3373         var nextHtml   = '<button id="impress-navigation-ui-next" title="Next" ' +
3374                          'class="impress-navigation-ui">&gt;</button>';
3375
3376         prev = makeDomElement( prevHtml );
3377         prev.addEventListener( 'click',
3378             function() {
3379                 api.prev();
3380         } );
3381         select = makeDomElement( selectHtml );
3382         select.addEventListener( 'change',
3383             function( event ) {
3384                 api.goto( event.target.value );
3385         } );
3386         gc.addEventListener( root, 'impress:steprefresh', function( event ) {
3387
3388             // As impress.js core now allows to dynamically edit the steps, including adding,
3389             // removing, and reordering steps, we need to requery and redraw the select list on
3390             // every stepenter event.
3391             steps = root.querySelectorAll( '.step' );
3392             select.innerHTML = '\n' + selectOptionsHtml();
3393
3394             // Make sure the list always shows the step we're actually on, even if it wasn't
3395             // selected from the list
3396             select.value = event.target.id;
3397         } );
3398         next = makeDomElement( nextHtml );
3399         next.addEventListener( 'click',
3400             function() {
3401                 api.next();
3402         } );
3403
3404         triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );
3405         triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );
3406         triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );
3407
3408     };
3409
3410     // API for not listing given step in the select widget.
3411     // For example, if you set class="skip" on some element, you may not want it to show up in the
3412     // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted
3413     // must be specifically added with this API call.
3414     document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {
3415         hideSteps.push( event.target );
3416         if ( select ) {
3417             select.innerHTML = selectOptionsHtml();
3418         }
3419     }, false );
3420
3421     // Wait for impress.js to be initialized
3422     document.addEventListener( 'impress:init', function( event ) {
3423         toolbar = document.querySelector( '#impress-toolbar' );
3424         if ( toolbar ) {
3425             addNavigationControls( event );
3426         }
3427     }, false );
3428
3429 } )( document );
3430
3431
3432 /* global document */
3433 ( function( document ) {
3434     "use strict";
3435     var root;
3436     var stepids = [];
3437
3438     // Get stepids from the steps under impress root
3439     var getSteps = function() {
3440         stepids = [];
3441         var steps = root.querySelectorAll( ".step" );
3442         for ( var i = 0; i < steps.length; i++ )
3443         {
3444           stepids[ i + 1 ] = steps[ i ].id;
3445         }
3446         };
3447
3448     // Wait for impress.js to be initialized
3449     document.addEventListener( "impress:init", function( event ) {
3450             root = event.target;
3451         getSteps();
3452         var gc = event.detail.api.lib.gc;
3453         gc.pushCallback( function() {
3454             stepids = [];
3455             if ( progressbar ) {
3456                 progressbar.style.width = "";
3457                         }
3458             if ( progress ) {
3459                 progress.innerHTML = "";
3460                         }
3461         } );
3462     } );
3463
3464     var progressbar = document.querySelector( "div.impress-progressbar div" );
3465     var progress = document.querySelector( "div.impress-progress" );
3466
3467     if ( null !== progressbar || null !== progress ) {
3468         document.addEventListener( "impress:stepleave", function( event ) {
3469             updateProgressbar( event.detail.next.id );
3470         } );
3471
3472         document.addEventListener( "impress:steprefresh", function( event ) {
3473             getSteps();
3474             updateProgressbar( event.target.id );
3475         } );
3476
3477     }
3478
3479     function updateProgressbar( slideId ) {
3480         var slideNumber = stepids.indexOf( slideId );
3481         if ( null !== progressbar ) {
3482                         var width = 100 / ( stepids.length - 1 ) * ( slideNumber );
3483             progressbar.style.width = width.toFixed( 2 ) + "%";
3484         }
3485         if ( null !== progress ) {
3486             progress.innerHTML = slideNumber + "/" + ( stepids.length - 1 );
3487         }
3488     }
3489 } )( document );
3490
3491 /**
3492  * Relative Positioning Plugin
3493  *
3494  * This plugin provides support for defining the coordinates of a step relative
3495  * to the previous step. This is often more convenient when creating presentations,
3496  * since as you add, remove or move steps, you may not need to edit the positions
3497  * as much as is the case with the absolute coordinates supported by impress.js
3498  * core.
3499  *
3500  * Example:
3501  *
3502  *         <!-- Position step 1000 px to the right and 500 px up from the previous step. -->
3503  *         <div class="step" data-rel-x="1000" data-rel-y="500">
3504  *
3505  * Following html attributes are supported for step elements:
3506  *
3507  *     data-rel-x
3508  *     data-rel-y
3509  *     data-rel-z
3510  *
3511  * These values are also inherited from the previous step. This makes it easy to
3512  * create a boring presentation where each slide shifts for example 1000px down
3513  * from the previous.
3514  *
3515  * In addition to plain numbers, which are pixel values, it is also possible to
3516  * define relative positions as a multiple of screen height and width, using
3517  * a unit of "h" and "w", respectively, appended to the number.
3518  *
3519  * Example:
3520  *
3521  *        <div class="step" data-rel-x="1.5w" data-rel-y="1.5h">
3522  *
3523  * This plugin is a *pre-init plugin*. It is called synchronously from impress.js
3524  * core at the beginning of `impress().init()`. This allows it to process its own
3525  * data attributes first, and possibly alter the data-x, data-y and data-z attributes
3526  * that will then be processed by `impress().init()`.
3527  *
3528  * (Another name for this kind of plugin might be called a *filter plugin*, but
3529  * *pre-init plugin* is more generic, as a plugin might do whatever it wants in
3530  * the pre-init stage.)
3531  *
3532  * Copyright 2016 Henrik Ingo (@henrikingo)
3533  * Released under the MIT license.
3534  */
3535
3536 /* global document, window */
3537
3538 ( function( document, window ) {
3539     "use strict";
3540
3541     var startingState = {};
3542
3543     /**
3544      * Copied from core impress.js. We currently lack a library mechanism to
3545      * to share utility functions like this.
3546      */
3547     var toNumber = function( numeric, fallback ) {
3548         return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
3549     };
3550
3551     /**
3552      * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
3553      *
3554      * Returns the computed value in pixels with w/h postfix removed.
3555      */
3556     var toNumberAdvanced = function( numeric, fallback ) {
3557         if ( typeof numeric !== "string" ) {
3558             return toNumber( numeric, fallback );
3559         }
3560         var ratio = numeric.match( /^([+-]*[\d\.]+)([wh])$/ );
3561         if ( ratio == null ) {
3562             return toNumber( numeric, fallback );
3563         } else {
3564             var value = parseFloat( ratio[ 1 ] );
3565             var multiplier = ratio[ 2 ] === "w" ? window.innerWidth : window.innerHeight;
3566             return value * multiplier;
3567         }
3568     };
3569
3570     var computeRelativePositions = function( el, prev ) {
3571         var data = el.dataset;
3572
3573         if ( !prev ) {
3574
3575             // For the first step, inherit these defaults
3576             prev = { x:0, y:0, z:0, relative: { x:0, y:0, z:0 } };
3577         }
3578
3579         if ( data.relTo ) {
3580
3581             var ref = document.getElementById( data.relTo );
3582             if ( ref ) {
3583
3584                 // Test, if it is a previous step that already has some assigned position data
3585                 if ( el.compareDocumentPosition( ref ) & Node.DOCUMENT_POSITION_PRECEDING ) {
3586                     prev.x = toNumber( ref.getAttribute( "data-x" ) );
3587                     prev.y = toNumber( ref.getAttribute( "data-y" ) );
3588                     prev.z = toNumber( ref.getAttribute( "data-z" ) );
3589                     prev.relative = {};
3590                 } else {
3591                     window.console.error(
3592                         "impress.js rel plugin: Step \"" + data.relTo + "\" is not defined " +
3593                         "*before* the current step. Referencing is limited to previously defined " +
3594                         "steps. Please check your markup. Ignoring data-rel-to attribute of " +
3595                         "this step. Have a look at the documentation for how to create relative " +
3596                         "positioning to later shown steps with the help of the goto plugin."
3597                     );
3598                 }
3599             } else {
3600
3601                 // Step not found
3602                 window.console.warn(
3603                     "impress.js rel plugin: \"" + data.relTo + "\" is not a valid step in this " +
3604                     "impress.js presentation. Please check your markup. Ignoring data-rel-to " +
3605                     "attribute of this step."
3606                 );
3607             }
3608         }
3609
3610         var step = {
3611                 x: toNumber( data.x, prev.x ),
3612                 y: toNumber( data.y, prev.y ),
3613                 z: toNumber( data.z, prev.z ),
3614                 relative: {
3615                     x: toNumberAdvanced( data.relX, prev.relative.x ),
3616                     y: toNumberAdvanced( data.relY, prev.relative.y ),
3617                     z: toNumberAdvanced( data.relZ, prev.relative.z )
3618                 }
3619             };
3620
3621         // Relative position is ignored/zero if absolute is given.
3622         // Note that this also has the effect of resetting any inherited relative values.
3623         if ( data.x !== undefined ) {
3624             step.relative.x = 0;
3625         }
3626         if ( data.y !== undefined ) {
3627             step.relative.y = 0;
3628         }
3629         if ( data.z !== undefined ) {
3630             step.relative.z = 0;
3631         }
3632
3633         // Apply relative position to absolute position, if non-zero
3634         // Note that at this point, the relative values contain a number value of pixels.
3635         step.x = step.x + step.relative.x;
3636         step.y = step.y + step.relative.y;
3637         step.z = step.z + step.relative.z;
3638
3639         return step;
3640     };
3641
3642     var rel = function( root ) {
3643         var steps = root.querySelectorAll( ".step" );
3644         var prev;
3645         startingState[ root.id ] = [];
3646         for ( var i = 0; i < steps.length; i++ ) {
3647             var el = steps[ i ];
3648             startingState[ root.id ].push( {
3649                 el: el,
3650                 x: el.getAttribute( "data-x" ),
3651                 y: el.getAttribute( "data-y" ),
3652                 z: el.getAttribute( "data-z" )
3653             } );
3654             var step = computeRelativePositions( el, prev );
3655
3656             // Apply relative position (if non-zero)
3657             el.setAttribute( "data-x", step.x );
3658             el.setAttribute( "data-y", step.y );
3659             el.setAttribute( "data-z", step.z );
3660             prev = step;
3661         }
3662     };
3663
3664     // Register the plugin to be called in pre-init phase
3665     window.impress.addPreInitPlugin( rel );
3666
3667     // Register teardown callback to reset the data.x, .y, .z values.
3668     document.addEventListener( "impress:init", function( event ) {
3669         var root = event.target;
3670         event.detail.api.lib.gc.pushCallback( function() {
3671             var steps = startingState[ root.id ];
3672             var step;
3673             while ( step = steps.pop() ) {
3674                 if ( step.x === null ) {
3675                     step.el.removeAttribute( "data-x" );
3676                 } else {
3677                     step.el.setAttribute( "data-x", step.x );
3678                 }
3679                 if ( step.y === null ) {
3680                     step.el.removeAttribute( "data-y" );
3681                 } else {
3682                     step.el.setAttribute( "data-y", step.y );
3683                 }
3684                 if ( step.z === null ) {
3685                     step.el.removeAttribute( "data-z" );
3686                 } else {
3687                     step.el.setAttribute( "data-z", step.z );
3688                 }
3689             }
3690             delete startingState[ root.id ];
3691         } );
3692     }, false );
3693 } )( document, window );
3694
3695
3696 /**
3697  * Resize plugin
3698  *
3699  * Rescale the presentation after a window resize.
3700  *
3701  * Copyright 2011-2012 Bartek Szopka (@bartaz)
3702  * Released under the MIT license.
3703  * ------------------------------------------------
3704  *  author:  Bartek Szopka
3705  *  version: 0.5.3
3706  *  url:     http://bartaz.github.com/impress.js/
3707  *  source:  http://github.com/bartaz/impress.js/
3708  *
3709  */
3710
3711 /* global document, window */
3712
3713 ( function( document, window ) {
3714     "use strict";
3715
3716     // Wait for impress.js to be initialized
3717     document.addEventListener( "impress:init", function( event ) {
3718         var api = event.detail.api;
3719
3720         // Rescale presentation when window is resized
3721         api.lib.gc.addEventListener( window, "resize", api.lib.util.throttle( function() {
3722
3723             // Force going to active step again, to trigger rescaling
3724             api.goto( document.querySelector( ".step.active" ), 500 );
3725         }, 250 ), false );
3726     }, false );
3727
3728 } )( document, window );
3729
3730
3731 /**
3732  * Skip Plugin
3733  *
3734  * Example:
3735  *
3736  *    <!-- This slide is disabled in presentations, when moving with next()
3737  *         and prev() commands, but you can still move directly to it, for
3738  *         example with a url (anything using goto()). -->
3739  *         <div class="step skip">
3740  *
3741  * Copyright 2016 Henrik Ingo (@henrikingo)
3742  * Released under the MIT license.
3743  */
3744
3745 /* global document, window */
3746
3747 ( function( document, window ) {
3748     "use strict";
3749     var util;
3750
3751     document.addEventListener( "impress:init", function( event ) {
3752         util = event.detail.api.lib.util;
3753     }, false );
3754
3755     var getNextStep = function( el ) {
3756         var steps = document.querySelectorAll( ".step" );
3757         for ( var i = 0; i < steps.length; i++ ) {
3758             if ( steps[ i ] === el ) {
3759                 if ( i + 1 < steps.length ) {
3760                     return steps[ i + 1 ];
3761                 } else {
3762                     return steps[ 0 ];
3763                 }
3764             }
3765         }
3766     };
3767     var getPrevStep = function( el ) {
3768         var steps = document.querySelectorAll( ".step" );
3769         for ( var i = steps.length - 1; i >= 0; i-- ) {
3770             if ( steps[ i ] === el ) {
3771                 if ( i - 1 >= 0 ) {
3772                     return steps[ i - 1 ];
3773                 } else {
3774                     return steps[ steps.length - 1 ];
3775                 }
3776             }
3777         }
3778     };
3779
3780     var skip = function( event ) {
3781         if ( ( !event ) || ( !event.target ) ) {
3782             return;
3783         }
3784
3785         if ( event.detail.next.classList.contains( "skip" ) ) {
3786             if ( event.detail.reason === "next" ) {
3787
3788                 // Go to the next next step instead
3789                 event.detail.next = getNextStep( event.detail.next );
3790
3791                 // Recursively call this plugin again, until there's a step not to skip
3792                 skip( event );
3793             } else if ( event.detail.reason === "prev" ) {
3794
3795                 // Go to the previous previous step instead
3796                 event.detail.next = getPrevStep( event.detail.next );
3797                 skip( event );
3798             }
3799
3800             // If the new next element has its own transitionDuration, we're responsible for setting
3801             // that on the event as well
3802             event.detail.transitionDuration = util.toNumber(
3803                 event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
3804             );
3805         }
3806     };
3807
3808     // Register the plugin to be called in pre-stepleave phase
3809     // The weight makes this plugin run early. This is a good thing, because this plugin calls
3810     // itself recursively.
3811     window.impress.addPreStepLeavePlugin( skip, 1 );
3812
3813 } )( document, window );
3814
3815
3816 /**
3817  * Stop Plugin
3818  *
3819  * Example:
3820  *
3821  *        <!-- Stop at this slide.
3822  *             (For example, when used on the last slide, this prevents the
3823  *             presentation from wrapping back to the beginning.) -->
3824  *        <div class="step stop">
3825  *
3826  * Copyright 2016 Henrik Ingo (@henrikingo)
3827  * Released under the MIT license.
3828  */
3829 /* global document, window */
3830 ( function( document, window ) {
3831     "use strict";
3832
3833     var stop = function( event ) {
3834         if ( ( !event ) || ( !event.target ) ) {
3835             return;
3836         }
3837
3838         if ( event.target.classList.contains( "stop" ) ) {
3839             if ( event.detail.reason === "next" ) {
3840                 return false;
3841             }
3842         }
3843     };
3844
3845     // Register the plugin to be called in pre-stepleave phase
3846     // The weight makes this plugin run fairly early.
3847     window.impress.addPreStepLeavePlugin( stop, 2 );
3848
3849 } )( document, window );
3850
3851
3852 /**
3853  * Substep Plugin
3854  *
3855  * Copyright 2017 Henrik Ingo (@henrikingo)
3856  * Released under the MIT license.
3857  */
3858
3859 /* global document, window */
3860
3861 ( function( document, window ) {
3862     "use strict";
3863
3864     // Copied from core impress.js. Good candidate for moving to src/lib/util.js.
3865     var triggerEvent = function( el, eventName, detail ) {
3866         var event = document.createEvent( "CustomEvent" );
3867         event.initCustomEvent( eventName, true, true, detail );
3868         el.dispatchEvent( event );
3869     };
3870
3871     var activeStep = null;
3872     document.addEventListener( "impress:stepenter", function( event ) {
3873         activeStep = event.target;
3874     }, false );
3875
3876     var substep = function( event ) {
3877         if ( ( !event ) || ( !event.target ) ) {
3878             return;
3879         }
3880
3881         var step = event.target;
3882         var el; // Needed by jshint
3883         if ( event.detail.reason === "next" ) {
3884             el = showSubstepIfAny( step );
3885             if ( el ) {
3886
3887                 // Send a message to others, that we aborted a stepleave event.
3888                 // Autoplay will reload itself from this, as there won't be a stepenter event now.
3889                 triggerEvent( step, "impress:substep:stepleaveaborted",
3890                               { reason: "next", substep: el } );
3891
3892                 // Returning false aborts the stepleave event
3893                 return false;
3894             }
3895         }
3896         if ( event.detail.reason === "prev" ) {
3897             el = hideSubstepIfAny( step );
3898             if ( el ) {
3899                 triggerEvent( step, "impress:substep:stepleaveaborted",
3900                               { reason: "prev", substep: el } );
3901                 return false;
3902             }
3903         }
3904     };
3905
3906     var showSubstepIfAny = function( step ) {
3907         var substeps = step.querySelectorAll( ".substep" );
3908         var visible = step.querySelectorAll( ".substep-visible" );
3909         if ( substeps.length > 0 ) {
3910             return showSubstep( substeps, visible );
3911         }
3912     };
3913
3914     var showSubstep = function( substeps, visible ) {
3915         if ( visible.length < substeps.length ) {
3916             var el = substeps[ visible.length ];
3917             el.classList.add( "substep-visible" );
3918             return el;
3919         }
3920     };
3921
3922     var hideSubstepIfAny = function( step ) {
3923         var substeps = step.querySelectorAll( ".substep" );
3924         var visible = step.querySelectorAll( ".substep-visible" );
3925         if ( substeps.length > 0 ) {
3926             return hideSubstep( visible );
3927         }
3928     };
3929
3930     var hideSubstep = function( visible ) {
3931         if ( visible.length > 0 ) {
3932             var el = visible[ visible.length - 1 ];
3933             el.classList.remove( "substep-visible" );
3934             return el;
3935         }
3936     };
3937
3938     // Register the plugin to be called in pre-stepleave phase.
3939     // The weight makes this plugin run before other preStepLeave plugins.
3940     window.impress.addPreStepLeavePlugin( substep, 1 );
3941
3942     // When entering a step, in particular when re-entering, make sure that all substeps are hidden
3943     // at first
3944     document.addEventListener( "impress:stepenter", function( event ) {
3945         var step = event.target;
3946         var visible = step.querySelectorAll( ".substep-visible" );
3947         for ( var i = 0; i < visible.length; i++ ) {
3948             visible[ i ].classList.remove( "substep-visible" );
3949         }
3950     }, false );
3951
3952     // API for others to reveal/hide next substep ////////////////////////////////////////////////
3953     document.addEventListener( "impress:substep:show", function() {
3954         showSubstepIfAny( activeStep );
3955     }, false );
3956
3957     document.addEventListener( "impress:substep:hide", function() {
3958         hideSubstepIfAny( activeStep );
3959     }, false );
3960
3961 } )( document, window );
3962
3963
3964 /**
3965  * Support for swipe and tap on touch devices
3966  *
3967  * This plugin implements navigation for plugin devices, via swiping left/right,
3968  * or tapping on the left/right edges of the screen.
3969  *
3970  *
3971  *
3972  * Copyright 2015: Andrew Dunai (@and3rson)
3973  * Modified to a plugin, 2016: Henrik Ingo (@henrikingo)
3974  *
3975  * MIT License
3976  */
3977 /* global document, window */
3978 ( function( document, window ) {
3979     "use strict";
3980
3981     // Touch handler to detect swiping left and right based on window size.
3982     // If the difference in X change is bigger than 1/20 of the screen width,
3983     // we simply call an appropriate API function to complete the transition.
3984     var startX = 0;
3985     var lastX = 0;
3986     var lastDX = 0;
3987     var threshold = window.innerWidth / 20;
3988
3989     document.addEventListener( "touchstart", function( event ) {
3990         lastX = startX = event.touches[ 0 ].clientX;
3991     } );
3992
3993     document.addEventListener( "touchmove", function( event ) {
3994          var x = event.touches[ 0 ].clientX;
3995          var diff = x - startX;
3996
3997          // To be used in touchend
3998          lastDX = lastX - x;
3999          lastX = x;
4000
4001          window.impress().swipe( diff / window.innerWidth );
4002      } );
4003
4004      document.addEventListener( "touchend", function() {
4005          var totalDiff = lastX - startX;
4006          if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {
4007              if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {
4008                  window.impress().prev();
4009              } else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {
4010                  window.impress().next();
4011              }
4012          } else if ( Math.abs( lastDX ) > threshold ) {
4013              if ( lastDX < -threshold ) {
4014                  window.impress().prev();
4015              } else if ( lastDX > threshold ) {
4016                  window.impress().next();
4017              }
4018          } else {
4019
4020              // No movement - move (back) to the current slide
4021              window.impress().goto( document.querySelector( "#impress .step.active" ) );
4022          }
4023      } );
4024
4025      document.addEventListener( "touchcancel", function() {
4026
4027              // Move (back) to the current slide
4028              window.impress().goto( document.querySelector( "#impress .step.active" ) );
4029      } );
4030
4031 } )( document, window );
4032
4033 /**
4034  * Toolbar plugin
4035  *
4036  * This plugin provides a generic graphical toolbar. Other plugins that
4037  * want to expose a button or other widget, can add those to this toolbar.
4038  *
4039  * Using a single consolidated toolbar for all GUI widgets makes it easier
4040  * to position and style the toolbar rather than having to do that for lots
4041  * of different divs.
4042  *
4043  *
4044  * *** For presentation authors: *****************************************
4045  *
4046  * To add/activate the toolbar in your presentation, add this div:
4047  *
4048  *     <div id="impress-toolbar"></div>
4049  *
4050  * Styling the toolbar is left to presentation author. Here's an example CSS:
4051  *
4052  *    .impress-enabled div#impress-toolbar {
4053  *        position: fixed;
4054  *        right: 1px;
4055  *        bottom: 1px;
4056  *        opacity: 0.6;
4057  *    }
4058  *    .impress-enabled div#impress-toolbar > span {
4059  *        margin-right: 10px;
4060  *    }
4061  *
4062  * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide
4063  * the toolbar from sight, and only make it visible when mouse is moved.
4064  *
4065  *    body.impress-mouse-timeout div#impress-toolbar {
4066  *        display: none;
4067  *    }
4068  *
4069  *
4070  * *** For plugin authors **********************************************
4071  *
4072  * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`
4073  * or `impress:toolbar:insertBefore` events as appropriate. The detail object
4074  * should contain following parameters:
4075  *
4076  *    { group : 1,                       // integer. Widgets with the same group are grouped inside
4077  *                                       // the same <span> element.
4078  *      html : "<button>Click</button>", // The html to add.
4079  *      callback : "mycallback",         // Toolbar plugin will trigger event
4080  *                                       // `impress:toolbar:added:mycallback` when done.
4081  *      before: element }                // The reference element for an insertBefore() call.
4082  *
4083  * You should also listen to the `impress:toolbar:added:mycallback` event. At
4084  * this point you can find the new widget in the DOM, and for example add an
4085  * event listener to it.
4086  *
4087  * You are free to use any integer for the group. It's ok to leave gaps. It's
4088  * ok to co-locate with widgets for another plugin, if you think they belong
4089  * together.
4090  *
4091  * See navigation-ui for an example.
4092  *
4093  * Copyright 2016 Henrik Ingo (@henrikingo)
4094  * Released under the MIT license.
4095  */
4096
4097 /* global document */
4098
4099 ( function( document ) {
4100     "use strict";
4101     var toolbar = document.getElementById( "impress-toolbar" );
4102     var groups = [];
4103
4104     /**
4105      * Get the span element that is a child of toolbar, identified by index.
4106      *
4107      * If span element doesn't exist yet, it is created.
4108      *
4109      * Note: Because of Run-to-completion, this is not a race condition.
4110      * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion
4111      *
4112      * :param: index   Method will return the element <span id="impress-toolbar-group-{index}">
4113      */
4114     var getGroupElement = function( index ) {
4115         var id = "impress-toolbar-group-" + index;
4116         if ( !groups[ index ] ) {
4117             groups[ index ] = document.createElement( "span" );
4118             groups[ index ].id = id;
4119             var nextIndex = getNextGroupIndex( index );
4120             if ( nextIndex === undefined ) {
4121                 toolbar.appendChild( groups[ index ] );
4122             } else {
4123                 toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );
4124             }
4125         }
4126         return groups[ index ];
4127     };
4128
4129     /**
4130      * Get the span element from groups[] that is immediately after given index.
4131      *
4132      * This can be used to find the reference node for an insertBefore() call.
4133      * If no element exists at a larger index, returns undefined. (In this case,
4134      * you'd use appendChild() instead.)
4135      *
4136      * Note that index needn't itself exist in groups[].
4137      */
4138     var getNextGroupIndex = function( index ) {
4139         var i = index + 1;
4140         while ( !groups[ i ] && i < groups.length ) {
4141             i++;
4142         }
4143         if ( i < groups.length ) {
4144             return i;
4145         }
4146     };
4147
4148     // API
4149     // Other plugins can add and remove buttons by sending them as events.
4150     // In return, toolbar plugin will trigger events when button was added.
4151     if ( toolbar ) {
4152         /**
4153          * Append a widget inside toolbar span element identified by given group index.
4154          *
4155          * :param: e.detail.group    integer specifying the span element where widget will be placed
4156          * :param: e.detail.element  a dom element to add to the toolbar
4157          */
4158         toolbar.addEventListener( "impress:toolbar:appendChild", function( e ) {
4159             var group = getGroupElement( e.detail.group );
4160             group.appendChild( e.detail.element );
4161         } );
4162
4163         /**
4164          * Add a widget to toolbar using insertBefore() DOM method.
4165          *
4166          * :param: e.detail.before   the reference dom element, before which new element is added
4167          * :param: e.detail.element  a dom element to add to the toolbar
4168          */
4169         toolbar.addEventListener( "impress:toolbar:insertBefore", function( e ) {
4170             toolbar.insertBefore( e.detail.element, e.detail.before );
4171         } );
4172
4173         /**
4174          * Remove the widget in e.detail.remove.
4175          */
4176         toolbar.addEventListener( "impress:toolbar:removeWidget", function( e ) {
4177             toolbar.removeChild( e.detail.remove );
4178         } );
4179
4180         document.addEventListener( "impress:init", function( event ) {
4181             var api = event.detail.api;
4182             api.lib.gc.pushCallback( function() {
4183                 toolbar.innerHTML = "";
4184                 groups = [];
4185             } );
4186         } );
4187     } // If toolbar
4188
4189 } )( document );