summary refs log tree commit diff stats
path: root/geschichte/js
diff options
context:
space:
mode:
Diffstat (limited to 'geschichte/js')
-rw-r--r--geschichte/js/components/playback.js165
-rw-r--r--geschichte/js/config.js300
-rw-r--r--geschichte/js/controllers/autoanimate.js640
-rw-r--r--geschichte/js/controllers/backgrounds.js406
-rw-r--r--geschichte/js/controllers/controls.js266
-rw-r--r--geschichte/js/controllers/focus.js103
-rw-r--r--geschichte/js/controllers/fragments.js376
-rw-r--r--geschichte/js/controllers/jumptoslide.js170
-rw-r--r--geschichte/js/controllers/keyboard.js399
-rw-r--r--geschichte/js/controllers/location.js245
-rw-r--r--geschichte/js/controllers/notes.js120
-rw-r--r--geschichte/js/controllers/overview.js255
-rw-r--r--geschichte/js/controllers/plugins.js254
-rw-r--r--geschichte/js/controllers/pointer.js129
-rw-r--r--geschichte/js/controllers/print.js237
-rw-r--r--geschichte/js/controllers/progress.js110
-rw-r--r--geschichte/js/controllers/slidecontent.js480
-rw-r--r--geschichte/js/controllers/slidenumber.js132
-rw-r--r--geschichte/js/controllers/touch.js263
-rw-r--r--geschichte/js/index.js58
-rw-r--r--geschichte/js/reveal.js2836
-rw-r--r--geschichte/js/utils/color.js77
-rw-r--r--geschichte/js/utils/constants.js10
-rw-r--r--geschichte/js/utils/device.js8
-rw-r--r--geschichte/js/utils/loader.js46
-rw-r--r--geschichte/js/utils/util.js313
26 files changed, 0 insertions, 8398 deletions
diff --git a/geschichte/js/components/playback.js b/geschichte/js/components/playback.js
deleted file mode 100644
index 06fa7ba..0000000
--- a/geschichte/js/components/playback.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * UI component that lets the use control auto-slide
- * playback via play/pause.
- */
-export default class Playback {
-
-	/**
-	 * @param {HTMLElement} container The component will append
-	 * itself to this
-	 * @param {function} progressCheck A method which will be
-	 * called frequently to get the current playback progress on
-	 * a range of 0-1
-	 */
-	constructor( container, progressCheck ) {
-
-		// Cosmetics
-		this.diameter = 100;
-		this.diameter2 = this.diameter/2;
-		this.thickness = 6;
-
-		// Flags if we are currently playing
-		this.playing = false;
-
-		// Current progress on a 0-1 range
-		this.progress = 0;
-
-		// Used to loop the animation smoothly
-		this.progressOffset = 1;
-
-		this.container = container;
-		this.progressCheck = progressCheck;
-
-		this.canvas = document.createElement( 'canvas' );
-		this.canvas.className = 'playback';
-		this.canvas.width = this.diameter;
-		this.canvas.height = this.diameter;
-		this.canvas.style.width = this.diameter2 + 'px';
-		this.canvas.style.height = this.diameter2 + 'px';
-		this.context = this.canvas.getContext( '2d' );
-
-		this.container.appendChild( this.canvas );
-
-		this.render();
-
-	}
-
-	setPlaying( value ) {
-
-		const wasPlaying = this.playing;
-
-		this.playing = value;
-
-		// Start repainting if we weren't already
-		if( !wasPlaying && this.playing ) {
-			this.animate();
-		}
-		else {
-			this.render();
-		}
-
-	}
-
-	animate() {
-
-		const progressBefore = this.progress;
-
-		this.progress = this.progressCheck();
-
-		// When we loop, offset the progress so that it eases
-		// smoothly rather than immediately resetting
-		if( progressBefore > 0.8 && this.progress < 0.2 ) {
-			this.progressOffset = this.progress;
-		}
-
-		this.render();
-
-		if( this.playing ) {
-			requestAnimationFrame( this.animate.bind( this ) );
-		}
-
-	}
-
-	/**
-	 * Renders the current progress and playback state.
-	 */
-	render() {
-
-		let progress = this.playing ? this.progress : 0,
-			radius = ( this.diameter2 ) - this.thickness,
-			x = this.diameter2,
-			y = this.diameter2,
-			iconSize = 28;
-
-		// Ease towards 1
-		this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
-
-		const endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
-		const startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
-
-		this.context.save();
-		this.context.clearRect( 0, 0, this.diameter, this.diameter );
-
-		// Solid background color
-		this.context.beginPath();
-		this.context.arc( x, y, radius + 4, 0, Math.PI * 2, false );
-		this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
-		this.context.fill();
-
-		// Draw progress track
-		this.context.beginPath();
-		this.context.arc( x, y, radius, 0, Math.PI * 2, false );
-		this.context.lineWidth = this.thickness;
-		this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )';
-		this.context.stroke();
-
-		if( this.playing ) {
-			// Draw progress on top of track
-			this.context.beginPath();
-			this.context.arc( x, y, radius, startAngle, endAngle, false );
-			this.context.lineWidth = this.thickness;
-			this.context.strokeStyle = '#fff';
-			this.context.stroke();
-		}
-
-		this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
-
-		// Draw play/pause icons
-		if( this.playing ) {
-			this.context.fillStyle = '#fff';
-			this.context.fillRect( 0, 0, iconSize / 2 - 4, iconSize );
-			this.context.fillRect( iconSize / 2 + 4, 0, iconSize / 2 - 4, iconSize );
-		}
-		else {
-			this.context.beginPath();
-			this.context.translate( 4, 0 );
-			this.context.moveTo( 0, 0 );
-			this.context.lineTo( iconSize - 4, iconSize / 2 );
-			this.context.lineTo( 0, iconSize );
-			this.context.fillStyle = '#fff';
-			this.context.fill();
-		}
-
-		this.context.restore();
-
-	}
-
-	on( type, listener ) {
-		this.canvas.addEventListener( type, listener, false );
-	}
-
-	off( type, listener ) {
-		this.canvas.removeEventListener( type, listener, false );
-	}
-
-	destroy() {
-
-		this.playing = false;
-
-		if( this.canvas.parentNode ) {
-			this.container.removeChild( this.canvas );
-		}
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/config.js b/geschichte/js/config.js
deleted file mode 100644
index 8b7a883..0000000
--- a/geschichte/js/config.js
+++ /dev/null
@@ -1,300 +0,0 @@
-/**
- * The default reveal.js config object.
- */
-export default {
-
-	// The "normal" size of the presentation, aspect ratio will be preserved
-	// when the presentation is scaled to fit different resolutions
-	width: 960,
-	height: 700,
-
-	// Factor of the display size that should remain empty around the content
-	margin: 0.04,
-
-	// Bounds for smallest/largest possible scale to apply to content
-	minScale: 0.2,
-	maxScale: 2.0,
-
-	// Display presentation control arrows
-	controls: true,
-
-	// Help the user learn the controls by providing hints, for example by
-	// bouncing the down arrow when they first encounter a vertical slide
-	controlsTutorial: true,
-
-	// Determines where controls appear, "edges" or "bottom-right"
-	controlsLayout: 'bottom-right',
-
-	// Visibility rule for backwards navigation arrows; "faded", "hidden"
-	// or "visible"
-	controlsBackArrows: 'faded',
-
-	// Display a presentation progress bar
-	progress: true,
-
-	// Display the page number of the current slide
-	// - true:    Show slide number
-	// - false:   Hide slide number
-	//
-	// Can optionally be set as a string that specifies the number formatting:
-	// - "h.v":	  Horizontal . vertical slide number (default)
-	// - "h/v":	  Horizontal / vertical slide number
-	// - "c":	  Flattened slide number
-	// - "c/t":	  Flattened slide number / total slides
-	//
-	// Alternatively, you can provide a function that returns the slide
-	// number for the current slide. The function should take in a slide
-	// object and return an array with one string [slideNumber] or
-	// three strings [n1,delimiter,n2]. See #formatSlideNumber().
-	slideNumber: false,
-
-	// Can be used to limit the contexts in which the slide number appears
-	// - "all":      Always show the slide number
-	// - "print":    Only when printing to PDF
-	// - "speaker":  Only in the speaker view
-	showSlideNumber: 'all',
-
-	// Use 1 based indexing for # links to match slide number (default is zero
-	// based)
-	hashOneBasedIndex: false,
-
-	// Add the current slide number to the URL hash so that reloading the
-	// page/copying the URL will return you to the same slide
-	hash: false,
-
-	// Flags if we should monitor the hash and change slides accordingly
-	respondToHashChanges: true,
-
-	// Enable support for jump-to-slide navigation shortcuts
-	jumpToSlide: true,
-
-	// Push each slide change to the browser history.  Implies `hash: true`
-	history: false,
-
-	// Enable keyboard shortcuts for navigation
-	keyboard: true,
-
-	// Optional function that blocks keyboard events when retuning false
-	//
-	// If you set this to 'focused', we will only capture keyboard events
-	// for embedded decks when they are in focus
-	keyboardCondition: null,
-
-	// Disables the default reveal.js slide layout (scaling and centering)
-	// so that you can use custom CSS layout
-	disableLayout: false,
-
-	// Enable the slide overview mode
-	overview: true,
-
-	// Vertical centering of slides
-	center: true,
-
-	// Enables touch navigation on devices with touch input
-	touch: true,
-
-	// Loop the presentation
-	loop: false,
-
-	// Change the presentation direction to be RTL
-	rtl: false,
-
-	// Changes the behavior of our navigation directions.
-	//
-	// "default"
-	// Left/right arrow keys step between horizontal slides, up/down
-	// arrow keys step between vertical slides. Space key steps through
-	// all slides (both horizontal and vertical).
-	//
-	// "linear"
-	// Removes the up/down arrows. Left/right arrows step through all
-	// slides (both horizontal and vertical).
-	//
-	// "grid"
-	// When this is enabled, stepping left/right from a vertical stack
-	// to an adjacent vertical stack will land you at the same vertical
-	// index.
-	//
-	// Consider a deck with six slides ordered in two vertical stacks:
-	// 1.1    2.1
-	// 1.2    2.2
-	// 1.3    2.3
-	//
-	// If you're on slide 1.3 and navigate right, you will normally move
-	// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
-	// from 1.3 -> 2.3.
-	navigationMode: 'default',
-
-	// Randomizes the order of slides each time the presentation loads
-	shuffle: false,
-
-	// Turns fragments on and off globally
-	fragments: true,
-
-	// Flags whether to include the current fragment in the URL,
-	// so that reloading brings you to the same fragment position
-	fragmentInURL: true,
-
-	// Flags if the presentation is running in an embedded mode,
-	// i.e. contained within a limited portion of the screen
-	embedded: false,
-
-	// Flags if we should show a help overlay when the question-mark
-	// key is pressed
-	help: true,
-
-	// Flags if it should be possible to pause the presentation (blackout)
-	pause: true,
-
-	// Flags if speaker notes should be visible to all viewers
-	showNotes: false,
-
-	// Flags if slides with data-visibility="hidden" should be kep visible
-	showHiddenSlides: false,
-
-	// Global override for autoplaying embedded media (video/audio/iframe)
-	// - null:   Media will only autoplay if data-autoplay is present
-	// - true:   All media will autoplay, regardless of individual setting
-	// - false:  No media will autoplay, regardless of individual setting
-	autoPlayMedia: null,
-
-	// Global override for preloading lazy-loaded iframes
-	// - null:   Iframes with data-src AND data-preload will be loaded when within
-	//           the viewDistance, iframes with only data-src will be loaded when visible
-	// - true:   All iframes with data-src will be loaded when within the viewDistance
-	// - false:  All iframes with data-src will be loaded only when visible
-	preloadIframes: null,
-
-	// Can be used to globally disable auto-animation
-	autoAnimate: true,
-
-	// Optionally provide a custom element matcher that will be
-	// used to dictate which elements we can animate between.
-	autoAnimateMatcher: null,
-
-	// Default settings for our auto-animate transitions, can be
-	// overridden per-slide or per-element via data arguments
-	autoAnimateEasing: 'ease',
-	autoAnimateDuration: 1.0,
-	autoAnimateUnmatched: true,
-
-	// CSS properties that can be auto-animated. Position & scale
-	// is matched separately so there's no need to include styles
-	// like top/right/bottom/left, width/height or margin.
-	autoAnimateStyles: [
-		'opacity',
-		'color',
-		'background-color',
-		'padding',
-		'font-size',
-		'line-height',
-		'letter-spacing',
-		'border-width',
-		'border-color',
-		'border-radius',
-		'outline',
-		'outline-offset'
-	],
-
-	// Controls automatic progression to the next slide
-	// - 0:      Auto-sliding only happens if the data-autoslide HTML attribute
-	//           is present on the current slide or fragment
-	// - 1+:     All slides will progress automatically at the given interval
-	// - false:  No auto-sliding, even if data-autoslide is present
-	autoSlide: 0,
-
-	// Stop auto-sliding after user input
-	autoSlideStoppable: true,
-
-	// Use this method for navigation when auto-sliding (defaults to navigateNext)
-	autoSlideMethod: null,
-
-	// Specify the average time in seconds that you think you will spend
-	// presenting each slide. This is used to show a pacing timer in the
-	// speaker view
-	defaultTiming: null,
-
-	// Enable slide navigation via mouse wheel
-	mouseWheel: false,
-
-	// Opens links in an iframe preview overlay
-	// Add `data-preview-link` and `data-preview-link="false"` to customise each link
-	// individually
-	previewLinks: false,
-
-	// Exposes the reveal.js API through window.postMessage
-	postMessage: true,
-
-	// Dispatches all reveal.js events to the parent window through postMessage
-	postMessageEvents: false,
-
-	// Focuses body when page changes visibility to ensure keyboard shortcuts work
-	focusBodyOnPageVisibilityChange: true,
-
-	// Transition style
-	transition: 'slide', // none/fade/slide/convex/concave/zoom
-
-	// Transition speed
-	transitionSpeed: 'default', // default/fast/slow
-
-	// Transition style for full page slide backgrounds
-	backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
-
-	// Parallax background image
-	parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
-
-	// Parallax background size
-	parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
-
-	// Parallax background repeat
-	parallaxBackgroundRepeat: '', // repeat/repeat-x/repeat-y/no-repeat/initial/inherit
-
-	// Parallax background position
-	parallaxBackgroundPosition: '', // CSS syntax, e.g. "top left"
-
-	// Amount of pixels to move the parallax background per slide step
-	parallaxBackgroundHorizontal: null,
-	parallaxBackgroundVertical: null,
-
-	// The maximum number of pages a single slide can expand onto when printing
-	// to PDF, unlimited by default
-	pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
-
-	// Prints each fragment on a separate slide
-	pdfSeparateFragments: true,
-
-	// Offset used to reduce the height of content within exported PDF pages.
-	// This exists to account for environment differences based on how you
-	// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
-	// on precisely the total height of the document whereas in-browser
-	// printing has to end one pixel before.
-	pdfPageHeightOffset: -1,
-
-	// Number of slides away from the current that are visible
-	viewDistance: 3,
-
-	// Number of slides away from the current that are visible on mobile
-	// devices. It is advisable to set this to a lower number than
-	// viewDistance in order to save resources.
-	mobileViewDistance: 2,
-
-	// The display mode that will be used to show slides
-	display: 'block',
-
-	// Hide cursor if inactive
-	hideInactiveCursor: true,
-
-	// Time before the cursor is hidden (in ms)
-	hideCursorTime: 5000,
-
-	// Should we automatmically sort and set indices for fragments
-	// at each sync? (See Reveal.sync)
-	sortFragmentsOnSync: true,
-
-	// Script dependencies to load
-	dependencies: [],
-
-	// Plugin objects to register and use for this presentation
-	plugins: []
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/autoanimate.js b/geschichte/js/controllers/autoanimate.js
deleted file mode 100644
index 3fd2c99..0000000
--- a/geschichte/js/controllers/autoanimate.js
+++ /dev/null
@@ -1,640 +0,0 @@
-import { queryAll, extend, createStyleSheet, matches, closest } from '../utils/util.js'
-import { FRAGMENT_STYLE_REGEX } from '../utils/constants.js'
-
-// Counter used to generate unique IDs for auto-animated elements
-let autoAnimateCounter = 0;
-
-/**
- * Automatically animates matching elements across
- * slides with the [data-auto-animate] attribute.
- */
-export default class AutoAnimate {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-	}
-
-	/**
-	 * Runs an auto-animation between the given slides.
-	 *
-	 * @param  {HTMLElement} fromSlide
-	 * @param  {HTMLElement} toSlide
-	 */
-	run( fromSlide, toSlide ) {
-
-		// Clean up after prior animations
-		this.reset();
-
-		let allSlides = this.Reveal.getSlides();
-		let toSlideIndex = allSlides.indexOf( toSlide );
-		let fromSlideIndex = allSlides.indexOf( fromSlide );
-
-		// Ensure that both slides are auto-animate targets with the same data-auto-animate-id value
-		// (including null if absent on both) and that data-auto-animate-restart isn't set on the
-		// physically latter slide (independent of slide direction)
-		if( fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' )
-				&& fromSlide.getAttribute( 'data-auto-animate-id' ) === toSlide.getAttribute( 'data-auto-animate-id' ) 
-				&& !( toSlideIndex > fromSlideIndex ? toSlide : fromSlide ).hasAttribute( 'data-auto-animate-restart' ) ) {
-
-			// Create a new auto-animate sheet
-			this.autoAnimateStyleSheet = this.autoAnimateStyleSheet || createStyleSheet();
-
-			let animationOptions = this.getAutoAnimateOptions( toSlide );
-
-			// Set our starting state
-			fromSlide.dataset.autoAnimate = 'pending';
-			toSlide.dataset.autoAnimate = 'pending';
-
-			// Flag the navigation direction, needed for fragment buildup
-			animationOptions.slideDirection = toSlideIndex > fromSlideIndex ? 'forward' : 'backward';
-
-			// If the from-slide is hidden because it has moved outside
-			// the view distance, we need to temporarily show it while
-			// measuring
-			let fromSlideIsHidden = fromSlide.style.display === 'none';
-			if( fromSlideIsHidden ) fromSlide.style.display = this.Reveal.getConfig().display;
-
-			// Inject our auto-animate styles for this transition
-			let css = this.getAutoAnimatableElements( fromSlide, toSlide ).map( elements => {
-				return this.autoAnimateElements( elements.from, elements.to, elements.options || {}, animationOptions, autoAnimateCounter++ );
-			} );
-
-			if( fromSlideIsHidden ) fromSlide.style.display = 'none';
-
-			// Animate unmatched elements, if enabled
-			if( toSlide.dataset.autoAnimateUnmatched !== 'false' && this.Reveal.getConfig().autoAnimateUnmatched === true ) {
-
-				// Our default timings for unmatched elements
-				let defaultUnmatchedDuration = animationOptions.duration * 0.8,
-					defaultUnmatchedDelay = animationOptions.duration * 0.2;
-
-				this.getUnmatchedAutoAnimateElements( toSlide ).forEach( unmatchedElement => {
-
-					let unmatchedOptions = this.getAutoAnimateOptions( unmatchedElement, animationOptions );
-					let id = 'unmatched';
-
-					// If there is a duration or delay set specifically for this
-					// element our unmatched elements should adhere to those
-					if( unmatchedOptions.duration !== animationOptions.duration || unmatchedOptions.delay !== animationOptions.delay ) {
-						id = 'unmatched-' + autoAnimateCounter++;
-						css.push( `[data-auto-animate="running"] [data-auto-animate-target="${id}"] { transition: opacity ${unmatchedOptions.duration}s ease ${unmatchedOptions.delay}s; }` );
-					}
-
-					unmatchedElement.dataset.autoAnimateTarget = id;
-
-				}, this );
-
-				// Our default transition for unmatched elements
-				css.push( `[data-auto-animate="running"] [data-auto-animate-target="unmatched"] { transition: opacity ${defaultUnmatchedDuration}s ease ${defaultUnmatchedDelay}s; }` );
-
-			}
-
-			// Setting the whole chunk of CSS at once is the most
-			// efficient way to do this. Using sheet.insertRule
-			// is multiple factors slower.
-			this.autoAnimateStyleSheet.innerHTML = css.join( '' );
-
-			// Start the animation next cycle
-			requestAnimationFrame( () => {
-				if( this.autoAnimateStyleSheet ) {
-					// This forces our newly injected styles to be applied in Firefox
-					getComputedStyle( this.autoAnimateStyleSheet ).fontWeight;
-
-					toSlide.dataset.autoAnimate = 'running';
-				}
-			} );
-
-			this.Reveal.dispatchEvent({
-				type: 'autoanimate',
-				data: {
-					fromSlide,
-					toSlide,
-					sheet: this.autoAnimateStyleSheet
-				}
-			});
-
-		}
-
-	}
-
-	/**
-	 * Rolls back all changes that we've made to the DOM so
-	 * that as part of animating.
-	 */
-	reset() {
-
-		// Reset slides
-		queryAll( this.Reveal.getRevealElement(), '[data-auto-animate]:not([data-auto-animate=""])' ).forEach( element => {
-			element.dataset.autoAnimate = '';
-		} );
-
-		// Reset elements
-		queryAll( this.Reveal.getRevealElement(), '[data-auto-animate-target]' ).forEach( element => {
-			delete element.dataset.autoAnimateTarget;
-		} );
-
-		// Remove the animation sheet
-		if( this.autoAnimateStyleSheet && this.autoAnimateStyleSheet.parentNode ) {
-			this.autoAnimateStyleSheet.parentNode.removeChild( this.autoAnimateStyleSheet );
-			this.autoAnimateStyleSheet = null;
-		}
-
-	}
-
-	/**
-	 * Creates a FLIP animation where the `to` element starts out
-	 * in the `from` element position and animates to its original
-	 * state.
-	 *
-	 * @param {HTMLElement} from
-	 * @param {HTMLElement} to
-	 * @param {Object} elementOptions Options for this element pair
-	 * @param {Object} animationOptions Options set at the slide level
-	 * @param {String} id Unique ID that we can use to identify this
-	 * auto-animate element in the DOM
-	 */
-	autoAnimateElements( from, to, elementOptions, animationOptions, id ) {
-
-		// 'from' elements are given a data-auto-animate-target with no value,
-		// 'to' elements are are given a data-auto-animate-target with an ID
-		from.dataset.autoAnimateTarget = '';
-		to.dataset.autoAnimateTarget = id;
-
-		// Each element may override any of the auto-animate options
-		// like transition easing, duration and delay via data-attributes
-		let options = this.getAutoAnimateOptions( to, animationOptions );
-
-		// If we're using a custom element matcher the element options
-		// may contain additional transition overrides
-		if( typeof elementOptions.delay !== 'undefined' ) options.delay = elementOptions.delay;
-		if( typeof elementOptions.duration !== 'undefined' ) options.duration = elementOptions.duration;
-		if( typeof elementOptions.easing !== 'undefined' ) options.easing = elementOptions.easing;
-
-		let fromProps = this.getAutoAnimatableProperties( 'from', from, elementOptions ),
-			toProps = this.getAutoAnimatableProperties( 'to', to, elementOptions );
-
-		// Maintain fragment visibility for matching elements when
-		// we're navigating forwards, this way the viewer won't need
-		// to step through the same fragments twice
-		if( to.classList.contains( 'fragment' ) ) {
-
-			// Don't auto-animate the opacity of fragments to avoid
-			// conflicts with fragment animations
-			delete toProps.styles['opacity'];
-
-			if( from.classList.contains( 'fragment' ) ) {
-
-				let fromFragmentStyle = ( from.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0];
-				let toFragmentStyle = ( to.className.match( FRAGMENT_STYLE_REGEX ) || [''] )[0];
-
-				// Only skip the fragment if the fragment animation style
-				// remains unchanged
-				if( fromFragmentStyle === toFragmentStyle && animationOptions.slideDirection === 'forward' ) {
-					to.classList.add( 'visible', 'disabled' );
-				}
-
-			}
-
-		}
-
-		// If translation and/or scaling are enabled, css transform
-		// the 'to' element so that it matches the position and size
-		// of the 'from' element
-		if( elementOptions.translate !== false || elementOptions.scale !== false ) {
-
-			let presentationScale = this.Reveal.getScale();
-
-			let delta = {
-				x: ( fromProps.x - toProps.x ) / presentationScale,
-				y: ( fromProps.y - toProps.y ) / presentationScale,
-				scaleX: fromProps.width / toProps.width,
-				scaleY: fromProps.height / toProps.height
-			};
-
-			// Limit decimal points to avoid 0.0001px blur and stutter
-			delta.x = Math.round( delta.x * 1000 ) / 1000;
-			delta.y = Math.round( delta.y * 1000 ) / 1000;
-			delta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000;
-			delta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000;
-
-			let translate = elementOptions.translate !== false && ( delta.x !== 0 || delta.y !== 0 ),
-				scale = elementOptions.scale !== false && ( delta.scaleX !== 0 || delta.scaleY !== 0 );
-
-			// No need to transform if nothing's changed
-			if( translate || scale ) {
-
-				let transform = [];
-
-				if( translate ) transform.push( `translate(${delta.x}px, ${delta.y}px)` );
-				if( scale ) transform.push( `scale(${delta.scaleX}, ${delta.scaleY})` );
-
-				fromProps.styles['transform'] = transform.join( ' ' );
-				fromProps.styles['transform-origin'] = 'top left';
-
-				toProps.styles['transform'] = 'none';
-
-			}
-
-		}
-
-		// Delete all unchanged 'to' styles
-		for( let propertyName in toProps.styles ) {
-			const toValue = toProps.styles[propertyName];
-			const fromValue = fromProps.styles[propertyName];
-
-			if( toValue === fromValue ) {
-				delete toProps.styles[propertyName];
-			}
-			else {
-				// If these property values were set via a custom matcher providing
-				// an explicit 'from' and/or 'to' value, we always inject those values.
-				if( toValue.explicitValue === true ) {
-					toProps.styles[propertyName] = toValue.value;
-				}
-
-				if( fromValue.explicitValue === true ) {
-					fromProps.styles[propertyName] = fromValue.value;
-				}
-			}
-		}
-
-		let css = '';
-
-		let toStyleProperties = Object.keys( toProps.styles );
-
-		// Only create animate this element IF at least one style
-		// property has changed
-		if( toStyleProperties.length > 0 ) {
-
-			// Instantly move to the 'from' state
-			fromProps.styles['transition'] = 'none';
-
-			// Animate towards the 'to' state
-			toProps.styles['transition'] = `all ${options.duration}s ${options.easing} ${options.delay}s`;
-			toProps.styles['transition-property'] = toStyleProperties.join( ', ' );
-			toProps.styles['will-change'] = toStyleProperties.join( ', ' );
-
-			// Build up our custom CSS. We need to override inline styles
-			// so we need to make our styles vErY IMPORTANT!1!!
-			let fromCSS = Object.keys( fromProps.styles ).map( propertyName => {
-				return propertyName + ': ' + fromProps.styles[propertyName] + ' !important;';
-			} ).join( '' );
-
-			let toCSS = Object.keys( toProps.styles ).map( propertyName => {
-				return propertyName + ': ' + toProps.styles[propertyName] + ' !important;';
-			} ).join( '' );
-
-			css = 	'[data-auto-animate-target="'+ id +'"] {'+ fromCSS +'}' +
-					'[data-auto-animate="running"] [data-auto-animate-target="'+ id +'"] {'+ toCSS +'}';
-
-		}
-
-		return css;
-
-	}
-
-	/**
-	 * Returns the auto-animate options for the given element.
-	 *
-	 * @param {HTMLElement} element Element to pick up options
-	 * from, either a slide or an animation target
-	 * @param {Object} [inheritedOptions] Optional set of existing
-	 * options
-	 */
-	getAutoAnimateOptions( element, inheritedOptions ) {
-
-		let options = {
-			easing: this.Reveal.getConfig().autoAnimateEasing,
-			duration: this.Reveal.getConfig().autoAnimateDuration,
-			delay: 0
-		};
-
-		options = extend( options, inheritedOptions );
-
-		// Inherit options from parent elements
-		if( element.parentNode ) {
-			let autoAnimatedParent = closest( element.parentNode, '[data-auto-animate-target]' );
-			if( autoAnimatedParent ) {
-				options = this.getAutoAnimateOptions( autoAnimatedParent, options );
-			}
-		}
-
-		if( element.dataset.autoAnimateEasing ) {
-			options.easing = element.dataset.autoAnimateEasing;
-		}
-
-		if( element.dataset.autoAnimateDuration ) {
-			options.duration = parseFloat( element.dataset.autoAnimateDuration );
-		}
-
-		if( element.dataset.autoAnimateDelay ) {
-			options.delay = parseFloat( element.dataset.autoAnimateDelay );
-		}
-
-		return options;
-
-	}
-
-	/**
-	 * Returns an object containing all of the properties
-	 * that can be auto-animated for the given element and
-	 * their current computed values.
-	 *
-	 * @param {String} direction 'from' or 'to'
-	 */
-	getAutoAnimatableProperties( direction, element, elementOptions ) {
-
-		let config = this.Reveal.getConfig();
-
-		let properties = { styles: [] };
-
-		// Position and size
-		if( elementOptions.translate !== false || elementOptions.scale !== false ) {
-			let bounds;
-
-			// Custom auto-animate may optionally return a custom tailored
-			// measurement function
-			if( typeof elementOptions.measure === 'function' ) {
-				bounds = elementOptions.measure( element );
-			}
-			else {
-				if( config.center ) {
-					// More precise, but breaks when used in combination
-					// with zoom for scaling the deck ¯\_(ツ)_/¯
-					bounds = element.getBoundingClientRect();
-				}
-				else {
-					let scale = this.Reveal.getScale();
-					bounds = {
-						x: element.offsetLeft * scale,
-						y: element.offsetTop * scale,
-						width: element.offsetWidth * scale,
-						height: element.offsetHeight * scale
-					};
-				}
-			}
-
-			properties.x = bounds.x;
-			properties.y = bounds.y;
-			properties.width = bounds.width;
-			properties.height = bounds.height;
-		}
-
-		const computedStyles = getComputedStyle( element );
-
-		// CSS styles
-		( elementOptions.styles || config.autoAnimateStyles ).forEach( style => {
-			let value;
-
-			// `style` is either the property name directly, or an object
-			// definition of a style property
-			if( typeof style === 'string' ) style = { property: style };
-
-			if( typeof style.from !== 'undefined' && direction === 'from' ) {
-				value = { value: style.from, explicitValue: true };
-			}
-			else if( typeof style.to !== 'undefined' && direction === 'to' ) {
-				value = { value: style.to, explicitValue: true };
-			}
-			else {
-				// Use a unitless value for line-height so that it inherits properly
-				if( style.property === 'line-height' ) {
-					value = parseFloat( computedStyles['line-height'] ) / parseFloat( computedStyles['font-size'] );
-				}
-
-				if( isNaN(value) ) {
-					value = computedStyles[style.property];
-				}
-			}
-
-			if( value !== '' ) {
-				properties.styles[style.property] = value;
-			}
-		} );
-
-		return properties;
-
-	}
-
-	/**
-	 * Get a list of all element pairs that we can animate
-	 * between the given slides.
-	 *
-	 * @param {HTMLElement} fromSlide
-	 * @param {HTMLElement} toSlide
-	 *
-	 * @return {Array} Each value is an array where [0] is
-	 * the element we're animating from and [1] is the
-	 * element we're animating to
-	 */
-	getAutoAnimatableElements( fromSlide, toSlide ) {
-
-		let matcher = typeof this.Reveal.getConfig().autoAnimateMatcher === 'function' ? this.Reveal.getConfig().autoAnimateMatcher : this.getAutoAnimatePairs;
-
-		let pairs = matcher.call( this, fromSlide, toSlide );
-
-		let reserved = [];
-
-		// Remove duplicate pairs
-		return pairs.filter( ( pair, index ) => {
-			if( reserved.indexOf( pair.to ) === -1 ) {
-				reserved.push( pair.to );
-				return true;
-			}
-		} );
-
-	}
-
-	/**
-	 * Identifies matching elements between slides.
-	 *
-	 * You can specify a custom matcher function by using
-	 * the `autoAnimateMatcher` config option.
-	 */
-	getAutoAnimatePairs( fromSlide, toSlide ) {
-
-		let pairs = [];
-
-		const codeNodes = 'pre';
-		const textNodes = 'h1, h2, h3, h4, h5, h6, p, li';
-		const mediaNodes = 'img, video, iframe';
-
-		// Explicit matches via data-id
-		this.findAutoAnimateMatches( pairs, fromSlide, toSlide, '[data-id]', node => {
-			return node.nodeName + ':::' + node.getAttribute( 'data-id' );
-		} );
-
-		// Text
-		this.findAutoAnimateMatches( pairs, fromSlide, toSlide, textNodes, node => {
-			return node.nodeName + ':::' + node.innerText;
-		} );
-
-		// Media
-		this.findAutoAnimateMatches( pairs, fromSlide, toSlide, mediaNodes, node => {
-			return node.nodeName + ':::' + ( node.getAttribute( 'src' ) || node.getAttribute( 'data-src' ) );
-		} );
-
-		// Code
-		this.findAutoAnimateMatches( pairs, fromSlide, toSlide, codeNodes, node => {
-			return node.nodeName + ':::' + node.innerText;
-		} );
-
-		pairs.forEach( pair => {
-			// Disable scale transformations on text nodes, we transition
-			// each individual text property instead
-			if( matches( pair.from, textNodes ) ) {
-				pair.options = { scale: false };
-			}
-			// Animate individual lines of code
-			else if( matches( pair.from, codeNodes ) ) {
-
-				// Transition the code block's width and height instead of scaling
-				// to prevent its content from being squished
-				pair.options = { scale: false, styles: [ 'width', 'height' ] };
-
-				// Lines of code
-				this.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-code', node => {
-					return node.textContent;
-				}, {
-					scale: false,
-					styles: [],
-					measure: this.getLocalBoundingBox.bind( this )
-				} );
-
-				// Line numbers
-				this.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-numbers[data-line-number]', node => {
-					return node.getAttribute( 'data-line-number' );
-				}, {
-					scale: false,
-					styles: [ 'width' ],
-					measure: this.getLocalBoundingBox.bind( this )
-				} );
-
-			}
-
-		}, this );
-
-		return pairs;
-
-	}
-
-	/**
-	 * Helper method which returns a bounding box based on
-	 * the given elements offset coordinates.
-	 *
-	 * @param {HTMLElement} element
-	 * @return {Object} x, y, width, height
-	 */
-	getLocalBoundingBox( element ) {
-
-		const presentationScale = this.Reveal.getScale();
-
-		return {
-			x: Math.round( ( element.offsetLeft * presentationScale ) * 100 ) / 100,
-			y: Math.round( ( element.offsetTop * presentationScale ) * 100 ) / 100,
-			width: Math.round( ( element.offsetWidth * presentationScale ) * 100 ) / 100,
-			height: Math.round( ( element.offsetHeight * presentationScale ) * 100 ) / 100
-		};
-
-	}
-
-	/**
-	 * Finds matching elements between two slides.
-	 *
-	 * @param {Array} pairs            	List of pairs to push matches to
-	 * @param {HTMLElement} fromScope   Scope within the from element exists
-	 * @param {HTMLElement} toScope     Scope within the to element exists
-	 * @param {String} selector         CSS selector of the element to match
-	 * @param {Function} serializer     A function that accepts an element and returns
-	 *                                  a stringified ID based on its contents
-	 * @param {Object} animationOptions Optional config options for this pair
-	 */
-	findAutoAnimateMatches( pairs, fromScope, toScope, selector, serializer, animationOptions ) {
-
-		let fromMatches = {};
-		let toMatches = {};
-
-		[].slice.call( fromScope.querySelectorAll( selector ) ).forEach( ( element, i ) => {
-			const key = serializer( element );
-			if( typeof key === 'string' && key.length ) {
-				fromMatches[key] = fromMatches[key] || [];
-				fromMatches[key].push( element );
-			}
-		} );
-
-		[].slice.call( toScope.querySelectorAll( selector ) ).forEach( ( element, i ) => {
-			const key = serializer( element );
-			toMatches[key] = toMatches[key] || [];
-			toMatches[key].push( element );
-
-			let fromElement;
-
-			// Retrieve the 'from' element
-			if( fromMatches[key] ) {
-				const primaryIndex = toMatches[key].length - 1;
-				const secondaryIndex = fromMatches[key].length - 1;
-
-				// If there are multiple identical from elements, retrieve
-				// the one at the same index as our to-element.
-				if( fromMatches[key][ primaryIndex ] ) {
-					fromElement = fromMatches[key][ primaryIndex ];
-					fromMatches[key][ primaryIndex ] = null;
-				}
-				// If there are no matching from-elements at the same index,
-				// use the last one.
-				else if( fromMatches[key][ secondaryIndex ] ) {
-					fromElement = fromMatches[key][ secondaryIndex ];
-					fromMatches[key][ secondaryIndex ] = null;
-				}
-			}
-
-			// If we've got a matching pair, push it to the list of pairs
-			if( fromElement ) {
-				pairs.push({
-					from: fromElement,
-					to: element,
-					options: animationOptions
-				});
-			}
-		} );
-
-	}
-
-	/**
-	 * Returns a all elements within the given scope that should
-	 * be considered unmatched in an auto-animate transition. If
-	 * fading of unmatched elements is turned on, these elements
-	 * will fade when going between auto-animate slides.
-	 *
-	 * Note that parents of auto-animate targets are NOT considered
-	 * unmatched since fading them would break the auto-animation.
-	 *
-	 * @param {HTMLElement} rootElement
-	 * @return {Array}
-	 */
-	getUnmatchedAutoAnimateElements( rootElement ) {
-
-		return [].slice.call( rootElement.children ).reduce( ( result, element ) => {
-
-			const containsAnimatedElements = element.querySelector( '[data-auto-animate-target]' );
-
-			// The element is unmatched if
-			// - It is not an auto-animate target
-			// - It does not contain any auto-animate targets
-			if( !element.hasAttribute( 'data-auto-animate-target' ) && !containsAnimatedElements ) {
-				result.push( element );
-			}
-
-			if( element.querySelector( '[data-auto-animate-target]' ) ) {
-				result = result.concat( this.getUnmatchedAutoAnimateElements( element ) );
-			}
-
-			return result;
-
-		}, [] );
-
-	}
-
-}
diff --git a/geschichte/js/controllers/backgrounds.js b/geschichte/js/controllers/backgrounds.js
deleted file mode 100644
index f906269..0000000
--- a/geschichte/js/controllers/backgrounds.js
+++ /dev/null
@@ -1,406 +0,0 @@
-import { queryAll } from '../utils/util.js'
-import { colorToRgb, colorBrightness } from '../utils/color.js'
-
-/**
- * Creates and updates slide backgrounds.
- */
-export default class Backgrounds {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-	}
-
-	render() {
-
-		this.element = document.createElement( 'div' );
-		this.element.className = 'backgrounds';
-		this.Reveal.getRevealElement().appendChild( this.element );
-
-	}
-
-	/**
-	 * Creates the slide background elements and appends them
-	 * to the background container. One element is created per
-	 * slide no matter if the given slide has visible background.
-	 */
-	create() {
-
-		// Clear prior backgrounds
-		this.element.innerHTML = '';
-		this.element.classList.add( 'no-transition' );
-
-		// Iterate over all horizontal slides
-		this.Reveal.getHorizontalSlides().forEach( slideh => {
-
-			let backgroundStack = this.createBackground( slideh, this.element );
-
-			// Iterate over all vertical slides
-			queryAll( slideh, 'section' ).forEach( slidev => {
-
-				this.createBackground( slidev, backgroundStack );
-
-				backgroundStack.classList.add( 'stack' );
-
-			} );
-
-		} );
-
-		// Add parallax background if specified
-		if( this.Reveal.getConfig().parallaxBackgroundImage ) {
-
-			this.element.style.backgroundImage = 'url("' + this.Reveal.getConfig().parallaxBackgroundImage + '")';
-			this.element.style.backgroundSize = this.Reveal.getConfig().parallaxBackgroundSize;
-			this.element.style.backgroundRepeat = this.Reveal.getConfig().parallaxBackgroundRepeat;
-			this.element.style.backgroundPosition = this.Reveal.getConfig().parallaxBackgroundPosition;
-
-			// Make sure the below properties are set on the element - these properties are
-			// needed for proper transitions to be set on the element via CSS. To remove
-			// annoying background slide-in effect when the presentation starts, apply
-			// these properties after short time delay
-			setTimeout( () => {
-				this.Reveal.getRevealElement().classList.add( 'has-parallax-background' );
-			}, 1 );
-
-		}
-		else {
-
-			this.element.style.backgroundImage = '';
-			this.Reveal.getRevealElement().classList.remove( 'has-parallax-background' );
-
-		}
-
-	}
-
-	/**
-	 * Creates a background for the given slide.
-	 *
-	 * @param {HTMLElement} slide
-	 * @param {HTMLElement} container The element that the background
-	 * should be appended to
-	 * @return {HTMLElement} New background div
-	 */
-	createBackground( slide, container ) {
-
-		// Main slide background element
-		let element = document.createElement( 'div' );
-		element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
-
-		// Inner background element that wraps images/videos/iframes
-		let contentElement = document.createElement( 'div' );
-		contentElement.className = 'slide-background-content';
-
-		element.appendChild( contentElement );
-		container.appendChild( element );
-
-		slide.slideBackgroundElement = element;
-		slide.slideBackgroundContentElement = contentElement;
-
-		// Syncs the background to reflect all current background settings
-		this.sync( slide );
-
-		return element;
-
-	}
-
-	/**
-	 * Renders all of the visual properties of a slide background
-	 * based on the various background attributes.
-	 *
-	 * @param {HTMLElement} slide
-	 */
-	sync( slide ) {
-
-		const element = slide.slideBackgroundElement,
-			contentElement = slide.slideBackgroundContentElement;
-
-		const data = {
-			background: slide.getAttribute( 'data-background' ),
-			backgroundSize: slide.getAttribute( 'data-background-size' ),
-			backgroundImage: slide.getAttribute( 'data-background-image' ),
-			backgroundVideo: slide.getAttribute( 'data-background-video' ),
-			backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
-			backgroundColor: slide.getAttribute( 'data-background-color' ),
-			backgroundGradient: slide.getAttribute( 'data-background-gradient' ),
-			backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
-			backgroundPosition: slide.getAttribute( 'data-background-position' ),
-			backgroundTransition: slide.getAttribute( 'data-background-transition' ),
-			backgroundOpacity: slide.getAttribute( 'data-background-opacity' ),
-		};
-
-		const dataPreload = slide.hasAttribute( 'data-preload' );
-
-		// Reset the prior background state in case this is not the
-		// initial sync
-		slide.classList.remove( 'has-dark-background' );
-		slide.classList.remove( 'has-light-background' );
-
-		element.removeAttribute( 'data-loaded' );
-		element.removeAttribute( 'data-background-hash' );
-		element.removeAttribute( 'data-background-size' );
-		element.removeAttribute( 'data-background-transition' );
-		element.style.backgroundColor = '';
-
-		contentElement.style.backgroundSize = '';
-		contentElement.style.backgroundRepeat = '';
-		contentElement.style.backgroundPosition = '';
-		contentElement.style.backgroundImage = '';
-		contentElement.style.opacity = '';
-		contentElement.innerHTML = '';
-
-		if( data.background ) {
-			// Auto-wrap image urls in url(...)
-			if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp|webp)([?#\s]|$)/gi.test( data.background ) ) {
-				slide.setAttribute( 'data-background-image', data.background );
-			}
-			else {
-				element.style.background = data.background;
-			}
-		}
-
-		// Create a hash for this combination of background settings.
-		// This is used to determine when two slide backgrounds are
-		// the same.
-		if( data.background || data.backgroundColor || data.backgroundGradient || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
-			element.setAttribute( 'data-background-hash', data.background +
-															data.backgroundSize +
-															data.backgroundImage +
-															data.backgroundVideo +
-															data.backgroundIframe +
-															data.backgroundColor +
-															data.backgroundGradient +
-															data.backgroundRepeat +
-															data.backgroundPosition +
-															data.backgroundTransition +
-															data.backgroundOpacity );
-		}
-
-		// Additional and optional background properties
-		if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );
-		if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
-		if( data.backgroundGradient ) element.style.backgroundImage = data.backgroundGradient;
-		if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
-
-		if( dataPreload ) element.setAttribute( 'data-preload', '' );
-
-		// Background image options are set on the content wrapper
-		if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize;
-		if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat;
-		if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
-		if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
-
-		// If this slide has a background color, we add a class that
-		// signals if it is light or dark. If the slide has no background
-		// color, no class will be added
-		let contrastColor = data.backgroundColor;
-
-		// If no bg color was found, or it cannot be converted by colorToRgb, check the computed background
-		if( !contrastColor || !colorToRgb( contrastColor ) ) {
-			let computedBackgroundStyle = window.getComputedStyle( element );
-			if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {
-				contrastColor = computedBackgroundStyle.backgroundColor;
-			}
-		}
-
-		if( contrastColor ) {
-			const rgb = colorToRgb( contrastColor );
-
-			// Ignore fully transparent backgrounds. Some browsers return
-			// rgba(0,0,0,0) when reading the computed background color of
-			// an element with no background
-			if( rgb && rgb.a !== 0 ) {
-				if( colorBrightness( contrastColor ) < 128 ) {
-					slide.classList.add( 'has-dark-background' );
-				}
-				else {
-					slide.classList.add( 'has-light-background' );
-				}
-			}
-		}
-
-	}
-
-	/**
-	 * Updates the background elements to reflect the current
-	 * slide.
-	 *
-	 * @param {boolean} includeAll If true, the backgrounds of
-	 * all vertical slides (not just the present) will be updated.
-	 */
-	update( includeAll = false ) {
-
-		let currentSlide = this.Reveal.getCurrentSlide();
-		let indices = this.Reveal.getIndices();
-
-		let currentBackground = null;
-
-		// Reverse past/future classes when in RTL mode
-		let horizontalPast = this.Reveal.getConfig().rtl ? 'future' : 'past',
-			horizontalFuture = this.Reveal.getConfig().rtl ? 'past' : 'future';
-
-		// Update the classes of all backgrounds to match the
-		// states of their slides (past/present/future)
-		Array.from( this.element.childNodes ).forEach( ( backgroundh, h ) => {
-
-			backgroundh.classList.remove( 'past', 'present', 'future' );
-
-			if( h < indices.h ) {
-				backgroundh.classList.add( horizontalPast );
-			}
-			else if ( h > indices.h ) {
-				backgroundh.classList.add( horizontalFuture );
-			}
-			else {
-				backgroundh.classList.add( 'present' );
-
-				// Store a reference to the current background element
-				currentBackground = backgroundh;
-			}
-
-			if( includeAll || h === indices.h ) {
-				queryAll( backgroundh, '.slide-background' ).forEach( ( backgroundv, v ) => {
-
-					backgroundv.classList.remove( 'past', 'present', 'future' );
-
-					if( v < indices.v ) {
-						backgroundv.classList.add( 'past' );
-					}
-					else if ( v > indices.v ) {
-						backgroundv.classList.add( 'future' );
-					}
-					else {
-						backgroundv.classList.add( 'present' );
-
-						// Only if this is the present horizontal and vertical slide
-						if( h === indices.h ) currentBackground = backgroundv;
-					}
-
-				} );
-			}
-
-		} );
-
-		// Stop content inside of previous backgrounds
-		if( this.previousBackground ) {
-
-			this.Reveal.slideContent.stopEmbeddedContent( this.previousBackground, { unloadIframes: !this.Reveal.slideContent.shouldPreload( this.previousBackground ) } );
-
-		}
-
-		// Start content in the current background
-		if( currentBackground ) {
-
-			this.Reveal.slideContent.startEmbeddedContent( currentBackground );
-
-			let currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' );
-			if( currentBackgroundContent ) {
-
-				let backgroundImageURL = currentBackgroundContent.style.backgroundImage || '';
-
-				// Restart GIFs (doesn't work in Firefox)
-				if( /\.gif/i.test( backgroundImageURL ) ) {
-					currentBackgroundContent.style.backgroundImage = '';
-					window.getComputedStyle( currentBackgroundContent ).opacity;
-					currentBackgroundContent.style.backgroundImage = backgroundImageURL;
-				}
-
-			}
-
-			// Don't transition between identical backgrounds. This
-			// prevents unwanted flicker.
-			let previousBackgroundHash = this.previousBackground ? this.previousBackground.getAttribute( 'data-background-hash' ) : null;
-			let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
-			if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) {
-				this.element.classList.add( 'no-transition' );
-			}
-
-			this.previousBackground = currentBackground;
-
-		}
-
-		// If there's a background brightness flag for this slide,
-		// bubble it to the .reveal container
-		if( currentSlide ) {
-			[ 'has-light-background', 'has-dark-background' ].forEach( classToBubble => {
-				if( currentSlide.classList.contains( classToBubble ) ) {
-					this.Reveal.getRevealElement().classList.add( classToBubble );
-				}
-				else {
-					this.Reveal.getRevealElement().classList.remove( classToBubble );
-				}
-			}, this );
-		}
-
-		// Allow the first background to apply without transition
-		setTimeout( () => {
-			this.element.classList.remove( 'no-transition' );
-		}, 1 );
-
-	}
-
-	/**
-	 * Updates the position of the parallax background based
-	 * on the current slide index.
-	 */
-	updateParallax() {
-
-		let indices = this.Reveal.getIndices();
-
-		if( this.Reveal.getConfig().parallaxBackgroundImage ) {
-
-			let horizontalSlides = this.Reveal.getHorizontalSlides(),
-				verticalSlides = this.Reveal.getVerticalSlides();
-
-			let backgroundSize = this.element.style.backgroundSize.split( ' ' ),
-				backgroundWidth, backgroundHeight;
-
-			if( backgroundSize.length === 1 ) {
-				backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
-			}
-			else {
-				backgroundWidth = parseInt( backgroundSize[0], 10 );
-				backgroundHeight = parseInt( backgroundSize[1], 10 );
-			}
-
-			let slideWidth = this.element.offsetWidth,
-				horizontalSlideCount = horizontalSlides.length,
-				horizontalOffsetMultiplier,
-				horizontalOffset;
-
-			if( typeof this.Reveal.getConfig().parallaxBackgroundHorizontal === 'number' ) {
-				horizontalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundHorizontal;
-			}
-			else {
-				horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0;
-			}
-
-			horizontalOffset = horizontalOffsetMultiplier * indices.h * -1;
-
-			let slideHeight = this.element.offsetHeight,
-				verticalSlideCount = verticalSlides.length,
-				verticalOffsetMultiplier,
-				verticalOffset;
-
-			if( typeof this.Reveal.getConfig().parallaxBackgroundVertical === 'number' ) {
-				verticalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundVertical;
-			}
-			else {
-				verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );
-			}
-
-			verticalOffset = verticalSlideCount > 0 ?  verticalOffsetMultiplier * indices.v : 0;
-
-			this.element.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';
-
-		}
-
-	}
-
-	destroy() {
-
-		this.element.remove();
-
-	}
-
-}
diff --git a/geschichte/js/controllers/controls.js b/geschichte/js/controllers/controls.js
deleted file mode 100644
index 734eb17..0000000
--- a/geschichte/js/controllers/controls.js
+++ /dev/null
@@ -1,266 +0,0 @@
-import { queryAll } from '../utils/util.js'
-import { isAndroid } from '../utils/device.js'
-
-/**
- * Manages our presentation controls. This includes both
- * the built-in control arrows as well as event monitoring
- * of any elements within the presentation with either of the
- * following helper classes:
- * - .navigate-up
- * - .navigate-right
- * - .navigate-down
- * - .navigate-left
- * - .navigate-next
- * - .navigate-prev
- */
-export default class Controls {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		this.onNavigateLeftClicked = this.onNavigateLeftClicked.bind( this );
-		this.onNavigateRightClicked = this.onNavigateRightClicked.bind( this );
-		this.onNavigateUpClicked = this.onNavigateUpClicked.bind( this );
-		this.onNavigateDownClicked = this.onNavigateDownClicked.bind( this );
-		this.onNavigatePrevClicked = this.onNavigatePrevClicked.bind( this );
-		this.onNavigateNextClicked = this.onNavigateNextClicked.bind( this );
-
-	}
-
-	render() {
-
-		const rtl = this.Reveal.getConfig().rtl;
-		const revealElement = this.Reveal.getRevealElement();
-
-		this.element = document.createElement( 'aside' );
-		this.element.className = 'controls';
-		this.element.innerHTML =
-			`<button class="navigate-left" aria-label="${ rtl ? 'next slide' : 'previous slide' }"><div class="controls-arrow"></div></button>
-			<button class="navigate-right" aria-label="${ rtl ? 'previous slide' : 'next slide' }"><div class="controls-arrow"></div></button>
-			<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>
-			<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>`;
-
-		this.Reveal.getRevealElement().appendChild( this.element );
-
-		// There can be multiple instances of controls throughout the page
-		this.controlsLeft = queryAll( revealElement, '.navigate-left' );
-		this.controlsRight = queryAll( revealElement, '.navigate-right' );
-		this.controlsUp = queryAll( revealElement, '.navigate-up' );
-		this.controlsDown = queryAll( revealElement, '.navigate-down' );
-		this.controlsPrev = queryAll( revealElement, '.navigate-prev' );
-		this.controlsNext = queryAll( revealElement, '.navigate-next' );
-
-		// The left, right and down arrows in the standard reveal.js controls
-		this.controlsRightArrow = this.element.querySelector( '.navigate-right' );
-		this.controlsLeftArrow = this.element.querySelector( '.navigate-left' );
-		this.controlsDownArrow = this.element.querySelector( '.navigate-down' );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		this.element.style.display = config.controls ? 'block' : 'none';
-
-		this.element.setAttribute( 'data-controls-layout', config.controlsLayout );
-		this.element.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
-
-	}
-
-	bind() {
-
-		// Listen to both touch and click events, in case the device
-		// supports both
-		let pointerEvents = [ 'touchstart', 'click' ];
-
-		// Only support touch for Android, fixes double navigations in
-		// stock browser
-		if( isAndroid ) {
-			pointerEvents = [ 'touchstart' ];
-		}
-
-		pointerEvents.forEach( eventName => {
-			this.controlsLeft.forEach( el => el.addEventListener( eventName, this.onNavigateLeftClicked, false ) );
-			this.controlsRight.forEach( el => el.addEventListener( eventName, this.onNavigateRightClicked, false ) );
-			this.controlsUp.forEach( el => el.addEventListener( eventName, this.onNavigateUpClicked, false ) );
-			this.controlsDown.forEach( el => el.addEventListener( eventName, this.onNavigateDownClicked, false ) );
-			this.controlsPrev.forEach( el => el.addEventListener( eventName, this.onNavigatePrevClicked, false ) );
-			this.controlsNext.forEach( el => el.addEventListener( eventName, this.onNavigateNextClicked, false ) );
-		} );
-
-	}
-
-	unbind() {
-
-		[ 'touchstart', 'click' ].forEach( eventName => {
-			this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );
-			this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );
-			this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );
-			this.controlsDown.forEach( el => el.removeEventListener( eventName, this.onNavigateDownClicked, false ) );
-			this.controlsPrev.forEach( el => el.removeEventListener( eventName, this.onNavigatePrevClicked, false ) );
-			this.controlsNext.forEach( el => el.removeEventListener( eventName, this.onNavigateNextClicked, false ) );
-		} );
-
-	}
-
-	/**
-	 * Updates the state of all control/navigation arrows.
-	 */
-	update() {
-
-		let routes = this.Reveal.availableRoutes();
-
-		// Remove the 'enabled' class from all directions
-		[...this.controlsLeft, ...this.controlsRight, ...this.controlsUp, ...this.controlsDown, ...this.controlsPrev, ...this.controlsNext].forEach( node => {
-			node.classList.remove( 'enabled', 'fragmented' );
-
-			// Set 'disabled' attribute on all directions
-			node.setAttribute( 'disabled', 'disabled' );
-		} );
-
-		// Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons
-		if( routes.left ) this.controlsLeft.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
-		if( routes.right ) this.controlsRight.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
-		if( routes.up ) this.controlsUp.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
-		if( routes.down ) this.controlsDown.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
-
-		// Prev/next buttons
-		if( routes.left || routes.up ) this.controlsPrev.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
-		if( routes.right || routes.down ) this.controlsNext.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
-
-		// Highlight fragment directions
-		let currentSlide = this.Reveal.getCurrentSlide();
-		if( currentSlide ) {
-
-			let fragmentsRoutes = this.Reveal.fragments.availableRoutes();
-
-			// Always apply fragment decorator to prev/next buttons
-			if( fragmentsRoutes.prev ) this.controlsPrev.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
-			if( fragmentsRoutes.next ) this.controlsNext.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
-
-			// Apply fragment decorators to directional buttons based on
-			// what slide axis they are in
-			if( this.Reveal.isVerticalSlide( currentSlide ) ) {
-				if( fragmentsRoutes.prev ) this.controlsUp.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
-				if( fragmentsRoutes.next ) this.controlsDown.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
-			}
-			else {
-				if( fragmentsRoutes.prev ) this.controlsLeft.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
-				if( fragmentsRoutes.next ) this.controlsRight.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
-			}
-
-		}
-
-		if( this.Reveal.getConfig().controlsTutorial ) {
-
-			let indices = this.Reveal.getIndices();
-
-			// Highlight control arrows with an animation to ensure
-			// that the viewer knows how to navigate
-			if( !this.Reveal.hasNavigatedVertically() && routes.down ) {
-				this.controlsDownArrow.classList.add( 'highlight' );
-			}
-			else {
-				this.controlsDownArrow.classList.remove( 'highlight' );
-
-				if( this.Reveal.getConfig().rtl ) {
-
-					if( !this.Reveal.hasNavigatedHorizontally() && routes.left && indices.v === 0 ) {
-						this.controlsLeftArrow.classList.add( 'highlight' );
-					}
-					else {
-						this.controlsLeftArrow.classList.remove( 'highlight' );
-					}
-
-				} else {
-
-					if( !this.Reveal.hasNavigatedHorizontally() && routes.right && indices.v === 0 ) {
-						this.controlsRightArrow.classList.add( 'highlight' );
-					}
-					else {
-						this.controlsRightArrow.classList.remove( 'highlight' );
-					}
-				}
-			}
-		}
-	}
-
-	destroy() {
-
-		this.unbind();
-		this.element.remove();
-
-	}
-
-	/**
-	 * Event handlers for navigation control buttons.
-	 */
-	onNavigateLeftClicked( event ) {
-
-		event.preventDefault();
-		this.Reveal.onUserInput();
-
-		if( this.Reveal.getConfig().navigationMode === 'linear' ) {
-			this.Reveal.prev();
-		}
-		else {
-			this.Reveal.left();
-		}
-
-	}
-
-	onNavigateRightClicked( event ) {
-
-		event.preventDefault();
-		this.Reveal.onUserInput();
-
-		if( this.Reveal.getConfig().navigationMode === 'linear' ) {
-			this.Reveal.next();
-		}
-		else {
-			this.Reveal.right();
-		}
-
-	}
-
-	onNavigateUpClicked( event ) {
-
-		event.preventDefault();
-		this.Reveal.onUserInput();
-
-		this.Reveal.up();
-
-	}
-
-	onNavigateDownClicked( event ) {
-
-		event.preventDefault();
-		this.Reveal.onUserInput();
-
-		this.Reveal.down();
-
-	}
-
-	onNavigatePrevClicked( event ) {
-
-		event.preventDefault();
-		this.Reveal.onUserInput();
-
-		this.Reveal.prev();
-
-	}
-
-	onNavigateNextClicked( event ) {
-
-		event.preventDefault();
-		this.Reveal.onUserInput();
-
-		this.Reveal.next();
-
-	}
-
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/focus.js b/geschichte/js/controllers/focus.js
deleted file mode 100644
index 3e68c3f..0000000
--- a/geschichte/js/controllers/focus.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import { closest } from '../utils/util.js'
-
-/**
- * Manages focus when a presentation is embedded. This
- * helps us only capture keyboard from the presentation
- * a user is currently interacting with in a page where
- * multiple presentations are embedded.
- */
-
-const STATE_FOCUS = 'focus';
-const STATE_BLUR = 'blur';
-
-export default class Focus {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		this.onRevealPointerDown = this.onRevealPointerDown.bind( this );
-		this.onDocumentPointerDown = this.onDocumentPointerDown.bind( this );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		if( config.embedded ) {
-			this.blur();
-		}
-		else {
-			this.focus();
-			this.unbind();
-		}
-
-	}
-
-	bind() {
-
-		if( this.Reveal.getConfig().embedded ) {
-			this.Reveal.getRevealElement().addEventListener( 'pointerdown', this.onRevealPointerDown, false );
-		}
-
-	}
-
-	unbind() {
-
-		this.Reveal.getRevealElement().removeEventListener( 'pointerdown', this.onRevealPointerDown, false );
-		document.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false );
-
-	}
-
-	focus() {
-
-		if( this.state !== STATE_FOCUS ) {
-			this.Reveal.getRevealElement().classList.add( 'focused' );
-			document.addEventListener( 'pointerdown', this.onDocumentPointerDown, false );
-		}
-
-		this.state = STATE_FOCUS;
-
-	}
-
-	blur() {
-
-		if( this.state !== STATE_BLUR ) {
-			this.Reveal.getRevealElement().classList.remove( 'focused' );
-			document.removeEventListener( 'pointerdown', this.onDocumentPointerDown, false );
-		}
-
-		this.state = STATE_BLUR;
-
-	}
-
-	isFocused() {
-
-		return this.state === STATE_FOCUS;
-
-	}
-
-	destroy() {
-
-		this.Reveal.getRevealElement().classList.remove( 'focused' );
-
-	}
-
-	onRevealPointerDown( event ) {
-
-		this.focus();
-
-	}
-
-	onDocumentPointerDown( event ) {
-
-		let revealElement = closest( event.target, '.reveal' );
-		if( !revealElement || revealElement !== this.Reveal.getRevealElement() ) {
-			this.blur();
-		}
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/fragments.js b/geschichte/js/controllers/fragments.js
deleted file mode 100644
index 796c168..0000000
--- a/geschichte/js/controllers/fragments.js
+++ /dev/null
@@ -1,376 +0,0 @@
-import { extend, queryAll } from '../utils/util.js'
-
-/**
- * Handles sorting and navigation of slide fragments.
- * Fragments are elements within a slide that are
- * revealed/animated incrementally.
- */
-export default class Fragments {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		if( config.fragments === false ) {
-			this.disable();
-		}
-		else if( oldConfig.fragments === false ) {
-			this.enable();
-		}
-
-	}
-
-	/**
-	 * If fragments are disabled in the deck, they should all be
-	 * visible rather than stepped through.
-	 */
-	disable() {
-
-		queryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => {
-			element.classList.add( 'visible' );
-			element.classList.remove( 'current-fragment' );
-		} );
-
-	}
-
-	/**
-	 * Reverse of #disable(). Only called if fragments have
-	 * previously been disabled.
-	 */
-	enable() {
-
-		queryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => {
-			element.classList.remove( 'visible' );
-			element.classList.remove( 'current-fragment' );
-		} );
-
-	}
-
-	/**
-	 * Returns an object describing the available fragment
-	 * directions.
-	 *
-	 * @return {{prev: boolean, next: boolean}}
-	 */
-	availableRoutes() {
-
-		let currentSlide = this.Reveal.getCurrentSlide();
-		if( currentSlide && this.Reveal.getConfig().fragments ) {
-			let fragments = currentSlide.querySelectorAll( '.fragment:not(.disabled)' );
-			let hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.disabled):not(.visible)' );
-
-			return {
-				prev: fragments.length - hiddenFragments.length > 0,
-				next: !!hiddenFragments.length
-			};
-		}
-		else {
-			return { prev: false, next: false };
-		}
-
-	}
-
-	/**
-	 * Return a sorted fragments list, ordered by an increasing
-	 * "data-fragment-index" attribute.
-	 *
-	 * Fragments will be revealed in the order that they are returned by
-	 * this function, so you can use the index attributes to control the
-	 * order of fragment appearance.
-	 *
-	 * To maintain a sensible default fragment order, fragments are presumed
-	 * to be passed in document order. This function adds a "fragment-index"
-	 * attribute to each node if such an attribute is not already present,
-	 * and sets that attribute to an integer value which is the position of
-	 * the fragment within the fragments list.
-	 *
-	 * @param {object[]|*} fragments
-	 * @param {boolean} grouped If true the returned array will contain
-	 * nested arrays for all fragments with the same index
-	 * @return {object[]} sorted Sorted array of fragments
-	 */
-	sort( fragments, grouped = false ) {
-
-		fragments = Array.from( fragments );
-
-		let ordered = [],
-			unordered = [],
-			sorted = [];
-
-		// Group ordered and unordered elements
-		fragments.forEach( fragment => {
-			if( fragment.hasAttribute( 'data-fragment-index' ) ) {
-				let index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
-
-				if( !ordered[index] ) {
-					ordered[index] = [];
-				}
-
-				ordered[index].push( fragment );
-			}
-			else {
-				unordered.push( [ fragment ] );
-			}
-		} );
-
-		// Append fragments without explicit indices in their
-		// DOM order
-		ordered = ordered.concat( unordered );
-
-		// Manually count the index up per group to ensure there
-		// are no gaps
-		let index = 0;
-
-		// Push all fragments in their sorted order to an array,
-		// this flattens the groups
-		ordered.forEach( group => {
-			group.forEach( fragment => {
-				sorted.push( fragment );
-				fragment.setAttribute( 'data-fragment-index', index );
-			} );
-
-			index ++;
-		} );
-
-		return grouped === true ? ordered : sorted;
-
-	}
-
-	/**
-	 * Sorts and formats all of fragments in the
-	 * presentation.
-	 */
-	sortAll() {
-
-		this.Reveal.getHorizontalSlides().forEach( horizontalSlide => {
-
-			let verticalSlides = queryAll( horizontalSlide, 'section' );
-			verticalSlides.forEach( ( verticalSlide, y ) => {
-
-				this.sort( verticalSlide.querySelectorAll( '.fragment' ) );
-
-			}, this );
-
-			if( verticalSlides.length === 0 ) this.sort( horizontalSlide.querySelectorAll( '.fragment' ) );
-
-		} );
-
-	}
-
-	/**
-	 * Refreshes the fragments on the current slide so that they
-	 * have the appropriate classes (.visible + .current-fragment).
-	 *
-	 * @param {number} [index] The index of the current fragment
-	 * @param {array} [fragments] Array containing all fragments
-	 * in the current slide
-	 *
-	 * @return {{shown: array, hidden: array}}
-	 */
-	update( index, fragments ) {
-
-		let changedFragments = {
-			shown: [],
-			hidden: []
-		};
-
-		let currentSlide = this.Reveal.getCurrentSlide();
-		if( currentSlide && this.Reveal.getConfig().fragments ) {
-
-			fragments = fragments || this.sort( currentSlide.querySelectorAll( '.fragment' ) );
-
-			if( fragments.length ) {
-
-				let maxIndex = 0;
-
-				if( typeof index !== 'number' ) {
-					let currentFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
-					if( currentFragment ) {
-						index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
-					}
-				}
-
-				Array.from( fragments ).forEach( ( el, i ) => {
-
-					if( el.hasAttribute( 'data-fragment-index' ) ) {
-						i = parseInt( el.getAttribute( 'data-fragment-index' ), 10 );
-					}
-
-					maxIndex = Math.max( maxIndex, i );
-
-					// Visible fragments
-					if( i <= index ) {
-						let wasVisible = el.classList.contains( 'visible' )
-						el.classList.add( 'visible' );
-						el.classList.remove( 'current-fragment' );
-
-						if( i === index ) {
-							// Announce the fragments one by one to the Screen Reader
-							this.Reveal.announceStatus( this.Reveal.getStatusText( el ) );
-
-							el.classList.add( 'current-fragment' );
-							this.Reveal.slideContent.startEmbeddedContent( el );
-						}
-
-						if( !wasVisible ) {
-							changedFragments.shown.push( el )
-							this.Reveal.dispatchEvent({
-								target: el,
-								type: 'visible',
-								bubbles: false
-							});
-						}
-					}
-					// Hidden fragments
-					else {
-						let wasVisible = el.classList.contains( 'visible' )
-						el.classList.remove( 'visible' );
-						el.classList.remove( 'current-fragment' );
-
-						if( wasVisible ) {
-							this.Reveal.slideContent.stopEmbeddedContent( el );
-							changedFragments.hidden.push( el );
-							this.Reveal.dispatchEvent({
-								target: el,
-								type: 'hidden',
-								bubbles: false
-							});
-						}
-					}
-
-				} );
-
-				// Write the current fragment index to the slide <section>.
-				// This can be used by end users to apply styles based on
-				// the current fragment index.
-				index = typeof index === 'number' ? index : -1;
-				index = Math.max( Math.min( index, maxIndex ), -1 );
-				currentSlide.setAttribute( 'data-fragment', index );
-
-			}
-
-		}
-
-		return changedFragments;
-
-	}
-
-	/**
-	 * Formats the fragments on the given slide so that they have
-	 * valid indices. Call this if fragments are changed in the DOM
-	 * after reveal.js has already initialized.
-	 *
-	 * @param {HTMLElement} slide
-	 * @return {Array} a list of the HTML fragments that were synced
-	 */
-	sync( slide = this.Reveal.getCurrentSlide() ) {
-
-		return this.sort( slide.querySelectorAll( '.fragment' ) );
-
-	}
-
-	/**
-	 * Navigate to the specified slide fragment.
-	 *
-	 * @param {?number} index The index of the fragment that
-	 * should be shown, -1 means all are invisible
-	 * @param {number} offset Integer offset to apply to the
-	 * fragment index
-	 *
-	 * @return {boolean} true if a change was made in any
-	 * fragments visibility as part of this call
-	 */
-	goto( index, offset = 0 ) {
-
-		let currentSlide = this.Reveal.getCurrentSlide();
-		if( currentSlide && this.Reveal.getConfig().fragments ) {
-
-			let fragments = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled)' ) );
-			if( fragments.length ) {
-
-				// If no index is specified, find the current
-				if( typeof index !== 'number' ) {
-					let lastVisibleFragment = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled).visible' ) ).pop();
-
-					if( lastVisibleFragment ) {
-						index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
-					}
-					else {
-						index = -1;
-					}
-				}
-
-				// Apply the offset if there is one
-				index += offset;
-
-				let changedFragments = this.update( index, fragments );
-
-				if( changedFragments.hidden.length ) {
-					this.Reveal.dispatchEvent({
-						type: 'fragmenthidden',
-						data: {
-							fragment: changedFragments.hidden[0],
-							fragments: changedFragments.hidden
-						}
-					});
-				}
-
-				if( changedFragments.shown.length ) {
-					this.Reveal.dispatchEvent({
-						type: 'fragmentshown',
-						data: {
-							fragment: changedFragments.shown[0],
-							fragments: changedFragments.shown
-						}
-					});
-				}
-
-				this.Reveal.controls.update();
-				this.Reveal.progress.update();
-
-				if( this.Reveal.getConfig().fragmentInURL ) {
-					this.Reveal.location.writeURL();
-				}
-
-				return !!( changedFragments.shown.length || changedFragments.hidden.length );
-
-			}
-
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Navigate to the next slide fragment.
-	 *
-	 * @return {boolean} true if there was a next fragment,
-	 * false otherwise
-	 */
-	next() {
-
-		return this.goto( null, 1 );
-
-	}
-
-	/**
-	 * Navigate to the previous slide fragment.
-	 *
-	 * @return {boolean} true if there was a previous fragment,
-	 * false otherwise
-	 */
-	prev() {
-
-		return this.goto( null, -1 );
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/jumptoslide.js b/geschichte/js/controllers/jumptoslide.js
deleted file mode 100644
index cf2de99..0000000
--- a/geschichte/js/controllers/jumptoslide.js
+++ /dev/null
@@ -1,170 +0,0 @@
-/**
- * Makes it possible to jump to a slide by entering its
- * slide number or id.
- */
-export default class JumpToSlide {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		this.onInput = this.onInput.bind( this );
-		this.onBlur = this.onBlur.bind( this );
-		this.onKeyDown = this.onKeyDown.bind( this );
-
-	}
-
-	render() {
-
-		this.element = document.createElement( 'div' );
-		this.element.className = 'jump-to-slide';
-
-    this.jumpInput = document.createElement( 'input' );
-    this.jumpInput.type = 'text';
-    this.jumpInput.className = 'jump-to-slide-input';
-    this.jumpInput.placeholder = 'Jump to slide';
-		this.jumpInput.addEventListener( 'input', this.onInput );
-		this.jumpInput.addEventListener( 'keydown', this.onKeyDown );
-		this.jumpInput.addEventListener( 'blur', this.onBlur );
-
-    this.element.appendChild( this.jumpInput );
-
-	}
-
-	show() {
-
-		this.indicesOnShow = this.Reveal.getIndices();
-
-		this.Reveal.getRevealElement().appendChild( this.element );
-		this.jumpInput.focus();
-
-	}
-
-	hide() {
-
-		if( this.isVisible() ) {
-			this.element.remove();
-			this.jumpInput.value = '';
-
-			clearTimeout( this.jumpTimeout );
-			delete this.jumpTimeout;
-		}
-
-	}
-
-	isVisible() {
-
-		return !!this.element.parentNode;
-
-	}
-
-	/**
-	 * Parses the current input and jumps to the given slide.
-	 */
-	jump() {
-
-		clearTimeout( this.jumpTimeout );
-		delete this.jumpTimeout;
-
-		const query = this.jumpInput.value.trim( '' );
-		let indices = this.Reveal.location.getIndicesFromHash( query, { oneBasedIndex: true } );
-
-		// If no valid index was found and the input query is a
-		// string, fall back on a simple search
-		if( !indices && /\S+/i.test( query ) && query.length > 1 ) {
-			indices = this.search( query );
-		}
-
-		if( indices && query !== '' ) {
-			this.Reveal.slide( indices.h, indices.v, indices.f );
-			return true;
-		}
-		else {
-			this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
-			return false;
-		}
-
-	}
-
-	jumpAfter( delay ) {
-
-		clearTimeout( this.jumpTimeout );
-		this.jumpTimeout = setTimeout( () => this.jump(), delay );
-
-	}
-
-	/**
-	 * A lofi search that looks for the given query in all
-	 * of our slides and returns the first match.
-	 */
-	search( query ) {
-
-		const regex = new RegExp( '\\b' + query.trim() + '\\b', 'i' );
-
-		const slide = this.Reveal.getSlides().find( ( slide ) => {
-			return regex.test( slide.innerText );
-		} );
-
-		if( slide ) {
-			return this.Reveal.getIndices( slide );
-		}
-		else {
-			return null;
-		}
-
-	}
-
-	/**
-	 * Reverts back to the slide we were on when jump to slide was
-	 * invoked.
-	 */
-	cancel() {
-
-		this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
-		this.hide();
-
-	}
-
-	confirm() {
-
-		this.jump();
-		this.hide();
-
-	}
-
-	destroy() {
-
-		this.jumpInput.removeEventListener( 'input', this.onInput );
-		this.jumpInput.removeEventListener( 'keydown', this.onKeyDown );
-		this.jumpInput.removeEventListener( 'blur', this.onBlur );
-
-		this.element.remove();
-
-	}
-
-	onKeyDown( event ) {
-
-		if( event.keyCode === 13 ) {
-			this.confirm();
-		}
-		else if( event.keyCode === 27 ) {
-			this.cancel();
-
-			event.stopImmediatePropagation();
-		}
-
-	}
-
-	onInput( event ) {
-
-		this.jumpAfter( 200 );
-
-	}
-
-	onBlur() {
-
-		setTimeout( () => this.hide(), 1 );
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/keyboard.js b/geschichte/js/controllers/keyboard.js
deleted file mode 100644
index e3bff7a..0000000
--- a/geschichte/js/controllers/keyboard.js
+++ /dev/null
@@ -1,399 +0,0 @@
-import { enterFullscreen } from '../utils/util.js'
-
-/**
- * Handles all reveal.js keyboard interactions.
- */
-export default class Keyboard {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		// A key:value map of keyboard keys and descriptions of
-		// the actions they trigger
-		this.shortcuts = {};
-
-		// Holds custom key code mappings
-		this.bindings = {};
-
-		this.onDocumentKeyDown = this.onDocumentKeyDown.bind( this );
-		this.onDocumentKeyPress = this.onDocumentKeyPress.bind( this );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		if( config.navigationMode === 'linear' ) {
-			this.shortcuts['&#8594;  ,  &#8595;  ,  SPACE  ,  N  ,  L  ,  J'] = 'Next slide';
-			this.shortcuts['&#8592;  ,  &#8593;  ,  P  ,  H  ,  K']           = 'Previous slide';
-		}
-		else {
-			this.shortcuts['N  ,  SPACE']   = 'Next slide';
-			this.shortcuts['P  ,  Shift SPACE']             = 'Previous slide';
-			this.shortcuts['&#8592;  ,  H'] = 'Navigate left';
-			this.shortcuts['&#8594;  ,  L'] = 'Navigate right';
-			this.shortcuts['&#8593;  ,  K'] = 'Navigate up';
-			this.shortcuts['&#8595;  ,  J'] = 'Navigate down';
-		}
-
-		this.shortcuts['Alt + &#8592;/&#8593/&#8594;/&#8595;']        = 'Navigate without fragments';
-		this.shortcuts['Shift + &#8592;/&#8593/&#8594;/&#8595;']      = 'Jump to first/last slide';
-		this.shortcuts['B  ,  .']                       = 'Pause';
-		this.shortcuts['F']                             = 'Fullscreen';
-		this.shortcuts['G']                             = 'Jump to slide';
-		this.shortcuts['ESC, O']                        = 'Slide overview';
-
-	}
-
-	/**
-	 * Starts listening for keyboard events.
-	 */
-	bind() {
-
-		document.addEventListener( 'keydown', this.onDocumentKeyDown, false );
-		document.addEventListener( 'keypress', this.onDocumentKeyPress, false );
-
-	}
-
-	/**
-	 * Stops listening for keyboard events.
-	 */
-	unbind() {
-
-		document.removeEventListener( 'keydown', this.onDocumentKeyDown, false );
-		document.removeEventListener( 'keypress', this.onDocumentKeyPress, false );
-
-	}
-
-	/**
-	 * Add a custom key binding with optional description to
-	 * be added to the help screen.
-	 */
-	addKeyBinding( binding, callback ) {
-
-		if( typeof binding === 'object' && binding.keyCode ) {
-			this.bindings[binding.keyCode] = {
-				callback: callback,
-				key: binding.key,
-				description: binding.description
-			};
-		}
-		else {
-			this.bindings[binding] = {
-				callback: callback,
-				key: null,
-				description: null
-			};
-		}
-
-	}
-
-	/**
-	 * Removes the specified custom key binding.
-	 */
-	removeKeyBinding( keyCode ) {
-
-		delete this.bindings[keyCode];
-
-	}
-
-	/**
-	 * Programmatically triggers a keyboard event
-	 *
-	 * @param {int} keyCode
-	 */
-	triggerKey( keyCode ) {
-
-		this.onDocumentKeyDown( { keyCode } );
-
-	}
-
-	/**
-	 * Registers a new shortcut to include in the help overlay
-	 *
-	 * @param {String} key
-	 * @param {String} value
-	 */
-	registerKeyboardShortcut( key, value ) {
-
-		this.shortcuts[key] = value;
-
-	}
-
-	getShortcuts() {
-
-		return this.shortcuts;
-
-	}
-
-	getBindings() {
-
-		return this.bindings;
-
-	}
-
-	/**
-	 * Handler for the document level 'keypress' event.
-	 *
-	 * @param {object} event
-	 */
-	onDocumentKeyPress( event ) {
-
-		// Check if the pressed key is question mark
-		if( event.shiftKey && event.charCode === 63 ) {
-			this.Reveal.toggleHelp();
-		}
-
-	}
-
-	/**
-	 * Handler for the document level 'keydown' event.
-	 *
-	 * @param {object} event
-	 */
-	onDocumentKeyDown( event ) {
-
-		let config = this.Reveal.getConfig();
-
-		// If there's a condition specified and it returns false,
-		// ignore this event
-		if( typeof config.keyboardCondition === 'function' && config.keyboardCondition(event) === false ) {
-			return true;
-		}
-
-		// If keyboardCondition is set, only capture keyboard events
-		// for embedded decks when they are focused
-		if( config.keyboardCondition === 'focused' && !this.Reveal.isFocused() ) {
-			return true;
-		}
-
-		// Shorthand
-		let keyCode = event.keyCode;
-
-		// Remember if auto-sliding was paused so we can toggle it
-		let autoSlideWasPaused = !this.Reveal.isAutoSliding();
-
-		this.Reveal.onUserInput( event );
-
-		// Is there a focused element that could be using the keyboard?
-		let activeElementIsCE = document.activeElement && document.activeElement.isContentEditable === true;
-		let activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
-		let activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
-
-		// Whitelist certain modifiers for slide navigation shortcuts
-		let isNavigationKey = [32, 37, 38, 39, 40, 78, 80].indexOf( event.keyCode ) !== -1;
-
-		// Prevent all other events when a modifier is pressed
-		let unusedModifier = 	!( isNavigationKey && event.shiftKey || event.altKey ) &&
-								( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey );
-
-		// Disregard the event if there's a focused element or a
-		// keyboard modifier key is present
-		if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || unusedModifier ) return;
-
-		// While paused only allow resume keyboard events; 'b', 'v', '.'
-		let resumeKeyCodes = [66,86,190,191];
-		let key;
-
-		// Custom key bindings for togglePause should be able to resume
-		if( typeof config.keyboard === 'object' ) {
-			for( key in config.keyboard ) {
-				if( config.keyboard[key] === 'togglePause' ) {
-					resumeKeyCodes.push( parseInt( key, 10 ) );
-				}
-			}
-		}
-
-		if( this.Reveal.isPaused() && resumeKeyCodes.indexOf( keyCode ) === -1 ) {
-			return false;
-		}
-
-		// Use linear navigation if we're configured to OR if
-		// the presentation is one-dimensional
-		let useLinearMode = config.navigationMode === 'linear' || !this.Reveal.hasHorizontalSlides() || !this.Reveal.hasVerticalSlides();
-
-		let triggered = false;
-
-		// 1. User defined key bindings
-		if( typeof config.keyboard === 'object' ) {
-
-			for( key in config.keyboard ) {
-
-				// Check if this binding matches the pressed key
-				if( parseInt( key, 10 ) === keyCode ) {
-
-					let value = config.keyboard[ key ];
-
-					// Callback function
-					if( typeof value === 'function' ) {
-						value.apply( null, [ event ] );
-					}
-					// String shortcuts to reveal.js API
-					else if( typeof value === 'string' && typeof this.Reveal[ value ] === 'function' ) {
-						this.Reveal[ value ].call();
-					}
-
-					triggered = true;
-
-				}
-
-			}
-
-		}
-
-		// 2. Registered custom key bindings
-		if( triggered === false ) {
-
-			for( key in this.bindings ) {
-
-				// Check if this binding matches the pressed key
-				if( parseInt( key, 10 ) === keyCode ) {
-
-					let action = this.bindings[ key ].callback;
-
-					// Callback function
-					if( typeof action === 'function' ) {
-						action.apply( null, [ event ] );
-					}
-					// String shortcuts to reveal.js API
-					else if( typeof action === 'string' && typeof this.Reveal[ action ] === 'function' ) {
-						this.Reveal[ action ].call();
-					}
-
-					triggered = true;
-				}
-			}
-		}
-
-		// 3. System defined key bindings
-		if( triggered === false ) {
-
-			// Assume true and try to prove false
-			triggered = true;
-
-			// P, PAGE UP
-			if( keyCode === 80 || keyCode === 33 ) {
-				this.Reveal.prev({skipFragments: event.altKey});
-			}
-			// N, PAGE DOWN
-			else if( keyCode === 78 || keyCode === 34 ) {
-				this.Reveal.next({skipFragments: event.altKey});
-			}
-			// H, LEFT
-			else if( keyCode === 72 || keyCode === 37 ) {
-				if( event.shiftKey ) {
-					this.Reveal.slide( 0 );
-				}
-				else if( !this.Reveal.overview.isActive() && useLinearMode ) {
-					this.Reveal.prev({skipFragments: event.altKey});
-				}
-				else {
-					this.Reveal.left({skipFragments: event.altKey});
-				}
-			}
-			// L, RIGHT
-			else if( keyCode === 76 || keyCode === 39 ) {
-				if( event.shiftKey ) {
-					this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );
-				}
-				else if( !this.Reveal.overview.isActive() && useLinearMode ) {
-					this.Reveal.next({skipFragments: event.altKey});
-				}
-				else {
-					this.Reveal.right({skipFragments: event.altKey});
-				}
-			}
-			// K, UP
-			else if( keyCode === 75 || keyCode === 38 ) {
-				if( event.shiftKey ) {
-					this.Reveal.slide( undefined, 0 );
-				}
-				else if( !this.Reveal.overview.isActive() && useLinearMode ) {
-					this.Reveal.prev({skipFragments: event.altKey});
-				}
-				else {
-					this.Reveal.up({skipFragments: event.altKey});
-				}
-			}
-			// J, DOWN
-			else if( keyCode === 74 || keyCode === 40 ) {
-				if( event.shiftKey ) {
-					this.Reveal.slide( undefined, Number.MAX_VALUE );
-				}
-				else if( !this.Reveal.overview.isActive() && useLinearMode ) {
-					this.Reveal.next({skipFragments: event.altKey});
-				}
-				else {
-					this.Reveal.down({skipFragments: event.altKey});
-				}
-			}
-			// HOME
-			else if( keyCode === 36 ) {
-				this.Reveal.slide( 0 );
-			}
-			// END
-			else if( keyCode === 35 ) {
-				this.Reveal.slide( this.Reveal.getHorizontalSlides().length - 1 );
-			}
-			// SPACE
-			else if( keyCode === 32 ) {
-				if( this.Reveal.overview.isActive() ) {
-					this.Reveal.overview.deactivate();
-				}
-				if( event.shiftKey ) {
-					this.Reveal.prev({skipFragments: event.altKey});
-				}
-				else {
-					this.Reveal.next({skipFragments: event.altKey});
-				}
-			}
-			// TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON
-			else if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) {
-				this.Reveal.togglePause();
-			}
-			// F
-			else if( keyCode === 70 ) {
-				enterFullscreen( config.embedded ? this.Reveal.getViewportElement() : document.documentElement );
-			}
-			// A
-			else if( keyCode === 65 ) {
-				if ( config.autoSlideStoppable ) {
-					this.Reveal.toggleAutoSlide( autoSlideWasPaused );
-				}
-			}
-			// G
-			else if( keyCode === 71 ) {
-				if ( config.jumpToSlide ) {
-					this.Reveal.toggleJumpToSlide();
-				}
-			}
-			else {
-				triggered = false;
-			}
-
-		}
-
-		// If the input resulted in a triggered action we should prevent
-		// the browsers default behavior
-		if( triggered ) {
-			event.preventDefault && event.preventDefault();
-		}
-		// ESC or O key
-		else if( keyCode === 27 || keyCode === 79 ) {
-			if( this.Reveal.closeOverlay() === false ) {
-				this.Reveal.overview.toggle();
-			}
-
-			event.preventDefault && event.preventDefault();
-		}
-
-		// If auto-sliding is enabled we need to cue up
-		// another timeout
-		this.Reveal.cueAutoSlide();
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/location.js b/geschichte/js/controllers/location.js
deleted file mode 100644
index 6fa4829..0000000
--- a/geschichte/js/controllers/location.js
+++ /dev/null
@@ -1,245 +0,0 @@
-/**
- * Reads and writes the URL based on reveal.js' current state.
- */
-export default class Location {
-
-	// The minimum number of milliseconds that must pass between
-	// calls to history.replaceState
-	MAX_REPLACE_STATE_FREQUENCY = 1000
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		// Delays updates to the URL due to a Chrome thumbnailer bug
-		this.writeURLTimeout = 0;
-
-		this.replaceStateTimestamp = 0;
-
-		this.onWindowHashChange = this.onWindowHashChange.bind( this );
-
-	}
-
-	bind() {
-
-		window.addEventListener( 'hashchange', this.onWindowHashChange, false );
-
-	}
-
-	unbind() {
-
-		window.removeEventListener( 'hashchange', this.onWindowHashChange, false );
-
-	}
-
-	/**
-	 * Returns the slide indices for the given hash link.
-	 *
-	 * @param {string} [hash] the hash string that we want to
-	 * find the indices for
-	 *
-	 * @returns slide indices or null
-	 */
-	getIndicesFromHash( hash=window.location.hash, options={} ) {
-
-		// Attempt to parse the hash as either an index or name
-		let name = hash.replace( /^#\/?/, '' );
-		let bits = name.split( '/' );
-
-		// If the first bit is not fully numeric and there is a name we
-		// can assume that this is a named link
-		if( !/^[0-9]*$/.test( bits[0] ) && name.length ) {
-			let element;
-
-			let f;
-
-			// Parse named links with fragments (#/named-link/2)
-			if( /\/[-\d]+$/g.test( name ) ) {
-				f = parseInt( name.split( '/' ).pop(), 10 );
-				f = isNaN(f) ? undefined : f;
-				name = name.split( '/' ).shift();
-			}
-
-			// Ensure the named link is a valid HTML ID attribute
-			try {
-				element = document.getElementById( decodeURIComponent( name ) );
-			}
-			catch ( error ) { }
-
-			if( element ) {
-				return { ...this.Reveal.getIndices( element ), f };
-			}
-		}
-		else {
-			const config = this.Reveal.getConfig();
-			let hashIndexBase = config.hashOneBasedIndex || options.oneBasedIndex ? 1 : 0;
-
-			// Read the index components of the hash
-			let h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0,
-				v = ( parseInt( bits[1], 10 ) - hashIndexBase ) || 0,
-				f;
-
-			if( config.fragmentInURL ) {
-				f = parseInt( bits[2], 10 );
-				if( isNaN( f ) ) {
-					f = undefined;
-				}
-			}
-
-			return { h, v, f };
-		}
-
-		// The hash couldn't be parsed or no matching named link was found
-		return null
-
-	}
-
-	/**
-	 * Reads the current URL (hash) and navigates accordingly.
-	 */
-	readURL() {
-
-		const currentIndices = this.Reveal.getIndices();
-		const newIndices = this.getIndicesFromHash();
-
-		if( newIndices ) {
-			if( ( newIndices.h !== currentIndices.h || newIndices.v !== currentIndices.v || newIndices.f !== undefined ) ) {
-					this.Reveal.slide( newIndices.h, newIndices.v, newIndices.f );
-			}
-		}
-		// If no new indices are available, we're trying to navigate to
-		// a slide hash that does not exist
-		else {
-			this.Reveal.slide( currentIndices.h || 0, currentIndices.v || 0 );
-		}
-
-	}
-
-	/**
-	 * Updates the page URL (hash) to reflect the current
-	 * state.
-	 *
-	 * @param {number} delay The time in ms to wait before
-	 * writing the hash
-	 */
-	writeURL( delay ) {
-
-		let config = this.Reveal.getConfig();
-		let currentSlide = this.Reveal.getCurrentSlide();
-
-		// Make sure there's never more than one timeout running
-		clearTimeout( this.writeURLTimeout );
-
-		// If a delay is specified, timeout this call
-		if( typeof delay === 'number' ) {
-			this.writeURLTimeout = setTimeout( this.writeURL, delay );
-		}
-		else if( currentSlide ) {
-
-			let hash = this.getHash();
-
-			// If we're configured to push to history OR the history
-			// API is not available.
-			if( config.history ) {
-				window.location.hash = hash;
-			}
-			// If we're configured to reflect the current slide in the
-			// URL without pushing to history.
-			else if( config.hash ) {
-				// If the hash is empty, don't add it to the URL
-				if( hash === '/' ) {
-					this.debouncedReplaceState( window.location.pathname + window.location.search );
-				}
-				else {
-					this.debouncedReplaceState( '#' + hash );
-				}
-			}
-			// UPDATE: The below nuking of all hash changes breaks
-			// anchors on pages where reveal.js is running. Removed
-			// in 4.0. Why was it here in the first place? ¯\_(ツ)_/¯
-			//
-			// If history and hash are both disabled, a hash may still
-			// be added to the URL by clicking on a href with a hash
-			// target. Counter this by always removing the hash.
-			// else {
-			// 	window.history.replaceState( null, null, window.location.pathname + window.location.search );
-			// }
-
-		}
-
-	}
-
-	replaceState( url ) {
-
-		window.history.replaceState( null, null, url );
-		this.replaceStateTimestamp = Date.now();
-
-	}
-
-	debouncedReplaceState( url ) {
-
-		clearTimeout( this.replaceStateTimeout );
-
-		if( Date.now() - this.replaceStateTimestamp > this.MAX_REPLACE_STATE_FREQUENCY ) {
-			this.replaceState( url );
-		}
-		else {
-			this.replaceStateTimeout = setTimeout( () => this.replaceState( url ), this.MAX_REPLACE_STATE_FREQUENCY );
-		}
-
-	}
-
-	/**
-	 * Return a hash URL that will resolve to the given slide location.
-	 *
-	 * @param {HTMLElement} [slide=currentSlide] The slide to link to
-	 */
-	getHash( slide ) {
-
-		let url = '/';
-
-		// Attempt to create a named link based on the slide's ID
-		let s = slide || this.Reveal.getCurrentSlide();
-		let id = s ? s.getAttribute( 'id' ) : null;
-		if( id ) {
-			id = encodeURIComponent( id );
-		}
-
-		let index = this.Reveal.getIndices( slide );
-		if( !this.Reveal.getConfig().fragmentInURL ) {
-			index.f = undefined;
-		}
-
-		// If the current slide has an ID, use that as a named link,
-		// but we don't support named links with a fragment index
-		if( typeof id === 'string' && id.length ) {
-			url = '/' + id;
-
-			// If there is also a fragment, append that at the end
-			// of the named link, like: #/named-link/2
-			if( index.f >= 0 ) url += '/' + index.f;
-		}
-		// Otherwise use the /h/v index
-		else {
-			let hashIndexBase = this.Reveal.getConfig().hashOneBasedIndex ? 1 : 0;
-			if( index.h > 0 || index.v > 0 || index.f >= 0 ) url += index.h + hashIndexBase;
-			if( index.v > 0 || index.f >= 0 ) url += '/' + (index.v + hashIndexBase );
-			if( index.f >= 0 ) url += '/' + index.f;
-		}
-
-		return url;
-
-	}
-
-	/**
-	 * Handler for the window level 'hashchange' event.
-	 *
-	 * @param {object} [event]
-	 */
-	onWindowHashChange( event ) {
-
-		this.readURL();
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/notes.js b/geschichte/js/controllers/notes.js
deleted file mode 100644
index 7256425..0000000
--- a/geschichte/js/controllers/notes.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * Handles the showing of speaker notes
- */
-export default class Notes {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-	}
-
-	render() {
-
-		this.element = document.createElement( 'div' );
-		this.element.className = 'speaker-notes';
-		this.element.setAttribute( 'data-prevent-swipe', '' );
-		this.element.setAttribute( 'tabindex', '0' );
-		this.Reveal.getRevealElement().appendChild( this.element );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		if( config.showNotes ) {
-			this.element.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );
-		}
-
-	}
-
-	/**
-	 * Pick up notes from the current slide and display them
-	 * to the viewer.
-	 *
-	 * @see {@link config.showNotes}
-	 */
-	update() {
-
-		if( this.Reveal.getConfig().showNotes && this.element && this.Reveal.getCurrentSlide() && !this.Reveal.print.isPrintingPDF() ) {
-
-			this.element.innerHTML = this.getSlideNotes() || '<span class="notes-placeholder">No notes on this slide.</span>';
-
-		}
-
-	}
-
-	/**
-	 * Updates the visibility of the speaker notes sidebar that
-	 * is used to share annotated slides. The notes sidebar is
-	 * only visible if showNotes is true and there are notes on
-	 * one or more slides in the deck.
-	 */
-	updateVisibility() {
-
-		if( this.Reveal.getConfig().showNotes && this.hasNotes() && !this.Reveal.print.isPrintingPDF() ) {
-			this.Reveal.getRevealElement().classList.add( 'show-notes' );
-		}
-		else {
-			this.Reveal.getRevealElement().classList.remove( 'show-notes' );
-		}
-
-	}
-
-	/**
-	 * Checks if there are speaker notes for ANY slide in the
-	 * presentation.
-	 */
-	hasNotes() {
-
-		return this.Reveal.getSlidesElement().querySelectorAll( '[data-notes], aside.notes' ).length > 0;
-
-	}
-
-	/**
-	 * Checks if this presentation is running inside of the
-	 * speaker notes window.
-	 *
-	 * @return {boolean}
-	 */
-	isSpeakerNotesWindow() {
-
-		return !!window.location.search.match( /receiver/gi );
-
-	}
-
-	/**
-	 * Retrieves the speaker notes from a slide. Notes can be
-	 * defined in two ways:
-	 * 1. As a data-notes attribute on the slide <section>
-	 * 2. With <aside class="notes"> elements inside the slide
-	 *
-	 * @param {HTMLElement} [slide=currentSlide]
-	 * @return {(string|null)}
-	 */
-	getSlideNotes( slide = this.Reveal.getCurrentSlide() ) {
-
-		// Notes can be specified via the data-notes attribute...
-		if( slide.hasAttribute( 'data-notes' ) ) {
-			return slide.getAttribute( 'data-notes' );
-		}
-
-		// ... or using <aside class="notes"> elements
-		let notesElements = slide.querySelectorAll( 'aside.notes' );
-		if( notesElements ) {
-			return Array.from(notesElements).map( notesElement => notesElement.innerHTML ).join( '\n' );
-		}
-
-		return null;
-
-	}
-
-	destroy() {
-
-		this.element.remove();
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/overview.js b/geschichte/js/controllers/overview.js
deleted file mode 100644
index 30e4c63..0000000
--- a/geschichte/js/controllers/overview.js
+++ /dev/null
@@ -1,255 +0,0 @@
-import { SLIDES_SELECTOR } from '../utils/constants.js'
-import { extend, queryAll, transformElement } from '../utils/util.js'
-
-/**
- * Handles all logic related to the overview mode
- * (birds-eye view of all slides).
- */
-export default class Overview {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		this.active = false;
-
-		this.onSlideClicked = this.onSlideClicked.bind( this );
-
-	}
-
-	/**
-	 * Displays the overview of slides (quick nav) by scaling
-	 * down and arranging all slide elements.
-	 */
-	activate() {
-
-		// Only proceed if enabled in config
-		if( this.Reveal.getConfig().overview && !this.isActive() ) {
-
-			this.active = true;
-
-			this.Reveal.getRevealElement().classList.add( 'overview' );
-
-			// Don't auto-slide while in overview mode
-			this.Reveal.cancelAutoSlide();
-
-			// Move the backgrounds element into the slide container to
-			// that the same scaling is applied
-			this.Reveal.getSlidesElement().appendChild( this.Reveal.getBackgroundsElement() );
-
-			// Clicking on an overview slide navigates to it
-			queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => {
-				if( !slide.classList.contains( 'stack' ) ) {
-					slide.addEventListener( 'click', this.onSlideClicked, true );
-				}
-			} );
-
-			// Calculate slide sizes
-			const margin = 70;
-			const slideSize = this.Reveal.getComputedSlideSize();
-			this.overviewSlideWidth = slideSize.width + margin;
-			this.overviewSlideHeight = slideSize.height + margin;
-
-			// Reverse in RTL mode
-			if( this.Reveal.getConfig().rtl ) {
-				this.overviewSlideWidth = -this.overviewSlideWidth;
-			}
-
-			this.Reveal.updateSlidesVisibility();
-
-			this.layout();
-			this.update();
-
-			this.Reveal.layout();
-
-			const indices = this.Reveal.getIndices();
-
-			// Notify observers of the overview showing
-			this.Reveal.dispatchEvent({
-				type: 'overviewshown',
-				data: {
-					'indexh': indices.h,
-					'indexv': indices.v,
-					'currentSlide': this.Reveal.getCurrentSlide()
-				}
-			});
-
-		}
-
-	}
-
-	/**
-	 * Uses CSS transforms to position all slides in a grid for
-	 * display inside of the overview mode.
-	 */
-	layout() {
-
-		// Layout slides
-		this.Reveal.getHorizontalSlides().forEach( ( hslide, h ) => {
-			hslide.setAttribute( 'data-index-h', h );
-			transformElement( hslide, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' );
-
-			if( hslide.classList.contains( 'stack' ) ) {
-
-				queryAll( hslide, 'section' ).forEach( ( vslide, v ) => {
-					vslide.setAttribute( 'data-index-h', h );
-					vslide.setAttribute( 'data-index-v', v );
-
-					transformElement( vslide, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' );
-				} );
-
-			}
-		} );
-
-		// Layout slide backgrounds
-		Array.from( this.Reveal.getBackgroundsElement().childNodes ).forEach( ( hbackground, h ) => {
-			transformElement( hbackground, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' );
-
-			queryAll( hbackground, '.slide-background' ).forEach( ( vbackground, v ) => {
-				transformElement( vbackground, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' );
-			} );
-		} );
-
-	}
-
-	/**
-	 * Moves the overview viewport to the current slides.
-	 * Called each time the current slide changes.
-	 */
-	update() {
-
-		const vmin = Math.min( window.innerWidth, window.innerHeight );
-		const scale = Math.max( vmin / 5, 150 ) / vmin;
-		const indices = this.Reveal.getIndices();
-
-		this.Reveal.transformSlides( {
-			overview: [
-				'scale('+ scale +')',
-				'translateX('+ ( -indices.h * this.overviewSlideWidth ) +'px)',
-				'translateY('+ ( -indices.v * this.overviewSlideHeight ) +'px)'
-			].join( ' ' )
-		} );
-
-	}
-
-	/**
-	 * Exits the slide overview and enters the currently
-	 * active slide.
-	 */
-	deactivate() {
-
-		// Only proceed if enabled in config
-		if( this.Reveal.getConfig().overview ) {
-
-			this.active = false;
-
-			this.Reveal.getRevealElement().classList.remove( 'overview' );
-
-			// Temporarily add a class so that transitions can do different things
-			// depending on whether they are exiting/entering overview, or just
-			// moving from slide to slide
-			this.Reveal.getRevealElement().classList.add( 'overview-deactivating' );
-
-			setTimeout( () => {
-				this.Reveal.getRevealElement().classList.remove( 'overview-deactivating' );
-			}, 1 );
-
-			// Move the background element back out
-			this.Reveal.getRevealElement().appendChild( this.Reveal.getBackgroundsElement() );
-
-			// Clean up changes made to slides
-			queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => {
-				transformElement( slide, '' );
-
-				slide.removeEventListener( 'click', this.onSlideClicked, true );
-			} );
-
-			// Clean up changes made to backgrounds
-			queryAll( this.Reveal.getBackgroundsElement(), '.slide-background' ).forEach( background => {
-				transformElement( background, '' );
-			} );
-
-			this.Reveal.transformSlides( { overview: '' } );
-
-			const indices = this.Reveal.getIndices();
-
-			this.Reveal.slide( indices.h, indices.v );
-			this.Reveal.layout();
-			this.Reveal.cueAutoSlide();
-
-			// Notify observers of the overview hiding
-			this.Reveal.dispatchEvent({
-				type: 'overviewhidden',
-				data: {
-					'indexh': indices.h,
-					'indexv': indices.v,
-					'currentSlide': this.Reveal.getCurrentSlide()
-				}
-			});
-
-		}
-	}
-
-	/**
-	 * Toggles the slide overview mode on and off.
-	 *
-	 * @param {Boolean} [override] Flag which overrides the
-	 * toggle logic and forcibly sets the desired state. True means
-	 * overview is open, false means it's closed.
-	 */
-	toggle( override ) {
-
-		if( typeof override === 'boolean' ) {
-			override ? this.activate() : this.deactivate();
-		}
-		else {
-			this.isActive() ? this.deactivate() : this.activate();
-		}
-
-	}
-
-	/**
-	 * Checks if the overview is currently active.
-	 *
-	 * @return {Boolean} true if the overview is active,
-	 * false otherwise
-	 */
-	isActive() {
-
-		return this.active;
-
-	}
-
-	/**
-	 * Invoked when a slide is and we're in the overview.
-	 *
-	 * @param {object} event
-	 */
-	onSlideClicked( event ) {
-
-		if( this.isActive() ) {
-			event.preventDefault();
-
-			let element = event.target;
-
-			while( element && !element.nodeName.match( /section/gi ) ) {
-				element = element.parentNode;
-			}
-
-			if( element && !element.classList.contains( 'disabled' ) ) {
-
-				this.deactivate();
-
-				if( element.nodeName.match( /section/gi ) ) {
-					let h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
-						v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
-
-					this.Reveal.slide( h, v );
-				}
-
-			}
-		}
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/plugins.js b/geschichte/js/controllers/plugins.js
deleted file mode 100644
index 88f57bf..0000000
--- a/geschichte/js/controllers/plugins.js
+++ /dev/null
@@ -1,254 +0,0 @@
-import { loadScript } from '../utils/loader.js'
-
-/**
- * Manages loading and registering of reveal.js plugins.
- */
-export default class Plugins {
-
-	constructor( reveal ) {
-
-		this.Reveal = reveal;
-
-		// Flags our current state (idle -> loading -> loaded)
-		this.state = 'idle';
-
-		// An id:instance map of currently registered plugins
-		this.registeredPlugins = {};
-
-		this.asyncDependencies = [];
-
-	}
-
-	/**
-	 * Loads reveal.js dependencies, registers and
-	 * initializes plugins.
-	 *
-	 * Plugins are direct references to a reveal.js plugin
-	 * object that we register and initialize after any
-	 * synchronous dependencies have loaded.
-	 *
-	 * Dependencies are defined via the 'dependencies' config
-	 * option and will be loaded prior to starting reveal.js.
-	 * Some dependencies may have an 'async' flag, if so they
-	 * will load after reveal.js has been started up.
-	 */
-	load( plugins, dependencies ) {
-
-		this.state = 'loading';
-
-		plugins.forEach( this.registerPlugin.bind( this ) );
-
-		return new Promise( resolve => {
-
-			let scripts = [],
-				scriptsToLoad = 0;
-
-			dependencies.forEach( s => {
-				// Load if there's no condition or the condition is truthy
-				if( !s.condition || s.condition() ) {
-					if( s.async ) {
-						this.asyncDependencies.push( s );
-					}
-					else {
-						scripts.push( s );
-					}
-				}
-			} );
-
-			if( scripts.length ) {
-				scriptsToLoad = scripts.length;
-
-				const scriptLoadedCallback = (s) => {
-					if( s && typeof s.callback === 'function' ) s.callback();
-
-					if( --scriptsToLoad === 0 ) {
-						this.initPlugins().then( resolve );
-					}
-				};
-
-				// Load synchronous scripts
-				scripts.forEach( s => {
-					if( typeof s.id === 'string' ) {
-						this.registerPlugin( s );
-						scriptLoadedCallback( s );
-					}
-					else if( typeof s.src === 'string' ) {
-						loadScript( s.src, () => scriptLoadedCallback(s) );
-					}
-					else {
-						console.warn( 'Unrecognized plugin format', s );
-						scriptLoadedCallback();
-					}
-				} );
-			}
-			else {
-				this.initPlugins().then( resolve );
-			}
-
-		} );
-
-	}
-
-	/**
-	 * Initializes our plugins and waits for them to be ready
-	 * before proceeding.
-	 */
-	initPlugins() {
-
-		return new Promise( resolve => {
-
-			let pluginValues = Object.values( this.registeredPlugins );
-			let pluginsToInitialize = pluginValues.length;
-
-			// If there are no plugins, skip this step
-			if( pluginsToInitialize === 0 ) {
-				this.loadAsync().then( resolve );
-			}
-			// ... otherwise initialize plugins
-			else {
-
-				let initNextPlugin;
-
-				let afterPlugInitialized = () => {
-					if( --pluginsToInitialize === 0 ) {
-						this.loadAsync().then( resolve );
-					}
-					else {
-						initNextPlugin();
-					}
-				};
-
-				let i = 0;
-
-				// Initialize plugins serially
-				initNextPlugin = () => {
-
-					let plugin = pluginValues[i++];
-
-					// If the plugin has an 'init' method, invoke it
-					if( typeof plugin.init === 'function' ) {
-						let promise = plugin.init( this.Reveal );
-
-						// If the plugin returned a Promise, wait for it
-						if( promise && typeof promise.then === 'function' ) {
-							promise.then( afterPlugInitialized );
-						}
-						else {
-							afterPlugInitialized();
-						}
-					}
-					else {
-						afterPlugInitialized();
-					}
-
-				}
-
-				initNextPlugin();
-
-			}
-
-		} )
-
-	}
-
-	/**
-	 * Loads all async reveal.js dependencies.
-	 */
-	loadAsync() {
-
-		this.state = 'loaded';
-
-		if( this.asyncDependencies.length ) {
-			this.asyncDependencies.forEach( s => {
-				loadScript( s.src, s.callback );
-			} );
-		}
-
-		return Promise.resolve();
-
-	}
-
-	/**
-	 * Registers a new plugin with this reveal.js instance.
-	 *
-	 * reveal.js waits for all registered plugins to initialize
-	 * before considering itself ready, as long as the plugin
-	 * is registered before calling `Reveal.initialize()`.
-	 */
-	registerPlugin( plugin ) {
-
-		// Backwards compatibility to make reveal.js ~3.9.0
-		// plugins work with reveal.js 4.0.0
-		if( arguments.length === 2 && typeof arguments[0] === 'string' ) {
-			plugin = arguments[1];
-			plugin.id = arguments[0];
-		}
-		// Plugin can optionally be a function which we call
-		// to create an instance of the plugin
-		else if( typeof plugin === 'function' ) {
-			plugin = plugin();
-		}
-
-		let id = plugin.id;
-
-		if( typeof id !== 'string' ) {
-			console.warn( 'Unrecognized plugin format; can\'t find plugin.id', plugin );
-		}
-		else if( this.registeredPlugins[id] === undefined ) {
-			this.registeredPlugins[id] = plugin;
-
-			// If a plugin is registered after reveal.js is loaded,
-			// initialize it right away
-			if( this.state === 'loaded' && typeof plugin.init === 'function' ) {
-				plugin.init( this.Reveal );
-			}
-		}
-		else {
-			console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
-		}
-
-	}
-
-	/**
-	 * Checks if a specific plugin has been registered.
-	 *
-	 * @param {String} id Unique plugin identifier
-	 */
-	hasPlugin( id ) {
-
-		return !!this.registeredPlugins[id];
-
-	}
-
-	/**
-	 * Returns the specific plugin instance, if a plugin
-	 * with the given ID has been registered.
-	 *
-	 * @param {String} id Unique plugin identifier
-	 */
-	getPlugin( id ) {
-
-		return this.registeredPlugins[id];
-
-	}
-
-	getRegisteredPlugins() {
-
-		return this.registeredPlugins;
-
-	}
-
-	destroy() {
-
-		Object.values( this.registeredPlugins ).forEach( plugin => {
-			if( typeof plugin.destroy === 'function' ) {
-				plugin.destroy();
-			}
-		} );
-
-		this.registeredPlugins = {};
-		this.asyncDependencies = [];
-
-	}
-
-}
diff --git a/geschichte/js/controllers/pointer.js b/geschichte/js/controllers/pointer.js
deleted file mode 100644
index f632fce..0000000
--- a/geschichte/js/controllers/pointer.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * Handles hiding of the pointer/cursor when inactive.
- */
-export default class Pointer {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		// Throttles mouse wheel navigation
-		this.lastMouseWheelStep = 0;
-
-		// Is the mouse pointer currently hidden from view
-		this.cursorHidden = false;
-
-		// Timeout used to determine when the cursor is inactive
-		this.cursorInactiveTimeout = 0;
-
-		this.onDocumentCursorActive = this.onDocumentCursorActive.bind( this );
-		this.onDocumentMouseScroll = this.onDocumentMouseScroll.bind( this );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		if( config.mouseWheel ) {
-			document.addEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false ); // FF
-			document.addEventListener( 'mousewheel', this.onDocumentMouseScroll, false );
-		}
-		else {
-			document.removeEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false ); // FF
-			document.removeEventListener( 'mousewheel', this.onDocumentMouseScroll, false );
-		}
-
-		// Auto-hide the mouse pointer when its inactive
-		if( config.hideInactiveCursor ) {
-			document.addEventListener( 'mousemove', this.onDocumentCursorActive, false );
-			document.addEventListener( 'mousedown', this.onDocumentCursorActive, false );
-		}
-		else {
-			this.showCursor();
-
-			document.removeEventListener( 'mousemove', this.onDocumentCursorActive, false );
-			document.removeEventListener( 'mousedown', this.onDocumentCursorActive, false );
-		}
-
-	}
-
-	/**
-	 * Shows the mouse pointer after it has been hidden with
-	 * #hideCursor.
-	 */
-	showCursor() {
-
-		if( this.cursorHidden ) {
-			this.cursorHidden = false;
-			this.Reveal.getRevealElement().style.cursor = '';
-		}
-
-	}
-
-	/**
-	 * Hides the mouse pointer when it's on top of the .reveal
-	 * container.
-	 */
-	hideCursor() {
-
-		if( this.cursorHidden === false ) {
-			this.cursorHidden = true;
-			this.Reveal.getRevealElement().style.cursor = 'none';
-		}
-
-	}
-
-	destroy() {
-
-		this.showCursor();
-
-		document.removeEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false );
-		document.removeEventListener( 'mousewheel', this.onDocumentMouseScroll, false );
-		document.removeEventListener( 'mousemove', this.onDocumentCursorActive, false );
-		document.removeEventListener( 'mousedown', this.onDocumentCursorActive, false );
-
-	}
-
-	/**
-	 * Called whenever there is mouse input at the document level
-	 * to determine if the cursor is active or not.
-	 *
-	 * @param {object} event
-	 */
-	onDocumentCursorActive( event ) {
-
-		this.showCursor();
-
-		clearTimeout( this.cursorInactiveTimeout );
-
-		this.cursorInactiveTimeout = setTimeout( this.hideCursor.bind( this ), this.Reveal.getConfig().hideCursorTime );
-
-	}
-
-	/**
-	 * Handles mouse wheel scrolling, throttled to avoid skipping
-	 * multiple slides.
-	 *
-	 * @param {object} event
-	 */
-	onDocumentMouseScroll( event ) {
-
-		if( Date.now() - this.lastMouseWheelStep > 1000 ) {
-
-			this.lastMouseWheelStep = Date.now();
-
-			let delta = event.detail || -event.wheelDelta;
-			if( delta > 0 ) {
-				this.Reveal.next();
-			}
-			else if( delta < 0 ) {
-				this.Reveal.prev();
-			}
-
-		}
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/print.js b/geschichte/js/controllers/print.js
deleted file mode 100644
index 4214519..0000000
--- a/geschichte/js/controllers/print.js
+++ /dev/null
@@ -1,237 +0,0 @@
-import { SLIDES_SELECTOR } from '../utils/constants.js'
-import { queryAll, createStyleSheet } from '../utils/util.js'
-
-/**
- * Setups up our presentation for printing/exporting to PDF.
- */
-export default class Print {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-	}
-
-	/**
-	 * Configures the presentation for printing to a static
-	 * PDF.
-	 */
-	async setupPDF() {
-
-		const config = this.Reveal.getConfig();
-		const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR )
-
-		// Compute slide numbers now, before we start duplicating slides
-		const injectPageNumbers = config.slideNumber && /all|print/i.test( config.showSlideNumber );
-
-		const slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight );
-
-		// Dimensions of the PDF pages
-		const pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
-			pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
-
-		// Dimensions of slides within the pages
-		const slideWidth = slideSize.width,
-			slideHeight = slideSize.height;
-
-		await new Promise( requestAnimationFrame );
-
-		// Let the browser know what page size we want to print
-		createStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );
-
-		// Limit the size of certain elements to the dimensions of the slide
-		createStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
-
-		document.documentElement.classList.add( 'print-pdf' );
-		document.body.style.width = pageWidth + 'px';
-		document.body.style.height = pageHeight + 'px';
-
-		const viewportElement = document.querySelector( '.reveal-viewport' );
-		let presentationBackground;
-		if( viewportElement ) {
-			const viewportStyles = window.getComputedStyle( viewportElement );
-			if( viewportStyles && viewportStyles.background ) {
-				presentationBackground = viewportStyles.background;
-			}
-		}
-
-		// Make sure stretch elements fit on slide
-		await new Promise( requestAnimationFrame );
-		this.Reveal.layoutSlideContents( slideWidth, slideHeight );
-
-		// Batch scrollHeight access to prevent layout thrashing
-		await new Promise( requestAnimationFrame );
-
-		const slideScrollHeights = slides.map( slide => slide.scrollHeight );
-
-		const pages = [];
-		const pageContainer = slides[0].parentNode;
-		let slideNumber = 1;
-
-		// Slide and slide background layout
-		slides.forEach( function( slide, index ) {
-
-			// Vertical stacks are not centred since their section
-			// children will be
-			if( slide.classList.contains( 'stack' ) === false ) {
-				// Center the slide inside of the page, giving the slide some margin
-				let left = ( pageWidth - slideWidth ) / 2;
-				let top = ( pageHeight - slideHeight ) / 2;
-
-				const contentHeight = slideScrollHeights[ index ];
-				let numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
-
-				// Adhere to configured pages per slide limit
-				numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );
-
-				// Center slides vertically
-				if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
-					top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
-				}
-
-				// Wrap the slide in a page element and hide its overflow
-				// so that no page ever flows onto another
-				const page = document.createElement( 'div' );
-				pages.push( page );
-
-				page.className = 'pdf-page';
-				page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';
-
-				// Copy the presentation-wide background to each individual
-				// page when printing
-				if( presentationBackground ) {
-					page.style.background = presentationBackground;
-				}
-
-				page.appendChild( slide );
-
-				// Position the slide inside of the page
-				slide.style.left = left + 'px';
-				slide.style.top = top + 'px';
-				slide.style.width = slideWidth + 'px';
-
-				this.Reveal.slideContent.layout( slide );
-
-				if( slide.slideBackgroundElement ) {
-					page.insertBefore( slide.slideBackgroundElement, slide );
-				}
-
-				// Inject notes if `showNotes` is enabled
-				if( config.showNotes ) {
-
-					// Are there notes for this slide?
-					const notes = this.Reveal.getSlideNotes( slide );
-					if( notes ) {
-
-						const notesSpacing = 8;
-						const notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';
-						const notesElement = document.createElement( 'div' );
-						notesElement.classList.add( 'speaker-notes' );
-						notesElement.classList.add( 'speaker-notes-pdf' );
-						notesElement.setAttribute( 'data-layout', notesLayout );
-						notesElement.innerHTML = notes;
-
-						if( notesLayout === 'separate-page' ) {
-							pages.push( notesElement );
-						}
-						else {
-							notesElement.style.left = notesSpacing + 'px';
-							notesElement.style.bottom = notesSpacing + 'px';
-							notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';
-							page.appendChild( notesElement );
-						}
-
-					}
-
-				}
-
-				// Inject page numbers if `slideNumbers` are enabled
-				if( injectPageNumbers ) {
-					const numberElement = document.createElement( 'div' );
-					numberElement.classList.add( 'slide-number' );
-					numberElement.classList.add( 'slide-number-pdf' );
-					numberElement.innerHTML = slideNumber++;
-					page.appendChild( numberElement );
-				}
-
-				// Copy page and show fragments one after another
-				if( config.pdfSeparateFragments ) {
-
-					// Each fragment 'group' is an array containing one or more
-					// fragments. Multiple fragments that appear at the same time
-					// are part of the same group.
-					const fragmentGroups = this.Reveal.fragments.sort( page.querySelectorAll( '.fragment' ), true );
-
-					let previousFragmentStep;
-
-					fragmentGroups.forEach( function( fragments, index ) {
-
-						// Remove 'current-fragment' from the previous group
-						if( previousFragmentStep ) {
-							previousFragmentStep.forEach( function( fragment ) {
-								fragment.classList.remove( 'current-fragment' );
-							} );
-						}
-
-						// Show the fragments for the current index
-						fragments.forEach( function( fragment ) {
-							fragment.classList.add( 'visible', 'current-fragment' );
-						}, this );
-
-						// Create a separate page for the current fragment state
-						const clonedPage = page.cloneNode( true );
-
-						// Inject unique page numbers for fragments
-						if( injectPageNumbers ) {
-							const numberElement = clonedPage.querySelector( '.slide-number-pdf' );
-							const fragmentNumber = index + 1;
-							numberElement.innerHTML += '.' + fragmentNumber;
-						}
-
-						pages.push( clonedPage );
-
-						previousFragmentStep = fragments;
-
-					}, this );
-
-					// Reset the first/original page so that all fragments are hidden
-					fragmentGroups.forEach( function( fragments ) {
-						fragments.forEach( function( fragment ) {
-							fragment.classList.remove( 'visible', 'current-fragment' );
-						} );
-					} );
-
-				}
-				// Show all fragments
-				else {
-					queryAll( page, '.fragment:not(.fade-out)' ).forEach( function( fragment ) {
-						fragment.classList.add( 'visible' );
-					} );
-				}
-
-			}
-
-		}, this );
-
-		await new Promise( requestAnimationFrame );
-
-		pages.forEach( page => pageContainer.appendChild( page ) );
-
-		// Re-run JS-based content layout after the slide is added to page DOM
-		this.Reveal.slideContent.layout( this.Reveal.getSlidesElement() );
-
-		// Notify subscribers that the PDF layout is good to go
-		this.Reveal.dispatchEvent({ type: 'pdf-ready' });
-
-	}
-
-	/**
-	 * Checks if this instance is being used to print a PDF.
-	 */
-	isPrintingPDF() {
-
-		return ( /print-pdf/gi ).test( window.location.search );
-
-	}
-
-}
diff --git a/geschichte/js/controllers/progress.js b/geschichte/js/controllers/progress.js
deleted file mode 100644
index 87e2aaf..0000000
--- a/geschichte/js/controllers/progress.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * Creates a visual progress bar for the presentation.
- */
-export default class Progress {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		this.onProgressClicked = this.onProgressClicked.bind( this );
-
-	}
-
-	render() {
-
-		this.element = document.createElement( 'div' );
-		this.element.className = 'progress';
-		this.Reveal.getRevealElement().appendChild( this.element );
-
-		this.bar = document.createElement( 'span' );
-		this.element.appendChild( this.bar );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		this.element.style.display = config.progress ? 'block' : 'none';
-
-	}
-
-	bind() {
-
-		if( this.Reveal.getConfig().progress && this.element ) {
-			this.element.addEventListener( 'click', this.onProgressClicked, false );
-		}
-
-	}
-
-	unbind() {
-
-		if ( this.Reveal.getConfig().progress && this.element ) {
-			this.element.removeEventListener( 'click', this.onProgressClicked, false );
-		}
-
-	}
-
-	/**
-	 * Updates the progress bar to reflect the current slide.
-	 */
-	update() {
-
-		// Update progress if enabled
-		if( this.Reveal.getConfig().progress && this.bar ) {
-
-			let scale = this.Reveal.getProgress();
-
-			// Don't fill the progress bar if there's only one slide
-			if( this.Reveal.getTotalSlides() < 2 ) {
-				scale = 0;
-			}
-
-			this.bar.style.transform = 'scaleX('+ scale +')';
-
-		}
-
-	}
-
-	getMaxWidth() {
-
-		return this.Reveal.getRevealElement().offsetWidth;
-
-	}
-
-	/**
-	 * Clicking on the progress bar results in a navigation to the
-	 * closest approximate horizontal slide using this equation:
-	 *
-	 * ( clickX / presentationWidth ) * numberOfSlides
-	 *
-	 * @param {object} event
-	 */
-	onProgressClicked( event ) {
-
-		this.Reveal.onUserInput( event );
-
-		event.preventDefault();
-
-		let slides = this.Reveal.getSlides();
-		let slidesTotal = slides.length;
-		let slideIndex = Math.floor( ( event.clientX / this.getMaxWidth() ) * slidesTotal );
-
-		if( this.Reveal.getConfig().rtl ) {
-			slideIndex = slidesTotal - slideIndex;
-		}
-
-		let targetIndices = this.Reveal.getIndices(slides[slideIndex]);
-		this.Reveal.slide( targetIndices.h, targetIndices.v );
-
-	}
-
-	destroy() {
-
-		this.element.remove();
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/slidecontent.js b/geschichte/js/controllers/slidecontent.js
deleted file mode 100644
index 5462dbf..0000000
--- a/geschichte/js/controllers/slidecontent.js
+++ /dev/null
@@ -1,480 +0,0 @@
-import { extend, queryAll, closest, getMimeTypeFromFile, encodeRFC3986URI } from '../utils/util.js'
-import { isMobile } from '../utils/device.js'
-
-import fitty from 'fitty';
-
-/**
- * Handles loading, unloading and playback of slide
- * content such as images, videos and iframes.
- */
-export default class SlideContent {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		this.startEmbeddedIframe = this.startEmbeddedIframe.bind( this );
-
-	}
-
-	/**
-	 * Should the given element be preloaded?
-	 * Decides based on local element attributes and global config.
-	 *
-	 * @param {HTMLElement} element
-	 */
-	shouldPreload( element ) {
-
-		// Prefer an explicit global preload setting
-		let preload = this.Reveal.getConfig().preloadIframes;
-
-		// If no global setting is available, fall back on the element's
-		// own preload setting
-		if( typeof preload !== 'boolean' ) {
-			preload = element.hasAttribute( 'data-preload' );
-		}
-
-		return preload;
-	}
-
-	/**
-	 * Called when the given slide is within the configured view
-	 * distance. Shows the slide element and loads any content
-	 * that is set to load lazily (data-src).
-	 *
-	 * @param {HTMLElement} slide Slide to show
-	 */
-	load( slide, options = {} ) {
-
-		// Show the slide element
-		slide.style.display = this.Reveal.getConfig().display;
-
-		// Media elements with data-src attributes
-		queryAll( slide, 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ).forEach( element => {
-			if( element.tagName !== 'IFRAME' || this.shouldPreload( element ) ) {
-				element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
-				element.setAttribute( 'data-lazy-loaded', '' );
-				element.removeAttribute( 'data-src' );
-			}
-		} );
-
-		// Media elements with <source> children
-		queryAll( slide, 'video, audio' ).forEach( media => {
-			let sources = 0;
-
-			queryAll( media, 'source[data-src]' ).forEach( source => {
-				source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
-				source.removeAttribute( 'data-src' );
-				source.setAttribute( 'data-lazy-loaded', '' );
-				sources += 1;
-			} );
-
-			// Enable inline video playback in mobile Safari
-			if( isMobile && media.tagName === 'VIDEO' ) {
-				media.setAttribute( 'playsinline', '' );
-			}
-
-			// If we rewrote sources for this video/audio element, we need
-			// to manually tell it to load from its new origin
-			if( sources > 0 ) {
-				media.load();
-			}
-		} );
-
-
-		// Show the corresponding background element
-		let background = slide.slideBackgroundElement;
-		if( background ) {
-			background.style.display = 'block';
-
-			let backgroundContent = slide.slideBackgroundContentElement;
-			let backgroundIframe = slide.getAttribute( 'data-background-iframe' );
-
-			// If the background contains media, load it
-			if( background.hasAttribute( 'data-loaded' ) === false ) {
-				background.setAttribute( 'data-loaded', 'true' );
-
-				let backgroundImage = slide.getAttribute( 'data-background-image' ),
-					backgroundVideo = slide.getAttribute( 'data-background-video' ),
-					backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
-					backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' );
-
-				// Images
-				if( backgroundImage ) {
-					// base64
-					if(  /^data:/.test( backgroundImage.trim() ) ) {
-						backgroundContent.style.backgroundImage = `url(${backgroundImage.trim()})`;
-					}
-					// URL(s)
-					else {
-						backgroundContent.style.backgroundImage = backgroundImage.split( ',' ).map( background => {
-							// Decode URL(s) that are already encoded first
-							let decoded = decodeURI(background.trim());
-							return `url(${encodeRFC3986URI(decoded)})`;
-						}).join( ',' );
-					}
-				}
-				// Videos
-				else if ( backgroundVideo && !this.Reveal.isSpeakerNotes() ) {
-					let video = document.createElement( 'video' );
-
-					if( backgroundVideoLoop ) {
-						video.setAttribute( 'loop', '' );
-					}
-
-					if( backgroundVideoMuted ) {
-						video.muted = true;
-					}
-
-					// Enable inline playback in mobile Safari
-					//
-					// Mute is required for video to play when using
-					// swipe gestures to navigate since they don't
-					// count as direct user actions :'(
-					if( isMobile ) {
-						video.muted = true;
-						video.setAttribute( 'playsinline', '' );
-					}
-
-					// Support comma separated lists of video sources
-					backgroundVideo.split( ',' ).forEach( source => {
-						let type = getMimeTypeFromFile( source );
-						if( type ) {
-							video.innerHTML += `<source src="${source}" type="${type}">`;
-						}
-						else {
-							video.innerHTML += `<source src="${source}">`;
-						}
-					} );
-
-					backgroundContent.appendChild( video );
-				}
-				// Iframes
-				else if( backgroundIframe && options.excludeIframes !== true ) {
-					let iframe = document.createElement( 'iframe' );
-					iframe.setAttribute( 'allowfullscreen', '' );
-					iframe.setAttribute( 'mozallowfullscreen', '' );
-					iframe.setAttribute( 'webkitallowfullscreen', '' );
-					iframe.setAttribute( 'allow', 'autoplay' );
-
-					iframe.setAttribute( 'data-src', backgroundIframe );
-
-					iframe.style.width  = '100%';
-					iframe.style.height = '100%';
-					iframe.style.maxHeight = '100%';
-					iframe.style.maxWidth = '100%';
-
-					backgroundContent.appendChild( iframe );
-				}
-			}
-
-			// Start loading preloadable iframes
-			let backgroundIframeElement = backgroundContent.querySelector( 'iframe[data-src]' );
-			if( backgroundIframeElement ) {
-
-				// Check if this iframe is eligible to be preloaded
-				if( this.shouldPreload( background ) && !/autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {
-					if( backgroundIframeElement.getAttribute( 'src' ) !== backgroundIframe ) {
-						backgroundIframeElement.setAttribute( 'src', backgroundIframe );
-					}
-				}
-
-			}
-
-		}
-
-		this.layout( slide );
-
-	}
-
-	/**
-	 * Applies JS-dependent layout helpers for the scope.
-	 */
-	layout( scopeElement ) {
-
-		// Autosize text with the r-fit-text class based on the
-		// size of its container. This needs to happen after the
-		// slide is visible in order to measure the text.
-		Array.from( scopeElement.querySelectorAll( '.r-fit-text' ) ).forEach( element => {
-			fitty( element, {
-				minSize: 24,
-				maxSize: this.Reveal.getConfig().height * 0.8,
-				observeMutations: false,
-				observeWindow: false
-			} );
-		} );
-
-	}
-
-	/**
-	 * Unloads and hides the given slide. This is called when the
-	 * slide is moved outside of the configured view distance.
-	 *
-	 * @param {HTMLElement} slide
-	 */
-	unload( slide ) {
-
-		// Hide the slide element
-		slide.style.display = 'none';
-
-		// Hide the corresponding background element
-		let background = this.Reveal.getSlideBackground( slide );
-		if( background ) {
-			background.style.display = 'none';
-
-			// Unload any background iframes
-			queryAll( background, 'iframe[src]' ).forEach( element => {
-				element.removeAttribute( 'src' );
-			} );
-		}
-
-		// Reset lazy-loaded media elements with src attributes
-		queryAll( slide, 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src], iframe[data-lazy-loaded][src]' ).forEach( element => {
-			element.setAttribute( 'data-src', element.getAttribute( 'src' ) );
-			element.removeAttribute( 'src' );
-		} );
-
-		// Reset lazy-loaded media elements with <source> children
-		queryAll( slide, 'video[data-lazy-loaded] source[src], audio source[src]' ).forEach( source => {
-			source.setAttribute( 'data-src', source.getAttribute( 'src' ) );
-			source.removeAttribute( 'src' );
-		} );
-
-	}
-
-	/**
-	 * Enforces origin-specific format rules for embedded media.
-	 */
-	formatEmbeddedContent() {
-
-		let _appendParamToIframeSource = ( sourceAttribute, sourceURL, param ) => {
-			queryAll( this.Reveal.getSlidesElement(), 'iframe['+ sourceAttribute +'*="'+ sourceURL +'"]' ).forEach( el => {
-				let src = el.getAttribute( sourceAttribute );
-				if( src && src.indexOf( param ) === -1 ) {
-					el.setAttribute( sourceAttribute, src + ( !/\?/.test( src ) ? '?' : '&' ) + param );
-				}
-			});
-		};
-
-		// YouTube frames must include "?enablejsapi=1"
-		_appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );
-		_appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );
-
-		// Vimeo frames must include "?api=1"
-		_appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
-		_appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
-
-	}
-
-	/**
-	 * Start playback of any embedded content inside of
-	 * the given element.
-	 *
-	 * @param {HTMLElement} element
-	 */
-	startEmbeddedContent( element ) {
-
-		if( element && !this.Reveal.isSpeakerNotes() ) {
-
-			// Restart GIFs
-			queryAll( element, 'img[src$=".gif"]' ).forEach( el => {
-				// Setting the same unchanged source like this was confirmed
-				// to work in Chrome, FF & Safari
-				el.setAttribute( 'src', el.getAttribute( 'src' ) );
-			} );
-
-			// HTML5 media elements
-			queryAll( element, 'video, audio' ).forEach( el => {
-				if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
-					return;
-				}
-
-				// Prefer an explicit global autoplay setting
-				let autoplay = this.Reveal.getConfig().autoPlayMedia;
-
-				// If no global setting is available, fall back on the element's
-				// own autoplay setting
-				if( typeof autoplay !== 'boolean' ) {
-					autoplay = el.hasAttribute( 'data-autoplay' ) || !!closest( el, '.slide-background' );
-				}
-
-				if( autoplay && typeof el.play === 'function' ) {
-
-					// If the media is ready, start playback
-					if( el.readyState > 1 ) {
-						this.startEmbeddedMedia( { target: el } );
-					}
-					// Mobile devices never fire a loaded event so instead
-					// of waiting, we initiate playback
-					else if( isMobile ) {
-						let promise = el.play();
-
-						// If autoplay does not work, ensure that the controls are visible so
-						// that the viewer can start the media on their own
-						if( promise && typeof promise.catch === 'function' && el.controls === false ) {
-							promise.catch( () => {
-								el.controls = true;
-
-								// Once the video does start playing, hide the controls again
-								el.addEventListener( 'play', () => {
-									el.controls = false;
-								} );
-							} );
-						}
-					}
-					// If the media isn't loaded, wait before playing
-					else {
-						el.removeEventListener( 'loadeddata', this.startEmbeddedMedia ); // remove first to avoid dupes
-						el.addEventListener( 'loadeddata', this.startEmbeddedMedia );
-					}
-
-				}
-			} );
-
-			// Normal iframes
-			queryAll( element, 'iframe[src]' ).forEach( el => {
-				if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
-					return;
-				}
-
-				this.startEmbeddedIframe( { target: el } );
-			} );
-
-			// Lazy loading iframes
-			queryAll( element, 'iframe[data-src]' ).forEach( el => {
-				if( closest( el, '.fragment' ) && !closest( el, '.fragment.visible' ) ) {
-					return;
-				}
-
-				if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
-					el.removeEventListener( 'load', this.startEmbeddedIframe ); // remove first to avoid dupes
-					el.addEventListener( 'load', this.startEmbeddedIframe );
-					el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
-				}
-			} );
-
-		}
-
-	}
-
-	/**
-	 * Starts playing an embedded video/audio element after
-	 * it has finished loading.
-	 *
-	 * @param {object} event
-	 */
-	startEmbeddedMedia( event ) {
-
-		let isAttachedToDOM = !!closest( event.target, 'html' ),
-			isVisible  		= !!closest( event.target, '.present' );
-
-		if( isAttachedToDOM && isVisible ) {
-			event.target.currentTime = 0;
-			event.target.play();
-		}
-
-		event.target.removeEventListener( 'loadeddata', this.startEmbeddedMedia );
-
-	}
-
-	/**
-	 * "Starts" the content of an embedded iframe using the
-	 * postMessage API.
-	 *
-	 * @param {object} event
-	 */
-	startEmbeddedIframe( event ) {
-
-		let iframe = event.target;
-
-		if( iframe && iframe.contentWindow ) {
-
-			let isAttachedToDOM = !!closest( event.target, 'html' ),
-				isVisible  		= !!closest( event.target, '.present' );
-
-			if( isAttachedToDOM && isVisible ) {
-
-				// Prefer an explicit global autoplay setting
-				let autoplay = this.Reveal.getConfig().autoPlayMedia;
-
-				// If no global setting is available, fall back on the element's
-				// own autoplay setting
-				if( typeof autoplay !== 'boolean' ) {
-					autoplay = iframe.hasAttribute( 'data-autoplay' ) || !!closest( iframe, '.slide-background' );
-				}
-
-				// YouTube postMessage API
-				if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {
-					iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
-				}
-				// Vimeo postMessage API
-				else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {
-					iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
-				}
-				// Generic postMessage API
-				else {
-					iframe.contentWindow.postMessage( 'slide:start', '*' );
-				}
-
-			}
-
-		}
-
-	}
-
-	/**
-	 * Stop playback of any embedded content inside of
-	 * the targeted slide.
-	 *
-	 * @param {HTMLElement} element
-	 */
-	stopEmbeddedContent( element, options = {} ) {
-
-		options = extend( {
-			// Defaults
-			unloadIframes: true
-		}, options );
-
-		if( element && element.parentNode ) {
-			// HTML5 media elements
-			queryAll( element, 'video, audio' ).forEach( el => {
-				if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
-					el.setAttribute('data-paused-by-reveal', '');
-					el.pause();
-				}
-			} );
-
-			// Generic postMessage API for non-lazy loaded iframes
-			queryAll( element, 'iframe' ).forEach( el => {
-				if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );
-				el.removeEventListener( 'load', this.startEmbeddedIframe );
-			});
-
-			// YouTube postMessage API
-			queryAll( element, 'iframe[src*="youtube.com/embed/"]' ).forEach( el => {
-				if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {
-					el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
-				}
-			});
-
-			// Vimeo postMessage API
-			queryAll( element, 'iframe[src*="player.vimeo.com/"]' ).forEach( el => {
-				if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {
-					el.contentWindow.postMessage( '{"method":"pause"}', '*' );
-				}
-			});
-
-			if( options.unloadIframes === true ) {
-				// Unload lazy-loaded iframes
-				queryAll( element, 'iframe[data-src]' ).forEach( el => {
-					// Only removing the src doesn't actually unload the frame
-					// in all browsers (Firefox) so we set it to blank first
-					el.setAttribute( 'src', 'about:blank' );
-					el.removeAttribute( 'src' );
-				} );
-			}
-		}
-
-	}
-
-}
diff --git a/geschichte/js/controllers/slidenumber.js b/geschichte/js/controllers/slidenumber.js
deleted file mode 100644
index ff887ec..0000000
--- a/geschichte/js/controllers/slidenumber.js
+++ /dev/null
@@ -1,132 +0,0 @@
-/**
- * Handles the display of reveal.js' optional slide number.
- */
-export default class SlideNumber {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-	}
-
-	render() {
-
-		this.element = document.createElement( 'div' );
-		this.element.className = 'slide-number';
-		this.Reveal.getRevealElement().appendChild( this.element );
-
-	}
-
-	/**
-	 * Called when the reveal.js config is updated.
-	 */
-	configure( config, oldConfig ) {
-
-		let slideNumberDisplay = 'none';
-		if( config.slideNumber && !this.Reveal.isPrintingPDF() ) {
-			if( config.showSlideNumber === 'all' ) {
-				slideNumberDisplay = 'block';
-			}
-			else if( config.showSlideNumber === 'speaker' && this.Reveal.isSpeakerNotes() ) {
-				slideNumberDisplay = 'block';
-			}
-		}
-
-		this.element.style.display = slideNumberDisplay;
-
-	}
-
-	/**
-	 * Updates the slide number to match the current slide.
-	 */
-	update() {
-
-		// Update slide number if enabled
-		if( this.Reveal.getConfig().slideNumber && this.element ) {
-			this.element.innerHTML = this.getSlideNumber();
-		}
-
-	}
-
-	/**
-	 * Returns the HTML string corresponding to the current slide
-	 * number, including formatting.
-	 */
-	getSlideNumber( slide = this.Reveal.getCurrentSlide() ) {
-
-		let config = this.Reveal.getConfig();
-		let value;
-		let format = 'h.v';
-
-		if ( typeof config.slideNumber === 'function' ) {
-			value = config.slideNumber( slide );
-		} else {
-			// Check if a custom number format is available
-			if( typeof config.slideNumber === 'string' ) {
-				format = config.slideNumber;
-			}
-
-			// If there are ONLY vertical slides in this deck, always use
-			// a flattened slide number
-			if( !/c/.test( format ) && this.Reveal.getHorizontalSlides().length === 1 ) {
-				format = 'c';
-			}
-
-			// Offset the current slide number by 1 to make it 1-indexed
-			let horizontalOffset = slide && slide.dataset.visibility === 'uncounted' ? 0 : 1;
-
-			value = [];
-			switch( format ) {
-				case 'c':
-					value.push( this.Reveal.getSlidePastCount( slide ) + horizontalOffset );
-					break;
-				case 'c/t':
-					value.push( this.Reveal.getSlidePastCount( slide ) + horizontalOffset, '/', this.Reveal.getTotalSlides() );
-					break;
-				default:
-					let indices = this.Reveal.getIndices( slide );
-					value.push( indices.h + horizontalOffset );
-					let sep = format === 'h/v' ? '/' : '.';
-					if( this.Reveal.isVerticalSlide( slide ) ) value.push( sep, indices.v + 1 );
-			}
-		}
-
-		let url = '#' + this.Reveal.location.getHash( slide );
-		return this.formatNumber( value[0], value[1], value[2], url );
-
-	}
-
-	/**
-	 * Applies HTML formatting to a slide number before it's
-	 * written to the DOM.
-	 *
-	 * @param {number} a Current slide
-	 * @param {string} delimiter Character to separate slide numbers
-	 * @param {(number|*)} b Total slides
-	 * @param {HTMLElement} [url='#'+locationHash()] The url to link to
-	 * @return {string} HTML string fragment
-	 */
-	formatNumber( a, delimiter, b, url = '#' + this.Reveal.location.getHash() ) {
-
-		if( typeof b === 'number' && !isNaN( b ) ) {
-			return  `<a href="${url}">
-					<span class="slide-number-a">${a}</span>
-					<span class="slide-number-delimiter">${delimiter}</span>
-					<span class="slide-number-b">${b}</span>
-					</a>`;
-		}
-		else {
-			return `<a href="${url}">
-					<span class="slide-number-a">${a}</span>
-					</a>`;
-		}
-
-	}
-
-	destroy() {
-
-		this.element.remove();
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/controllers/touch.js b/geschichte/js/controllers/touch.js
deleted file mode 100644
index 5ac6a10..0000000
--- a/geschichte/js/controllers/touch.js
+++ /dev/null
@@ -1,263 +0,0 @@
-import { isAndroid } from '../utils/device.js'
-import { matches } from '../utils/util.js'
-
-const SWIPE_THRESHOLD = 40;
-
-/**
- * Controls all touch interactions and navigations for
- * a presentation.
- */
-export default class Touch {
-
-	constructor( Reveal ) {
-
-		this.Reveal = Reveal;
-
-		// Holds information about the currently ongoing touch interaction
-		this.touchStartX = 0;
-		this.touchStartY = 0;
-		this.touchStartCount = 0;
-		this.touchCaptured = false;
-
-		this.onPointerDown = this.onPointerDown.bind( this );
-		this.onPointerMove = this.onPointerMove.bind( this );
-		this.onPointerUp = this.onPointerUp.bind( this );
-		this.onTouchStart = this.onTouchStart.bind( this );
-		this.onTouchMove = this.onTouchMove.bind( this );
-		this.onTouchEnd = this.onTouchEnd.bind( this );
-
-	}
-
-	/**
-	 *
-	 */
-	bind() {
-
-		let revealElement = this.Reveal.getRevealElement();
-
-		if( 'onpointerdown' in window ) {
-			// Use W3C pointer events
-			revealElement.addEventListener( 'pointerdown', this.onPointerDown, false );
-			revealElement.addEventListener( 'pointermove', this.onPointerMove, false );
-			revealElement.addEventListener( 'pointerup', this.onPointerUp, false );
-		}
-		else if( window.navigator.msPointerEnabled ) {
-			// IE 10 uses prefixed version of pointer events
-			revealElement.addEventListener( 'MSPointerDown', this.onPointerDown, false );
-			revealElement.addEventListener( 'MSPointerMove', this.onPointerMove, false );
-			revealElement.addEventListener( 'MSPointerUp', this.onPointerUp, false );
-		}
-		else {
-			// Fall back to touch events
-			revealElement.addEventListener( 'touchstart', this.onTouchStart, false );
-			revealElement.addEventListener( 'touchmove', this.onTouchMove, false );
-			revealElement.addEventListener( 'touchend', this.onTouchEnd, false );
-		}
-
-	}
-
-	/**
-	 *
-	 */
-	unbind() {
-
-		let revealElement = this.Reveal.getRevealElement();
-
-		revealElement.removeEventListener( 'pointerdown', this.onPointerDown, false );
-		revealElement.removeEventListener( 'pointermove', this.onPointerMove, false );
-		revealElement.removeEventListener( 'pointerup', this.onPointerUp, false );
-
-		revealElement.removeEventListener( 'MSPointerDown', this.onPointerDown, false );
-		revealElement.removeEventListener( 'MSPointerMove', this.onPointerMove, false );
-		revealElement.removeEventListener( 'MSPointerUp', this.onPointerUp, false );
-
-		revealElement.removeEventListener( 'touchstart', this.onTouchStart, false );
-		revealElement.removeEventListener( 'touchmove', this.onTouchMove, false );
-		revealElement.removeEventListener( 'touchend', this.onTouchEnd, false );
-
-	}
-
-	/**
-	 * Checks if the target element prevents the triggering of
-	 * swipe navigation.
-	 */
-	isSwipePrevented( target ) {
-
-		// Prevent accidental swipes when scrubbing timelines
-		if( matches( target, 'video, audio' ) ) return true;
-
-		while( target && typeof target.hasAttribute === 'function' ) {
-			if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;
-			target = target.parentNode;
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Handler for the 'touchstart' event, enables support for
-	 * swipe and pinch gestures.
-	 *
-	 * @param {object} event
-	 */
-	onTouchStart( event ) {
-
-		if( this.isSwipePrevented( event.target ) ) return true;
-
-		this.touchStartX = event.touches[0].clientX;
-		this.touchStartY = event.touches[0].clientY;
-		this.touchStartCount = event.touches.length;
-
-	}
-
-	/**
-	 * Handler for the 'touchmove' event.
-	 *
-	 * @param {object} event
-	 */
-	onTouchMove( event ) {
-
-		if( this.isSwipePrevented( event.target ) ) return true;
-
-		let config = this.Reveal.getConfig();
-
-		// Each touch should only trigger one action
-		if( !this.touchCaptured ) {
-			this.Reveal.onUserInput( event );
-
-			let currentX = event.touches[0].clientX;
-			let currentY = event.touches[0].clientY;
-
-			// There was only one touch point, look for a swipe
-			if( event.touches.length === 1 && this.touchStartCount !== 2 ) {
-
-				let availableRoutes = this.Reveal.availableRoutes({ includeFragments: true });
-
-				let deltaX = currentX - this.touchStartX,
-					deltaY = currentY - this.touchStartY;
-
-				if( deltaX > SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
-					this.touchCaptured = true;
-					if( config.navigationMode === 'linear' ) {
-						if( config.rtl ) {
-							this.Reveal.next();
-						}
-						else {
-							this.Reveal.prev();
-						}
-					}
-					else {
-						this.Reveal.left();
-					}
-				}
-				else if( deltaX < -SWIPE_THRESHOLD && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
-					this.touchCaptured = true;
-					if( config.navigationMode === 'linear' ) {
-						if( config.rtl ) {
-							this.Reveal.prev();
-						}
-						else {
-							this.Reveal.next();
-						}
-					}
-					else {
-						this.Reveal.right();
-					}
-				}
-				else if( deltaY > SWIPE_THRESHOLD && availableRoutes.up ) {
-					this.touchCaptured = true;
-					if( config.navigationMode === 'linear' ) {
-						this.Reveal.prev();
-					}
-					else {
-						this.Reveal.up();
-					}
-				}
-				else if( deltaY < -SWIPE_THRESHOLD && availableRoutes.down ) {
-					this.touchCaptured = true;
-					if( config.navigationMode === 'linear' ) {
-						this.Reveal.next();
-					}
-					else {
-						this.Reveal.down();
-					}
-				}
-
-				// If we're embedded, only block touch events if they have
-				// triggered an action
-				if( config.embedded ) {
-					if( this.touchCaptured || this.Reveal.isVerticalSlide() ) {
-						event.preventDefault();
-					}
-				}
-				// Not embedded? Block them all to avoid needless tossing
-				// around of the viewport in iOS
-				else {
-					event.preventDefault();
-				}
-
-			}
-		}
-		// There's a bug with swiping on some Android devices unless
-		// the default action is always prevented
-		else if( isAndroid ) {
-			event.preventDefault();
-		}
-
-	}
-
-	/**
-	 * Handler for the 'touchend' event.
-	 *
-	 * @param {object} event
-	 */
-	onTouchEnd( event ) {
-
-		this.touchCaptured = false;
-
-	}
-
-	/**
-	 * Convert pointer down to touch start.
-	 *
-	 * @param {object} event
-	 */
-	onPointerDown( event ) {
-
-		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
-			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
-			this.onTouchStart( event );
-		}
-
-	}
-
-	/**
-	 * Convert pointer move to touch move.
-	 *
-	 * @param {object} event
-	 */
-	onPointerMove( event ) {
-
-		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" )  {
-			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
-			this.onTouchMove( event );
-		}
-
-	}
-
-	/**
-	 * Convert pointer up to touch end.
-	 *
-	 * @param {object} event
-	 */
-	onPointerUp( event ) {
-
-		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" )  {
-			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
-			this.onTouchEnd( event );
-		}
-
-	}
-
-}
\ No newline at end of file
diff --git a/geschichte/js/index.js b/geschichte/js/index.js
deleted file mode 100644
index 57be501..0000000
--- a/geschichte/js/index.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import Deck, { VERSION } from './reveal.js'
-
-/**
- * Expose the Reveal class to the window. To create a
- * new instance:
- * let deck = new Reveal( document.querySelector( '.reveal' ), {
- *   controls: false
- * } );
- * deck.initialize().then(() => {
- *   // reveal.js is ready
- * });
- */
-let Reveal = Deck;
-
-
-/**
- * The below is a thin shell that mimics the pre 4.0
- * reveal.js API and ensures backwards compatibility.
- * This API only allows for one Reveal instance per
- * page, whereas the new API above lets you run many
- * presentations on the same page.
- *
- * Reveal.initialize( { controls: false } ).then(() => {
- *   // reveal.js is ready
- * });
- */
-
-let enqueuedAPICalls = [];
-
-Reveal.initialize = options => {
-
-	// Create our singleton reveal.js instance
-	Object.assign( Reveal, new Deck( document.querySelector( '.reveal' ), options ) );
-
-	// Invoke any enqueued API calls
-	enqueuedAPICalls.map( method => method( Reveal ) );
-
-	return Reveal.initialize();
-
-}
-
-/**
- * The pre 4.0 API let you add event listener before
- * initializing. We maintain the same behavior by
- * queuing up premature API calls and invoking all
- * of them when Reveal.initialize is called.
- */
-[ 'configure', 'on', 'off', 'addEventListener', 'removeEventListener', 'registerPlugin' ].forEach( method => {
-	Reveal[method] = ( ...args ) => {
-		enqueuedAPICalls.push( deck => deck[method].call( null, ...args ) );
-	}
-} );
-
-Reveal.isReady = () => false;
-
-Reveal.VERSION = VERSION;
-
-export default Reveal;
\ No newline at end of file
diff --git a/geschichte/js/reveal.js b/geschichte/js/reveal.js
deleted file mode 100644
index d74e429..0000000
--- a/geschichte/js/reveal.js
+++ /dev/null
@@ -1,2836 +0,0 @@
-import SlideContent from './controllers/slidecontent.js'
-import SlideNumber from './controllers/slidenumber.js'
-import JumpToSlide from './controllers/jumptoslide.js'
-import Backgrounds from './controllers/backgrounds.js'
-import AutoAnimate from './controllers/autoanimate.js'
-import Fragments from './controllers/fragments.js'
-import Overview from './controllers/overview.js'
-import Keyboard from './controllers/keyboard.js'
-import Location from './controllers/location.js'
-import Controls from './controllers/controls.js'
-import Progress from './controllers/progress.js'
-import Pointer from './controllers/pointer.js'
-import Plugins from './controllers/plugins.js'
-import Print from './controllers/print.js'
-import Touch from './controllers/touch.js'
-import Focus from './controllers/focus.js'
-import Notes from './controllers/notes.js'
-import Playback from './components/playback.js'
-import defaultConfig from './config.js'
-import * as Util from './utils/util.js'
-import * as Device from './utils/device.js'
-import {
-	SLIDES_SELECTOR,
-	HORIZONTAL_SLIDES_SELECTOR,
-	VERTICAL_SLIDES_SELECTOR,
-	POST_MESSAGE_METHOD_BLACKLIST
-} from './utils/constants.js'
-
-// The reveal.js version
-export const VERSION = '4.5.0';
-
-/**
- * reveal.js
- * https://revealjs.com
- * MIT licensed
- *
- * Copyright (C) 2011-2022 Hakim El Hattab, https://hakim.se
- */
-export default function( revealElement, options ) {
-
-	// Support initialization with no args, one arg
-	// [options] or two args [revealElement, options]
-	if( arguments.length < 2 ) {
-		options = arguments[0];
-		revealElement = document.querySelector( '.reveal' );
-	}
-
-	const Reveal = {};
-
-	// Configuration defaults, can be overridden at initialization time
-	let config = {},
-
-		// Flags if reveal.js is loaded (has dispatched the 'ready' event)
-		ready = false,
-
-		// The horizontal and vertical index of the currently active slide
-		indexh,
-		indexv,
-
-		// The previous and current slide HTML elements
-		previousSlide,
-		currentSlide,
-
-		// Remember which directions that the user has navigated towards
-		navigationHistory = {
-			hasNavigatedHorizontally: false,
-			hasNavigatedVertically: false
-		},
-
-		// Slides may have a data-state attribute which we pick up and apply
-		// as a class to the body. This list contains the combined state of
-		// all current slides.
-		state = [],
-
-		// The current scale of the presentation (see width/height config)
-		scale = 1,
-
-		// CSS transform that is currently applied to the slides container,
-		// split into two groups
-		slidesTransform = { layout: '', overview: '' },
-
-		// Cached references to DOM elements
-		dom = {},
-
-		// Flags if the interaction event listeners are bound
-		eventsAreBound = false,
-
-		// The current slide transition state; idle or running
-		transition = 'idle',
-
-		// The current auto-slide duration
-		autoSlide = 0,
-
-		// Auto slide properties
-		autoSlidePlayer,
-		autoSlideTimeout = 0,
-		autoSlideStartTime = -1,
-		autoSlidePaused = false,
-
-		// Controllers for different aspects of our presentation. They're
-		// all given direct references to this Reveal instance since there
-		// may be multiple presentations running in parallel.
-		slideContent = new SlideContent( Reveal ),
-		slideNumber = new SlideNumber( Reveal ),
-		jumpToSlide = new JumpToSlide( Reveal ),
-		autoAnimate = new AutoAnimate( Reveal ),
-		backgrounds = new Backgrounds( Reveal ),
-		fragments = new Fragments( Reveal ),
-		overview = new Overview( Reveal ),
-		keyboard = new Keyboard( Reveal ),
-		location = new Location( Reveal ),
-		controls = new Controls( Reveal ),
-		progress = new Progress( Reveal ),
-		pointer = new Pointer( Reveal ),
-		plugins = new Plugins( Reveal ),
-		print = new Print( Reveal ),
-		focus = new Focus( Reveal ),
-		touch = new Touch( Reveal ),
-		notes = new Notes( Reveal );
-
-	/**
-	 * Starts up the presentation.
-	 */
-	function initialize( initOptions ) {
-
-		if( !revealElement ) throw 'Unable to find presentation root (<div class="reveal">).';
-
-		// Cache references to key DOM elements
-		dom.wrapper = revealElement;
-		dom.slides = revealElement.querySelector( '.slides' );
-
-		if( !dom.slides ) throw 'Unable to find slides container (<div class="slides">).';
-
-		// Compose our config object in order of increasing precedence:
-		// 1. Default reveal.js options
-		// 2. Options provided via Reveal.configure() prior to
-		//    initialization
-		// 3. Options passed to the Reveal constructor
-		// 4. Options passed to Reveal.initialize
-		// 5. Query params
-		config = { ...defaultConfig, ...config, ...options, ...initOptions, ...Util.getQueryHash() };
-
-		setViewport();
-
-		// Force a layout when the whole page, incl fonts, has loaded
-		window.addEventListener( 'load', layout, false );
-
-		// Register plugins and load dependencies, then move on to #start()
-		plugins.load( config.plugins, config.dependencies ).then( start );
-
-		return new Promise( resolve => Reveal.on( 'ready', resolve ) );
-
-	}
-
-	/**
-	 * Encase the presentation in a reveal.js viewport. The
-	 * extent of the viewport differs based on configuration.
-	 */
-	function setViewport() {
-
-		// Embedded decks use the reveal element as their viewport
-		if( config.embedded === true ) {
-			dom.viewport = Util.closest( revealElement, '.reveal-viewport' ) || revealElement;
-		}
-		// Full-page decks use the body as their viewport
-		else {
-			dom.viewport = document.body;
-			document.documentElement.classList.add( 'reveal-full-page' );
-		}
-
-		dom.viewport.classList.add( 'reveal-viewport' );
-
-	}
-
-	/**
-	 * Starts up reveal.js by binding input events and navigating
-	 * to the current URL deeplink if there is one.
-	 */
-	function start() {
-
-		ready = true;
-
-		// Remove slides hidden with data-visibility
-		removeHiddenSlides();
-
-		// Make sure we've got all the DOM elements we need
-		setupDOM();
-
-		// Listen to messages posted to this window
-		setupPostMessage();
-
-		// Prevent the slides from being scrolled out of view
-		setupScrollPrevention();
-
-		// Adds bindings for fullscreen mode
-		setupFullscreen();
-
-		// Resets all vertical slides so that only the first is visible
-		resetVerticalSlides();
-
-		// Updates the presentation to match the current configuration values
-		configure();
-
-		// Read the initial hash
-		location.readURL();
-
-		// Create slide backgrounds
-		backgrounds.update( true );
-
-		// Notify listeners that the presentation is ready but use a 1ms
-		// timeout to ensure it's not fired synchronously after #initialize()
-		setTimeout( () => {
-			// Enable transitions now that we're loaded
-			dom.slides.classList.remove( 'no-transition' );
-
-			dom.wrapper.classList.add( 'ready' );
-
-			dispatchEvent({
-				type: 'ready',
-				data: {
-					indexh,
-					indexv,
-					currentSlide
-				}
-			});
-		}, 1 );
-
-		// Special setup and config is required when printing to PDF
-		if( print.isPrintingPDF() ) {
-			removeEventListeners();
-
-			// The document needs to have loaded for the PDF layout
-			// measurements to be accurate
-			if( document.readyState === 'complete' ) {
-				print.setupPDF();
-			}
-			else {
-				window.addEventListener( 'load', () => {
-					print.setupPDF();
-				} );
-			}
-		}
-
-	}
-
-	/**
-	 * Removes all slides with data-visibility="hidden". This
-	 * is done right before the rest of the presentation is
-	 * initialized.
-	 *
-	 * If you want to show all hidden slides, initialize
-	 * reveal.js with showHiddenSlides set to true.
-	 */
-	function removeHiddenSlides() {
-
-		if( !config.showHiddenSlides ) {
-			Util.queryAll( dom.wrapper, 'section[data-visibility="hidden"]' ).forEach( slide => {
-				slide.parentNode.removeChild( slide );
-			} );
-		}
-
-	}
-
-	/**
-	 * Finds and stores references to DOM elements which are
-	 * required by the presentation. If a required element is
-	 * not found, it is created.
-	 */
-	function setupDOM() {
-
-		// Prevent transitions while we're loading
-		dom.slides.classList.add( 'no-transition' );
-
-		if( Device.isMobile ) {
-			dom.wrapper.classList.add( 'no-hover' );
-		}
-		else {
-			dom.wrapper.classList.remove( 'no-hover' );
-		}
-
-		backgrounds.render();
-		slideNumber.render();
-		jumpToSlide.render();
-		controls.render();
-		progress.render();
-		notes.render();
-
-		// Overlay graphic which is displayed during the paused mode
-		dom.pauseOverlay = Util.createSingletonNode( dom.wrapper, 'div', 'pause-overlay', config.controls ? '<button class="resume-button">Resume presentation</button>' : null );
-
-		dom.statusElement = createStatusElement();
-
-		dom.wrapper.setAttribute( 'role', 'application' );
-	}
-
-	/**
-	 * Creates a hidden div with role aria-live to announce the
-	 * current slide content. Hide the div off-screen to make it
-	 * available only to Assistive Technologies.
-	 *
-	 * @return {HTMLElement}
-	 */
-	function createStatusElement() {
-
-		let statusElement = dom.wrapper.querySelector( '.aria-status' );
-		if( !statusElement ) {
-			statusElement = document.createElement( 'div' );
-			statusElement.style.position = 'absolute';
-			statusElement.style.height = '1px';
-			statusElement.style.width = '1px';
-			statusElement.style.overflow = 'hidden';
-			statusElement.style.clip = 'rect( 1px, 1px, 1px, 1px )';
-			statusElement.classList.add( 'aria-status' );
-			statusElement.setAttribute( 'aria-live', 'polite' );
-			statusElement.setAttribute( 'aria-atomic','true' );
-			dom.wrapper.appendChild( statusElement );
-		}
-		return statusElement;
-
-	}
-
-	/**
-	 * Announces the given text to screen readers.
-	 */
-	function announceStatus( value ) {
-
-		dom.statusElement.textContent = value;
-
-	}
-
-	/**
-	 * Converts the given HTML element into a string of text
-	 * that can be announced to a screen reader. Hidden
-	 * elements are excluded.
-	 */
-	function getStatusText( node ) {
-
-		let text = '';
-
-		// Text node
-		if( node.nodeType === 3 ) {
-			text += node.textContent;
-		}
-		// Element node
-		else if( node.nodeType === 1 ) {
-
-			let isAriaHidden = node.getAttribute( 'aria-hidden' );
-			let isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';
-			if( isAriaHidden !== 'true' && !isDisplayHidden ) {
-
-				Array.from( node.childNodes ).forEach( child => {
-					text += getStatusText( child );
-				} );
-
-			}
-
-		}
-
-		text = text.trim();
-
-		return text === '' ? '' : text + ' ';
-
-	}
-
-	/**
-	 * This is an unfortunate necessity. Some actions – such as
-	 * an input field being focused in an iframe or using the
-	 * keyboard to expand text selection beyond the bounds of
-	 * a slide – can trigger our content to be pushed out of view.
-	 * This scrolling can not be prevented by hiding overflow in
-	 * CSS (we already do) so we have to resort to repeatedly
-	 * checking if the slides have been offset :(
-	 */
-	function setupScrollPrevention() {
-
-		setInterval( () => {
-			if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
-				dom.wrapper.scrollTop = 0;
-				dom.wrapper.scrollLeft = 0;
-			}
-		}, 1000 );
-
-	}
-
-	/**
-	 * After entering fullscreen we need to force a layout to
-	 * get our presentations to scale correctly. This behavior
-	 * is inconsistent across browsers but a force layout seems
-	 * to normalize it.
-	 */
-	function setupFullscreen() {
-
-		document.addEventListener( 'fullscreenchange', onFullscreenChange );
-		document.addEventListener( 'webkitfullscreenchange', onFullscreenChange );
-
-	}
-
-	/**
-	 * Registers a listener to postMessage events, this makes it
-	 * possible to call all reveal.js API methods from another
-	 * window. For example:
-	 *
-	 * revealWindow.postMessage( JSON.stringify({
-	 *   method: 'slide',
-	 *   args: [ 2 ]
-	 * }), '*' );
-	 */
-	function setupPostMessage() {
-
-		if( config.postMessage ) {
-			window.addEventListener( 'message', onPostMessage, false );
-		}
-
-	}
-
-	/**
-	 * Applies the configuration settings from the config
-	 * object. May be called multiple times.
-	 *
-	 * @param {object} options
-	 */
-	function configure( options ) {
-
-		const oldConfig = { ...config }
-
-		// New config options may be passed when this method
-		// is invoked through the API after initialization
-		if( typeof options === 'object' ) Util.extend( config, options );
-
-		// Abort if reveal.js hasn't finished loading, config
-		// changes will be applied automatically once ready
-		if( Reveal.isReady() ===  false ) return;
-
-		const numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
-
-		// The transition is added as a class on the .reveal element
-		dom.wrapper.classList.remove( oldConfig.transition );
-		dom.wrapper.classList.add( config.transition );
-
-		dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
-		dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
-
-		// Expose our configured slide dimensions as custom props
-		dom.viewport.style.setProperty( '--slide-width', config.width + 'px' );
-		dom.viewport.style.setProperty( '--slide-height', config.height + 'px' );
-
-		if( config.shuffle ) {
-			shuffle();
-		}
-
-		Util.toggleClass( dom.wrapper, 'embedded', config.embedded );
-		Util.toggleClass( dom.wrapper, 'rtl', config.rtl );
-		Util.toggleClass( dom.wrapper, 'center', config.center );
-
-		// Exit the paused mode if it was configured off
-		if( config.pause === false ) {
-			resume();
-		}
-
-		// Iframe link previews
-		if( config.previewLinks ) {
-			enablePreviewLinks();
-			disablePreviewLinks( '[data-preview-link=false]' );
-		}
-		else {
-			disablePreviewLinks();
-			enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );
-		}
-
-		// Reset all changes made by auto-animations
-		autoAnimate.reset();
-
-		// Remove existing auto-slide controls
-		if( autoSlidePlayer ) {
-			autoSlidePlayer.destroy();
-			autoSlidePlayer = null;
-		}
-
-		// Generate auto-slide controls if needed
-		if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable ) {
-			autoSlidePlayer = new Playback( dom.wrapper, () => {
-				return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
-			} );
-
-			autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
-			autoSlidePaused = false;
-		}
-
-		// Add the navigation mode to the DOM so we can adjust styling
-		if( config.navigationMode !== 'default' ) {
-			dom.wrapper.setAttribute( 'data-navigation-mode', config.navigationMode );
-		}
-		else {
-			dom.wrapper.removeAttribute( 'data-navigation-mode' );
-		}
-
-		notes.configure( config, oldConfig );
-		focus.configure( config, oldConfig );
-		pointer.configure( config, oldConfig );
-		controls.configure( config, oldConfig );
-		progress.configure( config, oldConfig );
-		keyboard.configure( config, oldConfig );
-		fragments.configure( config, oldConfig );
-		slideNumber.configure( config, oldConfig );
-
-		sync();
-
-	}
-
-	/**
-	 * Binds all event listeners.
-	 */
-	function addEventListeners() {
-
-		eventsAreBound = true;
-
-		window.addEventListener( 'resize', onWindowResize, false );
-
-		if( config.touch ) touch.bind();
-		if( config.keyboard ) keyboard.bind();
-		if( config.progress ) progress.bind();
-		if( config.respondToHashChanges ) location.bind();
-		controls.bind();
-		focus.bind();
-
-		dom.slides.addEventListener( 'click', onSlidesClicked, false );
-		dom.slides.addEventListener( 'transitionend', onTransitionEnd, false );
-		dom.pauseOverlay.addEventListener( 'click', resume, false );
-
-		if( config.focusBodyOnPageVisibilityChange ) {
-			document.addEventListener( 'visibilitychange', onPageVisibilityChange, false );
-		}
-
-	}
-
-	/**
-	 * Unbinds all event listeners.
-	 */
-	function removeEventListeners() {
-
-		eventsAreBound = false;
-
-		touch.unbind();
-		focus.unbind();
-		keyboard.unbind();
-		controls.unbind();
-		progress.unbind();
-		location.unbind();
-
-		window.removeEventListener( 'resize', onWindowResize, false );
-
-		dom.slides.removeEventListener( 'click', onSlidesClicked, false );
-		dom.slides.removeEventListener( 'transitionend', onTransitionEnd, false );
-		dom.pauseOverlay.removeEventListener( 'click', resume, false );
-
-	}
-
-	/**
-	 * Uninitializes reveal.js by undoing changes made to the
-	 * DOM and removing all event listeners.
-	 */
-	function destroy() {
-
-		removeEventListeners();
-		cancelAutoSlide();
-		disablePreviewLinks();
-
-		// Destroy controllers
-		notes.destroy();
-		focus.destroy();
-		plugins.destroy();
-		pointer.destroy();
-		controls.destroy();
-		progress.destroy();
-		backgrounds.destroy();
-		slideNumber.destroy();
-		jumpToSlide.destroy();
-
-		// Remove event listeners
-		document.removeEventListener( 'fullscreenchange', onFullscreenChange );
-		document.removeEventListener( 'webkitfullscreenchange', onFullscreenChange );
-		document.removeEventListener( 'visibilitychange', onPageVisibilityChange, false );
-		window.removeEventListener( 'message', onPostMessage, false );
-		window.removeEventListener( 'load', layout, false );
-
-		// Undo DOM changes
-		if( dom.pauseOverlay ) dom.pauseOverlay.remove();
-		if( dom.statusElement ) dom.statusElement.remove();
-
-		document.documentElement.classList.remove( 'reveal-full-page' );
-
-		dom.wrapper.classList.remove( 'ready', 'center', 'has-horizontal-slides', 'has-vertical-slides' );
-		dom.wrapper.removeAttribute( 'data-transition-speed' );
-		dom.wrapper.removeAttribute( 'data-background-transition' );
-
-		dom.viewport.classList.remove( 'reveal-viewport' );
-		dom.viewport.style.removeProperty( '--slide-width' );
-		dom.viewport.style.removeProperty( '--slide-height' );
-
-		dom.slides.style.removeProperty( 'width' );
-		dom.slides.style.removeProperty( 'height' );
-		dom.slides.style.removeProperty( 'zoom' );
-		dom.slides.style.removeProperty( 'left' );
-		dom.slides.style.removeProperty( 'top' );
-		dom.slides.style.removeProperty( 'bottom' );
-		dom.slides.style.removeProperty( 'right' );
-		dom.slides.style.removeProperty( 'transform' );
-
-		Array.from( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( slide => {
-			slide.style.removeProperty( 'display' );
-			slide.style.removeProperty( 'top' );
-			slide.removeAttribute( 'hidden' );
-			slide.removeAttribute( 'aria-hidden' );
-		} );
-
-	}
-
-	/**
-	 * Adds a listener to one of our custom reveal.js events,
-	 * like slidechanged.
-	 */
-	function on( type, listener, useCapture ) {
-
-		revealElement.addEventListener( type, listener, useCapture );
-
-	}
-
-	/**
-	 * Unsubscribes from a reveal.js event.
-	 */
-	function off( type, listener, useCapture ) {
-
-		revealElement.removeEventListener( type, listener, useCapture );
-
-	}
-
-	/**
-	 * Applies CSS transforms to the slides container. The container
-	 * is transformed from two separate sources: layout and the overview
-	 * mode.
-	 *
-	 * @param {object} transforms
-	 */
-	function transformSlides( transforms ) {
-
-		// Pick up new transforms from arguments
-		if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
-		if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
-
-		// Apply the transforms to the slides container
-		if( slidesTransform.layout ) {
-			Util.transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
-		}
-		else {
-			Util.transformElement( dom.slides, slidesTransform.overview );
-		}
-
-	}
-
-	/**
-	 * Dispatches an event of the specified type from the
-	 * reveal DOM element.
-	 */
-	function dispatchEvent({ target=dom.wrapper, type, data, bubbles=true }) {
-
-		let event = document.createEvent( 'HTMLEvents', 1, 2 );
-		event.initEvent( type, bubbles, true );
-		Util.extend( event, data );
-		target.dispatchEvent( event );
-
-		if( target === dom.wrapper ) {
-			// If we're in an iframe, post each reveal.js event to the
-			// parent window. Used by the notes plugin
-			dispatchPostMessage( type );
-		}
-
-		return event;
-
-	}
-
-	/**
-	 * Dispatched a postMessage of the given type from our window.
-	 */
-	function dispatchPostMessage( type, data ) {
-
-		if( config.postMessageEvents && window.parent !== window.self ) {
-			let message = {
-				namespace: 'reveal',
-				eventName: type,
-				state: getState()
-			};
-
-			Util.extend( message, data );
-
-			window.parent.postMessage( JSON.stringify( message ), '*' );
-		}
-
-	}
-
-	/**
-	 * Bind preview frame links.
-	 *
-	 * @param {string} [selector=a] - selector for anchors
-	 */
-	function enablePreviewLinks( selector = 'a' ) {
-
-		Array.from( dom.wrapper.querySelectorAll( selector ) ).forEach( element => {
-			if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
-				element.addEventListener( 'click', onPreviewLinkClicked, false );
-			}
-		} );
-
-	}
-
-	/**
-	 * Unbind preview frame links.
-	 */
-	function disablePreviewLinks( selector = 'a' ) {
-
-		Array.from( dom.wrapper.querySelectorAll( selector ) ).forEach( element => {
-			if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
-				element.removeEventListener( 'click', onPreviewLinkClicked, false );
-			}
-		} );
-
-	}
-
-	/**
-	 * Opens a preview window for the target URL.
-	 *
-	 * @param {string} url - url for preview iframe src
-	 */
-	function showPreview( url ) {
-
-		closeOverlay();
-
-		dom.overlay = document.createElement( 'div' );
-		dom.overlay.classList.add( 'overlay' );
-		dom.overlay.classList.add( 'overlay-preview' );
-		dom.wrapper.appendChild( dom.overlay );
-
-		dom.overlay.innerHTML =
-			`<header>
-				<a class="close" href="#"><span class="icon"></span></a>
-				<a class="external" href="${url}" target="_blank"><span class="icon"></span></a>
-			</header>
-			<div class="spinner"></div>
-			<div class="viewport">
-				<iframe src="${url}"></iframe>
-				<small class="viewport-inner">
-					<span class="x-frame-error">Unable to load iframe. This is likely due to the site's policy (x-frame-options).</span>
-				</small>
-			</div>`;
-
-		dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', event => {
-			dom.overlay.classList.add( 'loaded' );
-		}, false );
-
-		dom.overlay.querySelector( '.close' ).addEventListener( 'click', event => {
-			closeOverlay();
-			event.preventDefault();
-		}, false );
-
-		dom.overlay.querySelector( '.external' ).addEventListener( 'click', event => {
-			closeOverlay();
-		}, false );
-
-	}
-
-	/**
-	 * Open or close help overlay window.
-	 *
-	 * @param {Boolean} [override] Flag which overrides the
-	 * toggle logic and forcibly sets the desired state. True means
-	 * help is open, false means it's closed.
-	 */
-	function toggleHelp( override ){
-
-		if( typeof override === 'boolean' ) {
-			override ? showHelp() : closeOverlay();
-		}
-		else {
-			if( dom.overlay ) {
-				closeOverlay();
-			}
-			else {
-				showHelp();
-			}
-		}
-	}
-
-	/**
-	 * Opens an overlay window with help material.
-	 */
-	function showHelp() {
-
-		if( config.help ) {
-
-			closeOverlay();
-
-			dom.overlay = document.createElement( 'div' );
-			dom.overlay.classList.add( 'overlay' );
-			dom.overlay.classList.add( 'overlay-help' );
-			dom.wrapper.appendChild( dom.overlay );
-
-			let html = '<p class="title">Keyboard Shortcuts</p><br/>';
-
-			let shortcuts = keyboard.getShortcuts(),
-				bindings = keyboard.getBindings();
-
-			html += '<table><th>KEY</th><th>ACTION</th>';
-			for( let key in shortcuts ) {
-				html += `<tr><td>${key}</td><td>${shortcuts[ key ]}</td></tr>`;
-			}
-
-			// Add custom key bindings that have associated descriptions
-			for( let binding in bindings ) {
-				if( bindings[binding].key && bindings[binding].description ) {
-					html += `<tr><td>${bindings[binding].key}</td><td>${bindings[binding].description}</td></tr>`;
-				}
-			}
-
-			html += '</table>';
-
-			dom.overlay.innerHTML = `
-				<header>
-					<a class="close" href="#"><span class="icon"></span></a>
-				</header>
-				<div class="viewport">
-					<div class="viewport-inner">${html}</div>
-				</div>
-			`;
-
-			dom.overlay.querySelector( '.close' ).addEventListener( 'click', event => {
-				closeOverlay();
-				event.preventDefault();
-			}, false );
-
-		}
-
-	}
-
-	/**
-	 * Closes any currently open overlay.
-	 */
-	function closeOverlay() {
-
-		if( dom.overlay ) {
-			dom.overlay.parentNode.removeChild( dom.overlay );
-			dom.overlay = null;
-			return true;
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Applies JavaScript-controlled layout rules to the
-	 * presentation.
-	 */
-	function layout() {
-
-		if( dom.wrapper && !print.isPrintingPDF() ) {
-
-			if( !config.disableLayout ) {
-
-				// On some mobile devices '100vh' is taller than the visible
-				// viewport which leads to part of the presentation being
-				// cut off. To work around this we define our own '--vh' custom
-				// property where 100x adds up to the correct height.
-				//
-				// https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
-				if( Device.isMobile && !config.embedded ) {
-					document.documentElement.style.setProperty( '--vh', ( window.innerHeight * 0.01 ) + 'px' );
-				}
-
-				const size = getComputedSlideSize();
-
-				const oldScale = scale;
-
-				// Layout the contents of the slides
-				layoutSlideContents( config.width, config.height );
-
-				dom.slides.style.width = size.width + 'px';
-				dom.slides.style.height = size.height + 'px';
-
-				// Determine scale of content to fit within available space
-				scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
-
-				// Respect max/min scale settings
-				scale = Math.max( scale, config.minScale );
-				scale = Math.min( scale, config.maxScale );
-
-				// Don't apply any scaling styles if scale is 1
-				if( scale === 1 ) {
-					dom.slides.style.zoom = '';
-					dom.slides.style.left = '';
-					dom.slides.style.top = '';
-					dom.slides.style.bottom = '';
-					dom.slides.style.right = '';
-					transformSlides( { layout: '' } );
-				}
-				else {
-					dom.slides.style.zoom = '';
-					dom.slides.style.left = '50%';
-					dom.slides.style.top = '50%';
-					dom.slides.style.bottom = 'auto';
-					dom.slides.style.right = 'auto';
-					transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
-				}
-
-				// Select all slides, vertical and horizontal
-				const slides = Array.from( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
-
-				for( let i = 0, len = slides.length; i < len; i++ ) {
-					const slide = slides[ i ];
-
-					// Don't bother updating invisible slides
-					if( slide.style.display === 'none' ) {
-						continue;
-					}
-
-					if( config.center || slide.classList.contains( 'center' ) ) {
-						// Vertical stacks are not centred since their section
-						// children will be
-						if( slide.classList.contains( 'stack' ) ) {
-							slide.style.top = 0;
-						}
-						else {
-							slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';
-						}
-					}
-					else {
-						slide.style.top = '';
-					}
-
-				}
-
-				if( oldScale !== scale ) {
-					dispatchEvent({
-						type: 'resize',
-						data: {
-							oldScale,
-							scale,
-							size
-						}
-					});
-				}
-			}
-
-			dom.viewport.style.setProperty( '--slide-scale', scale );
-
-			progress.update();
-			backgrounds.updateParallax();
-
-			if( overview.isActive() ) {
-				overview.update();
-			}
-
-		}
-
-	}
-
-	/**
-	 * Applies layout logic to the contents of all slides in
-	 * the presentation.
-	 *
-	 * @param {string|number} width
-	 * @param {string|number} height
-	 */
-	function layoutSlideContents( width, height ) {
-
-		// Handle sizing of elements with the 'r-stretch' class
-		Util.queryAll( dom.slides, 'section > .stretch, section > .r-stretch' ).forEach( element => {
-
-			// Determine how much vertical space we can use
-			let remainingHeight = Util.getRemainingHeight( element, height );
-
-			// Consider the aspect ratio of media elements
-			if( /(img|video)/gi.test( element.nodeName ) ) {
-				const nw = element.naturalWidth || element.videoWidth,
-					  nh = element.naturalHeight || element.videoHeight;
-
-				const es = Math.min( width / nw, remainingHeight / nh );
-
-				element.style.width = ( nw * es ) + 'px';
-				element.style.height = ( nh * es ) + 'px';
-
-			}
-			else {
-				element.style.width = width + 'px';
-				element.style.height = remainingHeight + 'px';
-			}
-
-		} );
-
-	}
-
-	/**
-	 * Calculates the computed pixel size of our slides. These
-	 * values are based on the width and height configuration
-	 * options.
-	 *
-	 * @param {number} [presentationWidth=dom.wrapper.offsetWidth]
-	 * @param {number} [presentationHeight=dom.wrapper.offsetHeight]
-	 */
-	function getComputedSlideSize( presentationWidth, presentationHeight ) {
-		let width = config.width;
-		let height = config.height;
-
-		if( config.disableLayout ) {
-			width = dom.slides.offsetWidth;
-			height = dom.slides.offsetHeight;
-		}
-
-		const size = {
-			// Slide size
-			width: width,
-			height: height,
-
-			// Presentation size
-			presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
-			presentationHeight: presentationHeight || dom.wrapper.offsetHeight
-		};
-
-		// Reduce available space by margin
-		size.presentationWidth -= ( size.presentationWidth * config.margin );
-		size.presentationHeight -= ( size.presentationHeight * config.margin );
-
-		// Slide width may be a percentage of available width
-		if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
-			size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
-		}
-
-		// Slide height may be a percentage of available height
-		if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
-			size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
-		}
-
-		return size;
-
-	}
-
-	/**
-	 * Stores the vertical index of a stack so that the same
-	 * vertical slide can be selected when navigating to and
-	 * from the stack.
-	 *
-	 * @param {HTMLElement} stack The vertical stack element
-	 * @param {string|number} [v=0] Index to memorize
-	 */
-	function setPreviousVerticalIndex( stack, v ) {
-
-		if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
-			stack.setAttribute( 'data-previous-indexv', v || 0 );
-		}
-
-	}
-
-	/**
-	 * Retrieves the vertical index which was stored using
-	 * #setPreviousVerticalIndex() or 0 if no previous index
-	 * exists.
-	 *
-	 * @param {HTMLElement} stack The vertical stack element
-	 */
-	function getPreviousVerticalIndex( stack ) {
-
-		if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
-			// Prefer manually defined start-indexv
-			const attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
-
-			return parseInt( stack.getAttribute( attributeName ) || 0, 10 );
-		}
-
-		return 0;
-
-	}
-
-	/**
-	 * Checks if the current or specified slide is vertical
-	 * (nested within another slide).
-	 *
-	 * @param {HTMLElement} [slide=currentSlide] The slide to check
-	 * orientation of
-	 * @return {Boolean}
-	 */
-	function isVerticalSlide( slide = currentSlide ) {
-
-		return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );
-
-	}
-
-	/**
-	 * Returns true if we're on the last slide in the current
-	 * vertical stack.
-	 */
-	function isLastVerticalSlide() {
-
-		if( currentSlide && isVerticalSlide( currentSlide ) ) {
-			// Does this slide have a next sibling?
-			if( currentSlide.nextElementSibling ) return false;
-
-			return true;
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Returns true if we're currently on the first slide in
-	 * the presentation.
-	 */
-	function isFirstSlide() {
-
-		return indexh === 0 && indexv === 0;
-
-	}
-
-	/**
-	 * Returns true if we're currently on the last slide in
-	 * the presenation. If the last slide is a stack, we only
-	 * consider this the last slide if it's at the end of the
-	 * stack.
-	 */
-	function isLastSlide() {
-
-		if( currentSlide ) {
-			// Does this slide have a next sibling?
-			if( currentSlide.nextElementSibling ) return false;
-
-			// If it's vertical, does its parent have a next sibling?
-			if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
-
-			return true;
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Enters the paused mode which fades everything on screen to
-	 * black.
-	 */
-	function pause() {
-
-		if( config.pause ) {
-			const wasPaused = dom.wrapper.classList.contains( 'paused' );
-
-			cancelAutoSlide();
-			dom.wrapper.classList.add( 'paused' );
-
-			if( wasPaused === false ) {
-				dispatchEvent({ type: 'paused' });
-			}
-		}
-
-	}
-
-	/**
-	 * Exits from the paused mode.
-	 */
-	function resume() {
-
-		const wasPaused = dom.wrapper.classList.contains( 'paused' );
-		dom.wrapper.classList.remove( 'paused' );
-
-		cueAutoSlide();
-
-		if( wasPaused ) {
-			dispatchEvent({ type: 'resumed' });
-		}
-
-	}
-
-	/**
-	 * Toggles the paused mode on and off.
-	 */
-	function togglePause( override ) {
-
-		if( typeof override === 'boolean' ) {
-			override ? pause() : resume();
-		}
-		else {
-			isPaused() ? resume() : pause();
-		}
-
-	}
-
-	/**
-	 * Checks if we are currently in the paused mode.
-	 *
-	 * @return {Boolean}
-	 */
-	function isPaused() {
-
-		return dom.wrapper.classList.contains( 'paused' );
-
-	}
-
-	/**
-	 * Toggles visibility of the jump-to-slide UI.
-	 */
-	function toggleJumpToSlide( override ) {
-
-		if( typeof override === 'boolean' ) {
-			override ? jumpToSlide.show() : jumpToSlide.hide();
-		}
-		else {
-			jumpToSlide.isVisible() ? jumpToSlide.hide() : jumpToSlide.show();
-		}
-
-	}
-
-	/**
-	 * Toggles the auto slide mode on and off.
-	 *
-	 * @param {Boolean} [override] Flag which sets the desired state.
-	 * True means autoplay starts, false means it stops.
-	 */
-
-	function toggleAutoSlide( override ) {
-
-		if( typeof override === 'boolean' ) {
-			override ? resumeAutoSlide() : pauseAutoSlide();
-		}
-
-		else {
-			autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
-		}
-
-	}
-
-	/**
-	 * Checks if the auto slide mode is currently on.
-	 *
-	 * @return {Boolean}
-	 */
-	function isAutoSliding() {
-
-		return !!( autoSlide && !autoSlidePaused );
-
-	}
-
-	/**
-	 * Steps from the current point in the presentation to the
-	 * slide which matches the specified horizontal and vertical
-	 * indices.
-	 *
-	 * @param {number} [h=indexh] Horizontal index of the target slide
-	 * @param {number} [v=indexv] Vertical index of the target slide
-	 * @param {number} [f] Index of a fragment within the
-	 * target slide to activate
-	 * @param {number} [origin] Origin for use in multimaster environments
-	 */
-	function slide( h, v, f, origin ) {
-
-		// Dispatch an event before the slide
-		const slidechange = dispatchEvent({
-			type: 'beforeslidechange',
-			data: {
-				indexh: h === undefined ? indexh : h,
-				indexv: v === undefined ? indexv : v,
-				origin
-			}
-		});
-
-		// Abort if this slide change was prevented by an event listener
-		if( slidechange.defaultPrevented ) return;
-
-		// Remember where we were at before
-		previousSlide = currentSlide;
-
-		// Query all horizontal slides in the deck
-		const horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
-
-		// Abort if there are no slides
-		if( horizontalSlides.length === 0 ) return;
-
-		// If no vertical index is specified and the upcoming slide is a
-		// stack, resume at its previous vertical index
-		if( v === undefined && !overview.isActive() ) {
-			v = getPreviousVerticalIndex( horizontalSlides[ h ] );
-		}
-
-		// If we were on a vertical stack, remember what vertical index
-		// it was on so we can resume at the same position when returning
-		if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {
-			setPreviousVerticalIndex( previousSlide.parentNode, indexv );
-		}
-
-		// Remember the state before this slide
-		const stateBefore = state.concat();
-
-		// Reset the state array
-		state.length = 0;
-
-		let indexhBefore = indexh || 0,
-			indexvBefore = indexv || 0;
-
-		// Activate and transition to the new slide
-		indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
-		indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
-
-		// Dispatch an event if the slide changed
-		let slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
-
-		// Ensure that the previous slide is never the same as the current
-		if( !slideChanged ) previousSlide = null;
-
-		// Find the current horizontal slide and any possible vertical slides
-		// within it
-		let currentHorizontalSlide = horizontalSlides[ indexh ],
-			currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
-
-		// Store references to the previous and current slides
-		currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
-
-		let autoAnimateTransition = false;
-
-		// Detect if we're moving between two auto-animated slides
-		if( slideChanged && previousSlide && currentSlide && !overview.isActive() ) {
-
-			// If this is an auto-animated transition, we disable the
-			// regular slide transition
-			//
-			// Note 20-03-2020:
-			// This needs to happen before we update slide visibility,
-			// otherwise transitions will still run in Safari.
-			if( previousSlide.hasAttribute( 'data-auto-animate' ) && currentSlide.hasAttribute( 'data-auto-animate' )
-					&& previousSlide.getAttribute( 'data-auto-animate-id' ) === currentSlide.getAttribute( 'data-auto-animate-id' )
-					&& !( ( indexh > indexhBefore || indexv > indexvBefore ) ? currentSlide : previousSlide ).hasAttribute( 'data-auto-animate-restart' ) ) {
-
-				autoAnimateTransition = true;
-				dom.slides.classList.add( 'disable-slide-transitions' );
-			}
-
-			transition = 'running';
-
-		}
-
-		// Update the visibility of slides now that the indices have changed
-		updateSlidesVisibility();
-
-		layout();
-
-		// Update the overview if it's currently active
-		if( overview.isActive() ) {
-			overview.update();
-		}
-
-		// Show fragment, if specified
-		if( typeof f !== 'undefined' ) {
-			fragments.goto( f );
-		}
-
-		// Solves an edge case where the previous slide maintains the
-		// 'present' class when navigating between adjacent vertical
-		// stacks
-		if( previousSlide && previousSlide !== currentSlide ) {
-			previousSlide.classList.remove( 'present' );
-			previousSlide.setAttribute( 'aria-hidden', 'true' );
-
-			// Reset all slides upon navigate to home
-			if( isFirstSlide() ) {
-				// Launch async task
-				setTimeout( () => {
-					getVerticalStacks().forEach( slide => {
-						setPreviousVerticalIndex( slide, 0 );
-					} );
-				}, 0 );
-			}
-		}
-
-		// Apply the new state
-		stateLoop: for( let i = 0, len = state.length; i < len; i++ ) {
-			// Check if this state existed on the previous slide. If it
-			// did, we will avoid adding it repeatedly
-			for( let j = 0; j < stateBefore.length; j++ ) {
-				if( stateBefore[j] === state[i] ) {
-					stateBefore.splice( j, 1 );
-					continue stateLoop;
-				}
-			}
-
-			dom.viewport.classList.add( state[i] );
-
-			// Dispatch custom event matching the state's name
-			dispatchEvent({ type: state[i] });
-		}
-
-		// Clean up the remains of the previous state
-		while( stateBefore.length ) {
-			dom.viewport.classList.remove( stateBefore.pop() );
-		}
-
-		if( slideChanged ) {
-			dispatchEvent({
-				type: 'slidechanged',
-				data: {
-					indexh,
-					indexv,
-					previousSlide,
-					currentSlide,
-					origin
-				}
-			});
-		}
-
-		// Handle embedded content
-		if( slideChanged || !previousSlide ) {
-			slideContent.stopEmbeddedContent( previousSlide );
-			slideContent.startEmbeddedContent( currentSlide );
-		}
-
-		// Announce the current slide contents to screen readers
-		// Use animation frame to prevent getComputedStyle in getStatusText
-		// from triggering layout mid-frame
-		requestAnimationFrame( () => {
-			announceStatus( getStatusText( currentSlide ) );
-		});
-
-		progress.update();
-		controls.update();
-		notes.update();
-		backgrounds.update();
-		backgrounds.updateParallax();
-		slideNumber.update();
-		fragments.update();
-
-		// Update the URL hash
-		location.writeURL();
-
-		cueAutoSlide();
-
-		// Auto-animation
-		if( autoAnimateTransition ) {
-
-			setTimeout( () => {
-				dom.slides.classList.remove( 'disable-slide-transitions' );
-			}, 0 );
-
-			if( config.autoAnimate ) {
-				// Run the auto-animation between our slides
-				autoAnimate.run( previousSlide, currentSlide );
-			}
-
-		}
-
-	}
-
-	/**
-	 * Syncs the presentation with the current DOM. Useful
-	 * when new slides or control elements are added or when
-	 * the configuration has changed.
-	 */
-	function sync() {
-
-		// Subscribe to input
-		removeEventListeners();
-		addEventListeners();
-
-		// Force a layout to make sure the current config is accounted for
-		layout();
-
-		// Reflect the current autoSlide value
-		autoSlide = config.autoSlide;
-
-		// Start auto-sliding if it's enabled
-		cueAutoSlide();
-
-		// Re-create all slide backgrounds
-		backgrounds.create();
-
-		// Write the current hash to the URL
-		location.writeURL();
-
-		if( config.sortFragmentsOnSync === true ) {
-			fragments.sortAll();
-		}
-
-		controls.update();
-		progress.update();
-
-		updateSlidesVisibility();
-
-		notes.update();
-		notes.updateVisibility();
-		backgrounds.update( true );
-		slideNumber.update();
-		slideContent.formatEmbeddedContent();
-
-		// Start or stop embedded content depending on global config
-		if( config.autoPlayMedia === false ) {
-			slideContent.stopEmbeddedContent( currentSlide, { unloadIframes: false } );
-		}
-		else {
-			slideContent.startEmbeddedContent( currentSlide );
-		}
-
-		if( overview.isActive() ) {
-			overview.layout();
-		}
-
-	}
-
-	/**
-	 * Updates reveal.js to keep in sync with new slide attributes. For
-	 * example, if you add a new `data-background-image` you can call
-	 * this to have reveal.js render the new background image.
-	 *
-	 * Similar to #sync() but more efficient when you only need to
-	 * refresh a specific slide.
-	 *
-	 * @param {HTMLElement} slide
-	 */
-	function syncSlide( slide = currentSlide ) {
-
-		backgrounds.sync( slide );
-		fragments.sync( slide );
-
-		slideContent.load( slide );
-
-		backgrounds.update();
-		notes.update();
-
-	}
-
-	/**
-	 * Resets all vertical slides so that only the first
-	 * is visible.
-	 */
-	function resetVerticalSlides() {
-
-		getHorizontalSlides().forEach( horizontalSlide => {
-
-			Util.queryAll( horizontalSlide, 'section' ).forEach( ( verticalSlide, y ) => {
-
-				if( y > 0 ) {
-					verticalSlide.classList.remove( 'present' );
-					verticalSlide.classList.remove( 'past' );
-					verticalSlide.classList.add( 'future' );
-					verticalSlide.setAttribute( 'aria-hidden', 'true' );
-				}
-
-			} );
-
-		} );
-
-	}
-
-	/**
-	 * Randomly shuffles all slides in the deck.
-	 */
-	function shuffle( slides = getHorizontalSlides() ) {
-
-		slides.forEach( ( slide, i ) => {
-
-			// Insert the slide next to a randomly picked sibling slide
-			// slide. This may cause the slide to insert before itself,
-			// but that's not an issue.
-			let beforeSlide = slides[ Math.floor( Math.random() * slides.length ) ];
-			if( beforeSlide.parentNode === slide.parentNode ) {
-				slide.parentNode.insertBefore( slide, beforeSlide );
-			}
-
-			// Randomize the order of vertical slides (if there are any)
-			let verticalSlides = slide.querySelectorAll( 'section' );
-			if( verticalSlides.length ) {
-				shuffle( verticalSlides );
-			}
-
-		} );
-
-	}
-
-	/**
-	 * Updates one dimension of slides by showing the slide
-	 * with the specified index.
-	 *
-	 * @param {string} selector A CSS selector that will fetch
-	 * the group of slides we are working with
-	 * @param {number} index The index of the slide that should be
-	 * shown
-	 *
-	 * @return {number} The index of the slide that is now shown,
-	 * might differ from the passed in index if it was out of
-	 * bounds.
-	 */
-	function updateSlides( selector, index ) {
-
-		// Select all slides and convert the NodeList result to
-		// an array
-		let slides = Util.queryAll( dom.wrapper, selector ),
-			slidesLength = slides.length;
-
-		let printMode = print.isPrintingPDF();
-		let loopedForwards = false;
-		let loopedBackwards = false;
-
-		if( slidesLength ) {
-
-			// Should the index loop?
-			if( config.loop ) {
-				if( index >= slidesLength ) loopedForwards = true;
-
-				index %= slidesLength;
-
-				if( index < 0 ) {
-					index = slidesLength + index;
-					loopedBackwards = true;
-				}
-			}
-
-			// Enforce max and minimum index bounds
-			index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
-
-			for( let i = 0; i < slidesLength; i++ ) {
-				let element = slides[i];
-
-				let reverse = config.rtl && !isVerticalSlide( element );
-
-				// Avoid .remove() with multiple args for IE11 support
-				element.classList.remove( 'past' );
-				element.classList.remove( 'present' );
-				element.classList.remove( 'future' );
-
-				// http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
-				element.setAttribute( 'hidden', '' );
-				element.setAttribute( 'aria-hidden', 'true' );
-
-				// If this element contains vertical slides
-				if( element.querySelector( 'section' ) ) {
-					element.classList.add( 'stack' );
-				}
-
-				// If we're printing static slides, all slides are "present"
-				if( printMode ) {
-					element.classList.add( 'present' );
-					continue;
-				}
-
-				if( i < index ) {
-					// Any element previous to index is given the 'past' class
-					element.classList.add( reverse ? 'future' : 'past' );
-
-					if( config.fragments ) {
-						// Show all fragments in prior slides
-						showFragmentsIn( element );
-					}
-				}
-				else if( i > index ) {
-					// Any element subsequent to index is given the 'future' class
-					element.classList.add( reverse ? 'past' : 'future' );
-
-					if( config.fragments ) {
-						// Hide all fragments in future slides
-						hideFragmentsIn( element );
-					}
-				}
-				// Update the visibility of fragments when a presentation loops
-				// in either direction
-				else if( i === index && config.fragments ) {
-					if( loopedForwards ) {
-						hideFragmentsIn( element );
-					}
-					else if( loopedBackwards ) {
-						showFragmentsIn( element );
-					}
-				}
-			}
-
-			let slide = slides[index];
-			let wasPresent = slide.classList.contains( 'present' );
-
-			// Mark the current slide as present
-			slide.classList.add( 'present' );
-			slide.removeAttribute( 'hidden' );
-			slide.removeAttribute( 'aria-hidden' );
-
-			if( !wasPresent ) {
-				// Dispatch an event indicating the slide is now visible
-				dispatchEvent({
-					target: slide,
-					type: 'visible',
-					bubbles: false
-				});
-			}
-
-			// If this slide has a state associated with it, add it
-			// onto the current state of the deck
-			let slideState = slide.getAttribute( 'data-state' );
-			if( slideState ) {
-				state = state.concat( slideState.split( ' ' ) );
-			}
-
-		}
-		else {
-			// Since there are no slides we can't be anywhere beyond the
-			// zeroth index
-			index = 0;
-		}
-
-		return index;
-
-	}
-
-	/**
-	 * Shows all fragment elements within the given contaienr.
-	 */
-	function showFragmentsIn( container ) {
-
-		Util.queryAll( container, '.fragment' ).forEach( fragment => {
-			fragment.classList.add( 'visible' );
-			fragment.classList.remove( 'current-fragment' );
-		} );
-
-	}
-
-	/**
-	 * Hides all fragment elements within the given contaienr.
-	 */
-	function hideFragmentsIn( container ) {
-
-		Util.queryAll( container, '.fragment.visible' ).forEach( fragment => {
-			fragment.classList.remove( 'visible', 'current-fragment' );
-		} );
-
-	}
-
-	/**
-	 * Optimization method; hide all slides that are far away
-	 * from the present slide.
-	 */
-	function updateSlidesVisibility() {
-
-		// Select all slides and convert the NodeList result to
-		// an array
-		let horizontalSlides = getHorizontalSlides(),
-			horizontalSlidesLength = horizontalSlides.length,
-			distanceX,
-			distanceY;
-
-		if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
-
-			// The number of steps away from the present slide that will
-			// be visible
-			let viewDistance = overview.isActive() ? 10 : config.viewDistance;
-
-			// Shorten the view distance on devices that typically have
-			// less resources
-			if( Device.isMobile ) {
-				viewDistance = overview.isActive() ? 6 : config.mobileViewDistance;
-			}
-
-			// All slides need to be visible when exporting to PDF
-			if( print.isPrintingPDF() ) {
-				viewDistance = Number.MAX_VALUE;
-			}
-
-			for( let x = 0; x < horizontalSlidesLength; x++ ) {
-				let horizontalSlide = horizontalSlides[x];
-
-				let verticalSlides = Util.queryAll( horizontalSlide, 'section' ),
-					verticalSlidesLength = verticalSlides.length;
-
-				// Determine how far away this slide is from the present
-				distanceX = Math.abs( ( indexh || 0 ) - x ) || 0;
-
-				// If the presentation is looped, distance should measure
-				// 1 between the first and last slides
-				if( config.loop ) {
-					distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
-				}
-
-				// Show the horizontal slide if it's within the view distance
-				if( distanceX < viewDistance ) {
-					slideContent.load( horizontalSlide );
-				}
-				else {
-					slideContent.unload( horizontalSlide );
-				}
-
-				if( verticalSlidesLength ) {
-
-					let oy = getPreviousVerticalIndex( horizontalSlide );
-
-					for( let y = 0; y < verticalSlidesLength; y++ ) {
-						let verticalSlide = verticalSlides[y];
-
-						distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
-
-						if( distanceX + distanceY < viewDistance ) {
-							slideContent.load( verticalSlide );
-						}
-						else {
-							slideContent.unload( verticalSlide );
-						}
-					}
-
-				}
-			}
-
-			// Flag if there are ANY vertical slides, anywhere in the deck
-			if( hasVerticalSlides() ) {
-				dom.wrapper.classList.add( 'has-vertical-slides' );
-			}
-			else {
-				dom.wrapper.classList.remove( 'has-vertical-slides' );
-			}
-
-			// Flag if there are ANY horizontal slides, anywhere in the deck
-			if( hasHorizontalSlides() ) {
-				dom.wrapper.classList.add( 'has-horizontal-slides' );
-			}
-			else {
-				dom.wrapper.classList.remove( 'has-horizontal-slides' );
-			}
-
-		}
-
-	}
-
-	/**
-	 * Determine what available routes there are for navigation.
-	 *
-	 * @return {{left: boolean, right: boolean, up: boolean, down: boolean}}
-	 */
-	function availableRoutes({ includeFragments = false } = {}) {
-
-		let horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
-			verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
-
-		let routes = {
-			left: indexh > 0,
-			right: indexh < horizontalSlides.length - 1,
-			up: indexv > 0,
-			down: indexv < verticalSlides.length - 1
-		};
-
-		// Looped presentations can always be navigated as long as
-		// there are slides available
-		if( config.loop ) {
-			if( horizontalSlides.length > 1 ) {
-				routes.left = true;
-				routes.right = true;
-			}
-
-			if( verticalSlides.length > 1 ) {
-				routes.up = true;
-				routes.down = true;
-			}
-		}
-
-		if ( horizontalSlides.length > 1 && config.navigationMode === 'linear' ) {
-			routes.right = routes.right || routes.down;
-			routes.left = routes.left || routes.up;
-		}
-
-		// If includeFragments is set, a route will be considered
-		// available if either a slid OR fragment is available in
-		// the given direction
-		if( includeFragments === true ) {
-			let fragmentRoutes = fragments.availableRoutes();
-			routes.left = routes.left || fragmentRoutes.prev;
-			routes.up = routes.up || fragmentRoutes.prev;
-			routes.down = routes.down || fragmentRoutes.next;
-			routes.right = routes.right || fragmentRoutes.next;
-		}
-
-		// Reverse horizontal controls for rtl
-		if( config.rtl ) {
-			let left = routes.left;
-			routes.left = routes.right;
-			routes.right = left;
-		}
-
-		return routes;
-
-	}
-
-	/**
-	 * Returns the number of past slides. This can be used as a global
-	 * flattened index for slides.
-	 *
-	 * @param {HTMLElement} [slide=currentSlide] The slide we're counting before
-	 *
-	 * @return {number} Past slide count
-	 */
-	function getSlidePastCount( slide = currentSlide ) {
-
-		let horizontalSlides = getHorizontalSlides();
-
-		// The number of past slides
-		let pastCount = 0;
-
-		// Step through all slides and count the past ones
-		mainLoop: for( let i = 0; i < horizontalSlides.length; i++ ) {
-
-			let horizontalSlide = horizontalSlides[i];
-			let verticalSlides = horizontalSlide.querySelectorAll( 'section' );
-
-			for( let j = 0; j < verticalSlides.length; j++ ) {
-
-				// Stop as soon as we arrive at the present
-				if( verticalSlides[j] === slide ) {
-					break mainLoop;
-				}
-
-				// Don't count slides with the "uncounted" class
-				if( verticalSlides[j].dataset.visibility !== 'uncounted' ) {
-					pastCount++;
-				}
-
-			}
-
-			// Stop as soon as we arrive at the present
-			if( horizontalSlide === slide ) {
-				break;
-			}
-
-			// Don't count the wrapping section for vertical slides and
-			// slides marked as uncounted
-			if( horizontalSlide.classList.contains( 'stack' ) === false && horizontalSlide.dataset.visibility !== 'uncounted' ) {
-				pastCount++;
-			}
-
-		}
-
-		return pastCount;
-
-	}
-
-	/**
-	 * Returns a value ranging from 0-1 that represents
-	 * how far into the presentation we have navigated.
-	 *
-	 * @return {number}
-	 */
-	function getProgress() {
-
-		// The number of past and total slides
-		let totalCount = getTotalSlides();
-		let pastCount = getSlidePastCount();
-
-		if( currentSlide ) {
-
-			let allFragments = currentSlide.querySelectorAll( '.fragment' );
-
-			// If there are fragments in the current slide those should be
-			// accounted for in the progress.
-			if( allFragments.length > 0 ) {
-				let visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
-
-				// This value represents how big a portion of the slide progress
-				// that is made up by its fragments (0-1)
-				let fragmentWeight = 0.9;
-
-				// Add fragment progress to the past slide count
-				pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
-			}
-
-		}
-
-		return Math.min( pastCount / ( totalCount - 1 ), 1 );
-
-	}
-
-	/**
-	 * Retrieves the h/v location and fragment of the current,
-	 * or specified, slide.
-	 *
-	 * @param {HTMLElement} [slide] If specified, the returned
-	 * index will be for this slide rather than the currently
-	 * active one
-	 *
-	 * @return {{h: number, v: number, f: number}}
-	 */
-	function getIndices( slide ) {
-
-		// By default, return the current indices
-		let h = indexh,
-			v = indexv,
-			f;
-
-		// If a slide is specified, return the indices of that slide
-		if( slide ) {
-			let isVertical = isVerticalSlide( slide );
-			let slideh = isVertical ? slide.parentNode : slide;
-
-			// Select all horizontal slides
-			let horizontalSlides = getHorizontalSlides();
-
-			// Now that we know which the horizontal slide is, get its index
-			h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
-
-			// Assume we're not vertical
-			v = undefined;
-
-			// If this is a vertical slide, grab the vertical index
-			if( isVertical ) {
-				v = Math.max( Util.queryAll( slide.parentNode, 'section' ).indexOf( slide ), 0 );
-			}
-		}
-
-		if( !slide && currentSlide ) {
-			let hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
-			if( hasFragments ) {
-				let currentFragment = currentSlide.querySelector( '.current-fragment' );
-				if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
-					f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
-				}
-				else {
-					f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
-				}
-			}
-		}
-
-		return { h, v, f };
-
-	}
-
-	/**
-	 * Retrieves all slides in this presentation.
-	 */
-	function getSlides() {
-
-		return Util.queryAll( dom.wrapper, SLIDES_SELECTOR + ':not(.stack):not([data-visibility="uncounted"])' );
-
-	}
-
-	/**
-	 * Returns a list of all horizontal slides in the deck. Each
-	 * vertical stack is included as one horizontal slide in the
-	 * resulting array.
-	 */
-	function getHorizontalSlides() {
-
-		return Util.queryAll( dom.wrapper, HORIZONTAL_SLIDES_SELECTOR );
-
-	}
-
-	/**
-	 * Returns all vertical slides that exist within this deck.
-	 */
-	function getVerticalSlides() {
-
-		return Util.queryAll( dom.wrapper, '.slides>section>section' );
-
-	}
-
-	/**
-	 * Returns all vertical stacks (each stack can contain multiple slides).
-	 */
-	function getVerticalStacks() {
-
-		return Util.queryAll( dom.wrapper, HORIZONTAL_SLIDES_SELECTOR + '.stack');
-
-	}
-
-	/**
-	 * Returns true if there are at least two horizontal slides.
-	 */
-	function hasHorizontalSlides() {
-
-		return getHorizontalSlides().length > 1;
-	}
-
-	/**
-	 * Returns true if there are at least two vertical slides.
-	 */
-	function hasVerticalSlides() {
-
-		return getVerticalSlides().length > 1;
-
-	}
-
-	/**
-	 * Returns an array of objects where each object represents the
-	 * attributes on its respective slide.
-	 */
-	function getSlidesAttributes() {
-
-		return getSlides().map( slide => {
-
-			let attributes = {};
-			for( let i = 0; i < slide.attributes.length; i++ ) {
-				let attribute = slide.attributes[ i ];
-				attributes[ attribute.name ] = attribute.value;
-			}
-			return attributes;
-
-		} );
-
-	}
-
-	/**
-	 * Retrieves the total number of slides in this presentation.
-	 *
-	 * @return {number}
-	 */
-	function getTotalSlides() {
-
-		return getSlides().length;
-
-	}
-
-	/**
-	 * Returns the slide element matching the specified index.
-	 *
-	 * @return {HTMLElement}
-	 */
-	function getSlide( x, y ) {
-
-		let horizontalSlide = getHorizontalSlides()[ x ];
-		let verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
-
-		if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
-			return verticalSlides ? verticalSlides[ y ] : undefined;
-		}
-
-		return horizontalSlide;
-
-	}
-
-	/**
-	 * Returns the background element for the given slide.
-	 * All slides, even the ones with no background properties
-	 * defined, have a background element so as long as the
-	 * index is valid an element will be returned.
-	 *
-	 * @param {mixed} x Horizontal background index OR a slide
-	 * HTML element
-	 * @param {number} y Vertical background index
-	 * @return {(HTMLElement[]|*)}
-	 */
-	function getSlideBackground( x, y ) {
-
-		let slide = typeof x === 'number' ? getSlide( x, y ) : x;
-		if( slide ) {
-			return slide.slideBackgroundElement;
-		}
-
-		return undefined;
-
-	}
-
-	/**
-	 * Retrieves the current state of the presentation as
-	 * an object. This state can then be restored at any
-	 * time.
-	 *
-	 * @return {{indexh: number, indexv: number, indexf: number, paused: boolean, overview: boolean}}
-	 */
-	function getState() {
-
-		let indices = getIndices();
-
-		return {
-			indexh: indices.h,
-			indexv: indices.v,
-			indexf: indices.f,
-			paused: isPaused(),
-			overview: overview.isActive()
-		};
-
-	}
-
-	/**
-	 * Restores the presentation to the given state.
-	 *
-	 * @param {object} state As generated by getState()
-	 * @see {@link getState} generates the parameter `state`
-	 */
-	function setState( state ) {
-
-		if( typeof state === 'object' ) {
-			slide( Util.deserialize( state.indexh ), Util.deserialize( state.indexv ), Util.deserialize( state.indexf ) );
-
-			let pausedFlag = Util.deserialize( state.paused ),
-				overviewFlag = Util.deserialize( state.overview );
-
-			if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
-				togglePause( pausedFlag );
-			}
-
-			if( typeof overviewFlag === 'boolean' && overviewFlag !== overview.isActive() ) {
-				overview.toggle( overviewFlag );
-			}
-		}
-
-	}
-
-	/**
-	 * Cues a new automated slide if enabled in the config.
-	 */
-	function cueAutoSlide() {
-
-		cancelAutoSlide();
-
-		if( currentSlide && config.autoSlide !== false ) {
-
-			let fragment = currentSlide.querySelector( '.current-fragment' );
-
-			let fragmentAutoSlide = fragment ? fragment.getAttribute( 'data-autoslide' ) : null;
-			let parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
-			let slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
-
-			// Pick value in the following priority order:
-			// 1. Current fragment's data-autoslide
-			// 2. Current slide's data-autoslide
-			// 3. Parent slide's data-autoslide
-			// 4. Global autoSlide setting
-			if( fragmentAutoSlide ) {
-				autoSlide = parseInt( fragmentAutoSlide, 10 );
-			}
-			else if( slideAutoSlide ) {
-				autoSlide = parseInt( slideAutoSlide, 10 );
-			}
-			else if( parentAutoSlide ) {
-				autoSlide = parseInt( parentAutoSlide, 10 );
-			}
-			else {
-				autoSlide = config.autoSlide;
-
-				// If there are media elements with data-autoplay,
-				// automatically set the autoSlide duration to the
-				// length of that media. Not applicable if the slide
-				// is divided up into fragments.
-				// playbackRate is accounted for in the duration.
-				if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
-					Util.queryAll( currentSlide, 'video, audio' ).forEach( el => {
-						if( el.hasAttribute( 'data-autoplay' ) ) {
-							if( autoSlide && (el.duration * 1000 / el.playbackRate ) > autoSlide ) {
-								autoSlide = ( el.duration * 1000 / el.playbackRate ) + 1000;
-							}
-						}
-					} );
-				}
-			}
-
-			// Cue the next auto-slide if:
-			// - There is an autoSlide value
-			// - Auto-sliding isn't paused by the user
-			// - The presentation isn't paused
-			// - The overview isn't active
-			// - The presentation isn't over
-			if( autoSlide && !autoSlidePaused && !isPaused() && !overview.isActive() && ( !isLastSlide() || fragments.availableRoutes().next || config.loop === true ) ) {
-				autoSlideTimeout = setTimeout( () => {
-					if( typeof config.autoSlideMethod === 'function' ) {
-						config.autoSlideMethod()
-					}
-					else {
-						navigateNext();
-					}
-					cueAutoSlide();
-				}, autoSlide );
-				autoSlideStartTime = Date.now();
-			}
-
-			if( autoSlidePlayer ) {
-				autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );
-			}
-
-		}
-
-	}
-
-	/**
-	 * Cancels any ongoing request to auto-slide.
-	 */
-	function cancelAutoSlide() {
-
-		clearTimeout( autoSlideTimeout );
-		autoSlideTimeout = -1;
-
-	}
-
-	function pauseAutoSlide() {
-
-		if( autoSlide && !autoSlidePaused ) {
-			autoSlidePaused = true;
-			dispatchEvent({ type: 'autoslidepaused' });
-			clearTimeout( autoSlideTimeout );
-
-			if( autoSlidePlayer ) {
-				autoSlidePlayer.setPlaying( false );
-			}
-		}
-
-	}
-
-	function resumeAutoSlide() {
-
-		if( autoSlide && autoSlidePaused ) {
-			autoSlidePaused = false;
-			dispatchEvent({ type: 'autoslideresumed' });
-			cueAutoSlide();
-		}
-
-	}
-
-	function navigateLeft({skipFragments=false}={}) {
-
-		navigationHistory.hasNavigatedHorizontally = true;
-
-		// Reverse for RTL
-		if( config.rtl ) {
-			if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().left ) {
-				slide( indexh + 1, config.navigationMode === 'grid' ? indexv : undefined );
-			}
-		}
-		// Normal navigation
-		else if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().left ) {
-			slide( indexh - 1, config.navigationMode === 'grid' ? indexv : undefined );
-		}
-
-	}
-
-	function navigateRight({skipFragments=false}={}) {
-
-		navigationHistory.hasNavigatedHorizontally = true;
-
-		// Reverse for RTL
-		if( config.rtl ) {
-			if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().right ) {
-				slide( indexh - 1, config.navigationMode === 'grid' ? indexv : undefined );
-			}
-		}
-		// Normal navigation
-		else if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().right ) {
-			slide( indexh + 1, config.navigationMode === 'grid' ? indexv : undefined );
-		}
-
-	}
-
-	function navigateUp({skipFragments=false}={}) {
-
-		// Prioritize hiding fragments
-		if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().up ) {
-			slide( indexh, indexv - 1 );
-		}
-
-	}
-
-	function navigateDown({skipFragments=false}={}) {
-
-		navigationHistory.hasNavigatedVertically = true;
-
-		// Prioritize revealing fragments
-		if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().down ) {
-			slide( indexh, indexv + 1 );
-		}
-
-	}
-
-	/**
-	 * Navigates backwards, prioritized in the following order:
-	 * 1) Previous fragment
-	 * 2) Previous vertical slide
-	 * 3) Previous horizontal slide
-	 */
-	function navigatePrev({skipFragments=false}={}) {
-
-		// Prioritize revealing fragments
-		if( skipFragments || fragments.prev() === false ) {
-			if( availableRoutes().up ) {
-				navigateUp({skipFragments});
-			}
-			else {
-				// Fetch the previous horizontal slide, if there is one
-				let previousSlide;
-
-				if( config.rtl ) {
-					previousSlide = Util.queryAll( dom.wrapper, HORIZONTAL_SLIDES_SELECTOR + '.future' ).pop();
-				}
-				else {
-					previousSlide = Util.queryAll( dom.wrapper, HORIZONTAL_SLIDES_SELECTOR + '.past' ).pop();
-				}
-
-				// When going backwards and arriving on a stack we start
-				// at the bottom of the stack
-				if( previousSlide && previousSlide.classList.contains( 'stack' ) ) {
-					let v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
-					let h = indexh - 1;
-					slide( h, v );
-				}
-				else {
-					navigateLeft({skipFragments});
-				}
-			}
-		}
-
-	}
-
-	/**
-	 * The reverse of #navigatePrev().
-	 */
-	function navigateNext({skipFragments=false}={}) {
-
-		navigationHistory.hasNavigatedHorizontally = true;
-		navigationHistory.hasNavigatedVertically = true;
-
-		// Prioritize revealing fragments
-		if( skipFragments || fragments.next() === false ) {
-
-			let routes = availableRoutes();
-
-			// When looping is enabled `routes.down` is always available
-			// so we need a separate check for when we've reached the
-			// end of a stack and should move horizontally
-			if( routes.down && routes.right && config.loop && isLastVerticalSlide() ) {
-				routes.down = false;
-			}
-
-			if( routes.down ) {
-				navigateDown({skipFragments});
-			}
-			else if( config.rtl ) {
-				navigateLeft({skipFragments});
-			}
-			else {
-				navigateRight({skipFragments});
-			}
-		}
-
-	}
-
-
-	// --------------------------------------------------------------------//
-	// ----------------------------- EVENTS -------------------------------//
-	// --------------------------------------------------------------------//
-
-	/**
-	 * Called by all event handlers that are based on user
-	 * input.
-	 *
-	 * @param {object} [event]
-	 */
-	function onUserInput( event ) {
-
-		if( config.autoSlideStoppable ) {
-			pauseAutoSlide();
-		}
-
-	}
-
-	/**
-	* Listener for post message events posted to this window.
-	*/
-	function onPostMessage( event ) {
-
-		let data = event.data;
-
-		// Make sure we're dealing with JSON
-		if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
-			data = JSON.parse( data );
-
-			// Check if the requested method can be found
-			if( data.method && typeof Reveal[data.method] === 'function' ) {
-
-				if( POST_MESSAGE_METHOD_BLACKLIST.test( data.method ) === false ) {
-
-					const result = Reveal[data.method].apply( Reveal, data.args );
-
-					// Dispatch a postMessage event with the returned value from
-					// our method invocation for getter functions
-					dispatchPostMessage( 'callback', { method: data.method, result: result } );
-
-				}
-				else {
-					console.warn( 'reveal.js: "'+ data.method +'" is is blacklisted from the postMessage API' );
-				}
-
-			}
-		}
-
-	}
-
-	/**
-	 * Event listener for transition end on the current slide.
-	 *
-	 * @param {object} [event]
-	 */
-	function onTransitionEnd( event ) {
-
-		if( transition === 'running' && /section/gi.test( event.target.nodeName ) ) {
-			transition = 'idle';
-			dispatchEvent({
-				type: 'slidetransitionend',
-				data: { indexh, indexv, previousSlide, currentSlide }
-			});
-		}
-
-	}
-
-	/**
-	 * A global listener for all click events inside of the
-	 * .slides container.
-	 *
-	 * @param {object} [event]
-	 */
-	function onSlidesClicked( event ) {
-
-		const anchor = Util.closest( event.target, 'a[href^="#"]' );
-
-		// If a hash link is clicked, we find the target slide
-		// and navigate to it. We previously relied on 'hashchange'
-		// for links like these but that prevented media with
-		// audio tracks from playing in mobile browsers since it
-		// wasn't considered a direct interaction with the document.
-		if( anchor ) {
-			const hash = anchor.getAttribute( 'href' );
-			const indices = location.getIndicesFromHash( hash );
-
-			if( indices ) {
-				Reveal.slide( indices.h, indices.v, indices.f );
-				event.preventDefault();
-			}
-		}
-
-	}
-
-	/**
-	 * Handler for the window level 'resize' event.
-	 *
-	 * @param {object} [event]
-	 */
-	function onWindowResize( event ) {
-
-		layout();
-
-	}
-
-	/**
-	 * Handle for the window level 'visibilitychange' event.
-	 *
-	 * @param {object} [event]
-	 */
-	function onPageVisibilityChange( event ) {
-
-		// If, after clicking a link or similar and we're coming back,
-		// focus the document.body to ensure we can use keyboard shortcuts
-		if( document.hidden === false && document.activeElement !== document.body ) {
-			// Not all elements support .blur() - SVGs among them.
-			if( typeof document.activeElement.blur === 'function' ) {
-				document.activeElement.blur();
-			}
-			document.body.focus();
-		}
-
-	}
-
-	/**
-	 * Handler for the document level 'fullscreenchange' event.
-	 *
-	 * @param {object} [event]
-	 */
-	function onFullscreenChange( event ) {
-
-		let element = document.fullscreenElement || document.webkitFullscreenElement;
-		if( element === dom.wrapper ) {
-			event.stopImmediatePropagation();
-
-			// Timeout to avoid layout shift in Safari
-			setTimeout( () => {
-				Reveal.layout();
-				Reveal.focus.focus(); // focus.focus :'(
-			}, 1 );
-		}
-
-	}
-
-	/**
-	 * Handles clicks on links that are set to preview in the
-	 * iframe overlay.
-	 *
-	 * @param {object} event
-	 */
-	function onPreviewLinkClicked( event ) {
-
-		if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
-			let url = event.currentTarget.getAttribute( 'href' );
-			if( url ) {
-				showPreview( url );
-				event.preventDefault();
-			}
-		}
-
-	}
-
-	/**
-	 * Handles click on the auto-sliding controls element.
-	 *
-	 * @param {object} [event]
-	 */
-	function onAutoSlidePlayerClick( event ) {
-
-		// Replay
-		if( isLastSlide() && config.loop === false ) {
-			slide( 0, 0 );
-			resumeAutoSlide();
-		}
-		// Resume
-		else if( autoSlidePaused ) {
-			resumeAutoSlide();
-		}
-		// Pause
-		else {
-			pauseAutoSlide();
-		}
-
-	}
-
-
-	// --------------------------------------------------------------------//
-	// ------------------------------- API --------------------------------//
-	// --------------------------------------------------------------------//
-
-	// The public reveal.js API
-	const API = {
-		VERSION,
-
-		initialize,
-		configure,
-		destroy,
-
-		sync,
-		syncSlide,
-		syncFragments: fragments.sync.bind( fragments ),
-
-		// Navigation methods
-		slide,
-		left: navigateLeft,
-		right: navigateRight,
-		up: navigateUp,
-		down: navigateDown,
-		prev: navigatePrev,
-		next: navigateNext,
-
-		// Navigation aliases
-		navigateLeft, navigateRight, navigateUp, navigateDown, navigatePrev, navigateNext,
-
-		// Fragment methods
-		navigateFragment: fragments.goto.bind( fragments ),
-		prevFragment: fragments.prev.bind( fragments ),
-		nextFragment: fragments.next.bind( fragments ),
-
-		// Event binding
-		on,
-		off,
-
-		// Legacy event binding methods left in for backwards compatibility
-		addEventListener: on,
-		removeEventListener: off,
-
-		// Forces an update in slide layout
-		layout,
-
-		// Randomizes the order of slides
-		shuffle,
-
-		// Returns an object with the available routes as booleans (left/right/top/bottom)
-		availableRoutes,
-
-		// Returns an object with the available fragments as booleans (prev/next)
-		availableFragments: fragments.availableRoutes.bind( fragments ),
-
-		// Toggles a help overlay with keyboard shortcuts
-		toggleHelp,
-
-		// Toggles the overview mode on/off
-		toggleOverview: overview.toggle.bind( overview ),
-
-		// Toggles the "black screen" mode on/off
-		togglePause,
-
-		// Toggles the auto slide mode on/off
-		toggleAutoSlide,
-
-		// Toggles visibility of the jump-to-slide UI
-		toggleJumpToSlide,
-
-		// Slide navigation checks
-		isFirstSlide,
-		isLastSlide,
-		isLastVerticalSlide,
-		isVerticalSlide,
-
-		// State checks
-		isPaused,
-		isAutoSliding,
-		isSpeakerNotes: notes.isSpeakerNotesWindow.bind( notes ),
-		isOverview: overview.isActive.bind( overview ),
-		isFocused: focus.isFocused.bind( focus ),
-		isPrintingPDF: print.isPrintingPDF.bind( print ),
-
-		// Checks if reveal.js has been loaded and is ready for use
-		isReady: () => ready,
-
-		// Slide preloading
-		loadSlide: slideContent.load.bind( slideContent ),
-		unloadSlide: slideContent.unload.bind( slideContent ),
-
-		// Preview management
-		showPreview,
-		hidePreview: closeOverlay,
-
-		// Adds or removes all internal event listeners
-		addEventListeners,
-		removeEventListeners,
-		dispatchEvent,
-
-		// Facility for persisting and restoring the presentation state
-		getState,
-		setState,
-
-		// Presentation progress on range of 0-1
-		getProgress,
-
-		// Returns the indices of the current, or specified, slide
-		getIndices,
-
-		// Returns an Array of key:value maps of the attributes of each
-		// slide in the deck
-		getSlidesAttributes,
-
-		// Returns the number of slides that we have passed
-		getSlidePastCount,
-
-		// Returns the total number of slides
-		getTotalSlides,
-
-		// Returns the slide element at the specified index
-		getSlide,
-
-		// Returns the previous slide element, may be null
-		getPreviousSlide: () => previousSlide,
-
-		// Returns the current slide element
-		getCurrentSlide: () => currentSlide,
-
-		// Returns the slide background element at the specified index
-		getSlideBackground,
-
-		// Returns the speaker notes string for a slide, or null
-		getSlideNotes: notes.getSlideNotes.bind( notes ),
-
-		// Returns an Array of all slides
-		getSlides,
-
-		// Returns an array with all horizontal/vertical slides in the deck
-		getHorizontalSlides,
-		getVerticalSlides,
-
-		// Checks if the presentation contains two or more horizontal
-		// and vertical slides
-		hasHorizontalSlides,
-		hasVerticalSlides,
-
-		// Checks if the deck has navigated on either axis at least once
-		hasNavigatedHorizontally: () => navigationHistory.hasNavigatedHorizontally,
-		hasNavigatedVertically: () => navigationHistory.hasNavigatedVertically,
-
-		// Adds/removes a custom key binding
-		addKeyBinding: keyboard.addKeyBinding.bind( keyboard ),
-		removeKeyBinding: keyboard.removeKeyBinding.bind( keyboard ),
-
-		// Programmatically triggers a keyboard event
-		triggerKey: keyboard.triggerKey.bind( keyboard ),
-
-		// Registers a new shortcut to include in the help overlay
-		registerKeyboardShortcut: keyboard.registerKeyboardShortcut.bind( keyboard ),
-
-		getComputedSlideSize,
-
-		// Returns the current scale of the presentation content
-		getScale: () => scale,
-
-		// Returns the current configuration object
-		getConfig: () => config,
-
-		// Helper method, retrieves query string as a key:value map
-		getQueryHash: Util.getQueryHash,
-
-		// Returns the path to the current slide as represented in the URL
-		getSlidePath: location.getHash.bind( location ),
-
-		// Returns reveal.js DOM elements
-		getRevealElement: () => revealElement,
-		getSlidesElement: () => dom.slides,
-		getViewportElement: () => dom.viewport,
-		getBackgroundsElement: () => backgrounds.element,
-
-		// API for registering and retrieving plugins
-		registerPlugin: plugins.registerPlugin.bind( plugins ),
-		hasPlugin: plugins.hasPlugin.bind( plugins ),
-		getPlugin: plugins.getPlugin.bind( plugins ),
-		getPlugins: plugins.getRegisteredPlugins.bind( plugins )
-
-	};
-
-	// Our internal API which controllers have access to
-	Util.extend( Reveal, {
-		...API,
-
-		// Methods for announcing content to screen readers
-		announceStatus,
-		getStatusText,
-
-		// Controllers
-		print,
-		focus,
-		progress,
-		controls,
-		location,
-		overview,
-		fragments,
-		slideContent,
-		slideNumber,
-
-		onUserInput,
-		closeOverlay,
-		updateSlidesVisibility,
-		layoutSlideContents,
-		transformSlides,
-		cueAutoSlide,
-		cancelAutoSlide
-	} );
-
-	return API;
-
-};
diff --git a/geschichte/js/utils/color.js b/geschichte/js/utils/color.js
deleted file mode 100644
index edf67c4..0000000
--- a/geschichte/js/utils/color.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Converts various color input formats to an {r:0,g:0,b:0} object.
- *
- * @param {string} color The string representation of a color
- * @example
- * colorToRgb('#000');
- * @example
- * colorToRgb('#000000');
- * @example
- * colorToRgb('rgb(0,0,0)');
- * @example
- * colorToRgb('rgba(0,0,0)');
- *
- * @return {{r: number, g: number, b: number, [a]: number}|null}
- */
-export const colorToRgb = ( color ) => {
-
-	let hex3 = color.match( /^#([0-9a-f]{3})$/i );
-	if( hex3 && hex3[1] ) {
-		hex3 = hex3[1];
-		return {
-			r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
-			g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
-			b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
-		};
-	}
-
-	let hex6 = color.match( /^#([0-9a-f]{6})$/i );
-	if( hex6 && hex6[1] ) {
-		hex6 = hex6[1];
-		return {
-			r: parseInt( hex6.slice( 0, 2 ), 16 ),
-			g: parseInt( hex6.slice( 2, 4 ), 16 ),
-			b: parseInt( hex6.slice( 4, 6 ), 16 )
-		};
-	}
-
-	let rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
-	if( rgb ) {
-		return {
-			r: parseInt( rgb[1], 10 ),
-			g: parseInt( rgb[2], 10 ),
-			b: parseInt( rgb[3], 10 )
-		};
-	}
-
-	let rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
-	if( rgba ) {
-		return {
-			r: parseInt( rgba[1], 10 ),
-			g: parseInt( rgba[2], 10 ),
-			b: parseInt( rgba[3], 10 ),
-			a: parseFloat( rgba[4] )
-		};
-	}
-
-	return null;
-
-}
-
-/**
- * Calculates brightness on a scale of 0-255.
- *
- * @param {string} color See colorToRgb for supported formats.
- * @see {@link colorToRgb}
- */
-export const colorBrightness = ( color ) => {
-
-	if( typeof color === 'string' ) color = colorToRgb( color );
-
-	if( color ) {
-		return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
-	}
-
-	return null;
-
-}
\ No newline at end of file
diff --git a/geschichte/js/utils/constants.js b/geschichte/js/utils/constants.js
deleted file mode 100644
index 43fcb84..0000000
--- a/geschichte/js/utils/constants.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-export const SLIDES_SELECTOR = '.slides section';
-export const HORIZONTAL_SLIDES_SELECTOR = '.slides>section';
-export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section';
-
-// Methods that may not be invoked via the postMessage API
-export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/;
-
-// Regex for retrieving the fragment style from a class attribute
-export const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/;
\ No newline at end of file
diff --git a/geschichte/js/utils/device.js b/geschichte/js/utils/device.js
deleted file mode 100644
index f2bce20..0000000
--- a/geschichte/js/utils/device.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const UA = navigator.userAgent;
-
-export const isMobile = /(iphone|ipod|ipad|android)/gi.test( UA ) ||
-						( navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 ); // iPadOS
-
-export const isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );
-
-export const isAndroid = /android/gi.test( UA );
\ No newline at end of file
diff --git a/geschichte/js/utils/loader.js b/geschichte/js/utils/loader.js
deleted file mode 100644
index 58d39ac..0000000
--- a/geschichte/js/utils/loader.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Loads a JavaScript file from the given URL and executes it.
- *
- * @param {string} url Address of the .js file to load
- * @param {function} callback Method to invoke when the script
- * has loaded and executed
- */
-export const loadScript = ( url, callback ) => {
-
-	const script = document.createElement( 'script' );
-	script.type = 'text/javascript';
-	script.async = false;
-	script.defer = false;
-	script.src = url;
-
-	if( typeof callback === 'function' ) {
-
-		// Success callback
-		script.onload = script.onreadystatechange = event => {
-			if( event.type === 'load' || /loaded|complete/.test( script.readyState ) ) {
-
-				// Kill event listeners
-				script.onload = script.onreadystatechange = script.onerror = null;
-
-				callback();
-
-			}
-		};
-
-		// Error callback
-		script.onerror = err => {
-
-			// Kill event listeners
-			script.onload = script.onreadystatechange = script.onerror = null;
-
-			callback( new Error( 'Failed loading script: ' + script.src + '\n' + err ) );
-
-		};
-
-	}
-
-	// Append the script at the end of <head>
-	const head = document.querySelector( 'head' );
-	head.insertBefore( script, head.lastChild );
-
-}
\ No newline at end of file
diff --git a/geschichte/js/utils/util.js b/geschichte/js/utils/util.js
deleted file mode 100644
index a5515e8..0000000
--- a/geschichte/js/utils/util.js
+++ /dev/null
@@ -1,313 +0,0 @@
-/**
- * Extend object a with the properties of object b.
- * If there's a conflict, object b takes precedence.
- *
- * @param {object} a
- * @param {object} b
- */
-export const extend = ( a, b ) => {
-
-	for( let i in b ) {
-		a[ i ] = b[ i ];
-	}
-
-	return a;
-
-}
-
-/**
- * querySelectorAll but returns an Array.
- */
-export const queryAll = ( el, selector ) => {
-
-	return Array.from( el.querySelectorAll( selector ) );
-
-}
-
-/**
- * classList.toggle() with cross browser support
- */
-export const toggleClass = ( el, className, value ) => {
-	if( value ) {
-		el.classList.add( className );
-	}
-	else {
-		el.classList.remove( className );
-	}
-}
-
-/**
- * Utility for deserializing a value.
- *
- * @param {*} value
- * @return {*}
- */
-export const deserialize = ( value ) => {
-
-	if( typeof value === 'string' ) {
-		if( value === 'null' ) return null;
-		else if( value === 'true' ) return true;
-		else if( value === 'false' ) return false;
-		else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );
-	}
-
-	return value;
-
-}
-
-/**
- * Measures the distance in pixels between point a
- * and point b.
- *
- * @param {object} a point with x/y properties
- * @param {object} b point with x/y properties
- *
- * @return {number}
- */
-export const distanceBetween = ( a, b ) => {
-
-	let dx = a.x - b.x,
-		dy = a.y - b.y;
-
-	return Math.sqrt( dx*dx + dy*dy );
-
-}
-
-/**
- * Applies a CSS transform to the target element.
- *
- * @param {HTMLElement} element
- * @param {string} transform
- */
-export const transformElement = ( element, transform ) => {
-
-	element.style.transform = transform;
-
-}
-
-/**
- * Element.matches with IE support.
- *
- * @param {HTMLElement} target The element to match
- * @param {String} selector The CSS selector to match
- * the element against
- *
- * @return {Boolean}
- */
-export const matches = ( target, selector ) => {
-
-	let matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector;
-
-	return !!( matchesMethod && matchesMethod.call( target, selector ) );
-
-}
-
-/**
- * Find the closest parent that matches the given
- * selector.
- *
- * @param {HTMLElement} target The child element
- * @param {String} selector The CSS selector to match
- * the parents against
- *
- * @return {HTMLElement} The matched parent or null
- * if no matching parent was found
- */
-export const closest = ( target, selector ) => {
-
-	// Native Element.closest
-	if( typeof target.closest === 'function' ) {
-		return target.closest( selector );
-	}
-
-	// Polyfill
-	while( target ) {
-		if( matches( target, selector ) ) {
-			return target;
-		}
-
-		// Keep searching
-		target = target.parentNode;
-	}
-
-	return null;
-
-}
-
-/**
- * Handling the fullscreen functionality via the fullscreen API
- *
- * @see http://fullscreen.spec.whatwg.org/
- * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
- */
-export const enterFullscreen = element => {
-
-	element = element || document.documentElement;
-
-	// Check which implementation is available
-	let requestMethod = element.requestFullscreen ||
-						element.webkitRequestFullscreen ||
-						element.webkitRequestFullScreen ||
-						element.mozRequestFullScreen ||
-						element.msRequestFullscreen;
-
-	if( requestMethod ) {
-		requestMethod.apply( element );
-	}
-
-}
-
-/**
- * Creates an HTML element and returns a reference to it.
- * If the element already exists the existing instance will
- * be returned.
- *
- * @param {HTMLElement} container
- * @param {string} tagname
- * @param {string} classname
- * @param {string} innerHTML
- *
- * @return {HTMLElement}
- */
-export const createSingletonNode = ( container, tagname, classname, innerHTML='' ) => {
-
-	// Find all nodes matching the description
-	let nodes = container.querySelectorAll( '.' + classname );
-
-	// Check all matches to find one which is a direct child of
-	// the specified container
-	for( let i = 0; i < nodes.length; i++ ) {
-		let testNode = nodes[i];
-		if( testNode.parentNode === container ) {
-			return testNode;
-		}
-	}
-
-	// If no node was found, create it now
-	let node = document.createElement( tagname );
-	node.className = classname;
-	node.innerHTML = innerHTML;
-	container.appendChild( node );
-
-	return node;
-
-}
-
-/**
- * Injects the given CSS styles into the DOM.
- *
- * @param {string} value
- */
-export const createStyleSheet = ( value ) => {
-
-	let tag = document.createElement( 'style' );
-	tag.type = 'text/css';
-
-	if( value && value.length > 0 ) {
-		if( tag.styleSheet ) {
-			tag.styleSheet.cssText = value;
-		}
-		else {
-			tag.appendChild( document.createTextNode( value ) );
-		}
-	}
-
-	document.head.appendChild( tag );
-
-	return tag;
-
-}
-
-/**
- * Returns a key:value hash of all query params.
- */
-export const getQueryHash = () => {
-
-	let query = {};
-
-	location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, a => {
-		query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
-	} );
-
-	// Basic deserialization
-	for( let i in query ) {
-		let value = query[ i ];
-
-		query[ i ] = deserialize( unescape( value ) );
-	}
-
-	// Do not accept new dependencies via query config to avoid
-	// the potential of malicious script injection
-	if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
-
-	return query;
-
-}
-
-/**
- * Returns the remaining height within the parent of the
- * target element.
- *
- * remaining height = [ configured parent height ] - [ current parent height ]
- *
- * @param {HTMLElement} element
- * @param {number} [height]
- */
-export const getRemainingHeight = ( element, height = 0 ) => {
-
-	if( element ) {
-		let newHeight, oldHeight = element.style.height;
-
-		// Change the .stretch element height to 0 in order find the height of all
-		// the other elements
-		element.style.height = '0px';
-
-		// In Overview mode, the parent (.slide) height is set of 700px.
-		// Restore it temporarily to its natural height.
-		element.parentNode.style.height = 'auto';
-
-		newHeight = height - element.parentNode.offsetHeight;
-
-		// Restore the old height, just in case
-		element.style.height = oldHeight + 'px';
-
-		// Clear the parent (.slide) height. .removeProperty works in IE9+
-		element.parentNode.style.removeProperty('height');
-
-		return newHeight;
-	}
-
-	return height;
-
-}
-
-const fileExtensionToMimeMap = {
-	'mp4': 'video/mp4',
-	'm4a': 'video/mp4',
-	'ogv': 'video/ogg',
-	'mpeg': 'video/mpeg',
-	'webm': 'video/webm'
-}
-
-/**
- * Guess the MIME type for common file formats.
- */
-export const getMimeTypeFromFile = ( filename='' ) => {
-	return fileExtensionToMimeMap[filename.split('.').pop()]
-}
-
-/**
- * Encodes a string for RFC3986-compliant URL format.
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encoding_for_rfc3986
- *
- * @param {string} url
- */
-export const encodeRFC3986URI = ( url='' ) => {
-	return encodeURI(url)
-	  .replace(/%5B/g, "[")
-	  .replace(/%5D/g, "]")
-	  .replace(
-		/[!'()*]/g,
-		(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
-	  );
-}
\ No newline at end of file