fix previous slide navigation logic (closes #397)
[reveal.js.git] / js / reveal.js
index 8d829f9f56bb461fc666433cc6c43823b961b78f..516d114e83d2c950da5e42c3a0a4f281aeb2593c 100644 (file)
@@ -3,7 +3,7 @@
  * http://lab.hakim.se/reveal-js
  * MIT licensed
  *
- * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
  */
 var Reveal = (function(){
 
@@ -16,6 +16,19 @@ var Reveal = (function(){
 
                // Configurations defaults, can be overridden at initialization time
                config = {
+
+                       // 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.1,
+
+                       // Bounds for smallest/largest possible scale to apply to content
+                       minScale: 0.2,
+                       maxScale: 1.0,
+
                        // Display controls in the bottom right corner
                        controls: true,
 
@@ -31,9 +44,12 @@ var Reveal = (function(){
                        // Enable the slide overview mode
                        overview: true,
 
-                       // Vertical centering of slides
+                       // Vertical centring of slides
                        center: true,
 
+                       // Enables touch navigation on devices with touch input
+                       touch: true,
+
                        // Loop the presentation
                        loop: false,
 
@@ -57,15 +73,17 @@ var Reveal = (function(){
                        // Transition style
                        transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
 
+                       // Transition speed
+                       transitionSpeed: 'default', // default/fast/slow
+
                        // Script dependencies to load
                        dependencies: []
                },
 
-               // Stores if the next slide should be shown automatically
-               // after n milliseconds
-               autoSlide = config.autoSlide,
+               // The current auto-slide duration
+               autoSlide = 0,
 
-               // The horizontal and verical index of the currently active slide
+               // The horizontal and vertical index of the currently active slide
                indexh = 0,
                indexv = 0,
 
@@ -78,6 +96,9 @@ var Reveal = (function(){
                // all current slides.
                state = [],
 
+               // The current scale of the presentation (see width/height config)
+               scale = 1,
+
                // Cached references to DOM elements
                dom = {},
 
@@ -88,6 +109,7 @@ var Reveal = (function(){
                                                                'OPerspective' in document.body.style ||
                                                                'perspective' in document.body.style,
 
+               // Detect support for CSS 2D transforms
                supports2DTransforms =  'WebkitTransform' in document.body.style ||
                                                                'MozTransform' in document.body.style ||
                                                                'msTransform' in document.body.style ||
@@ -103,12 +125,15 @@ var Reveal = (function(){
                // Delays updates to the URL due to a Chrome thumbnailer bug
                writeURLTimeout = 0,
 
-               // A delay used to ativate the overview mode
+               // A delay used to activate the overview mode
                activateOverviewTimeout = 0,
 
-               // A delay used to deativate the overview mode
+               // A delay used to deactivate the overview mode
                deactivateOverviewTimeout = 0,
 
+               // Flags if the interaction event listeners are bound
+               eventsAreBound = false,
+
                // Holds information about the currently ongoing touch input
                touch = {
                        startX: 0,
@@ -123,7 +148,8 @@ var Reveal = (function(){
         * Starts up the presentation if the client is capable.
         */
        function initialize( options ) {
-               if( ( !supports2DTransforms && !supports3DTransforms ) ) {
+
+               if( !supports2DTransforms && !supports3DTransforms ) {
                        document.body.setAttribute( 'class', 'no-transforms' );
 
                        // If the browser doesn't support core features we won't be
@@ -151,13 +177,14 @@ var Reveal = (function(){
         * not found, it is created.
         */
        function setupDOM() {
+
                // Cache references to key DOM elements
                dom.theme = document.querySelector( '#theme' );
                dom.wrapper = document.querySelector( '.reveal' );
                dom.slides = document.querySelector( '.reveal .slides' );
 
                // Progress bar
-               if( !dom.wrapper.querySelector( '.progress' ) && config.progress ) {
+               if( !dom.wrapper.querySelector( '.progress' ) ) {
                        var progressElement = document.createElement( 'div' );
                        progressElement.classList.add( 'progress' );
                        progressElement.innerHTML = '<span></span>';
@@ -165,7 +192,7 @@ var Reveal = (function(){
                }
 
                // Arrow controls
-               if( !dom.wrapper.querySelector( '.controls' ) && config.controls ) {
+               if( !dom.wrapper.querySelector( '.controls' ) ) {
                        var controlsElement = document.createElement( 'aside' );
                        controlsElement.classList.add( 'controls' );
                        controlsElement.innerHTML = '<div class="navigate-left"></div>' +
@@ -204,21 +231,20 @@ var Reveal = (function(){
                        dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
                        dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
                }
+
        }
 
        /**
         * Hides the address bar if we're on a mobile device.
         */
        function hideAddressBar() {
-               if( navigator.userAgent.match( /(iphone|ipod)/i ) ) {
-                       // Give the page some scrollable overflow
-                       document.documentElement.style.overflow = 'scroll';
-                       document.body.style.height = '120%';
 
+               if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
                        // Events that should trigger the address bar to hide
                        window.addEventListener( 'load', removeAddressBar, false );
                        window.addEventListener( 'orientationchange', removeAddressBar, false );
                }
+
        }
 
        /**
@@ -229,6 +255,7 @@ var Reveal = (function(){
         * will load after reveal.js has been started up.
         */
        function load() {
+
                var scripts = [],
                        scriptsAsync = [];
 
@@ -251,7 +278,7 @@ var Reveal = (function(){
                        }
                }
 
-               // Called once synchronous scritps finish loading
+               // Called once synchronous scripts finish loading
                function proceed() {
                        if( scriptsAsync.length ) {
                                // Load asynchronous scripts
@@ -270,6 +297,7 @@ var Reveal = (function(){
                else {
                        proceed();
                }
+
        }
 
        /**
@@ -277,25 +305,16 @@ var Reveal = (function(){
         * to the current URL deeplink if there is one.
         */
        function start() {
+
                // Make sure we've got all the DOM elements we need
                setupDOM();
 
-               // Subscribe to input
-               addEventListeners();
-
                // Updates the presentation to match the current configuration values
                configure();
 
-               // Force an initial layout, will thereafter be invoked as the window
-               // is resized
-               layout();
-
                // Read the initial hash
                readURL();
 
-               // Start auto-sliding if it's enabled
-               cueAutoSlide();
-
                // Notify listeners that the presentation is ready but use a 1ms
                // timeout to ensure it's not fired synchronously after #initialize()
                setTimeout( function() {
@@ -305,44 +324,64 @@ var Reveal = (function(){
                                'currentSlide': currentSlide
                        } );
                }, 1 );
+
        }
 
        /**
         * Applies the configuration settings from the config object.
         */
-       function configure() {
-               if( supports3DTransforms === false ) {
-                       config.transition = 'linear';
-               }
+       function configure( options ) {
 
-               if( config.controls && dom.controls ) {
-                       dom.controls.style.display = 'block';
-               }
+               dom.wrapper.classList.remove( config.transition );
 
-               if( config.progress && dom.progress ) {
-                       dom.progress.style.display = 'block';
+               // New config options may be passed when this method
+               // is invoked through the API after initialization
+               if( typeof options === 'object' ) extend( config, options );
+
+               // Force linear transition based on browser capabilities
+               if( supports3DTransforms === false ) config.transition = 'linear';
+
+               dom.wrapper.classList.add( config.transition );
+
+               dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
+
+               if( dom.controls ) {
+                       dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
                }
 
-               if( config.transition !== 'default' ) {
-                       dom.wrapper.classList.add( config.transition );
+               if( dom.progress ) {
+                       dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none';
                }
 
                if( config.rtl ) {
                        dom.wrapper.classList.add( 'rtl' );
                }
+               else {
+                       dom.wrapper.classList.remove( 'rtl' );
+               }
 
                if( config.center ) {
                        dom.wrapper.classList.add( 'center' );
                }
+               else {
+                       dom.wrapper.classList.remove( 'center' );
+               }
 
                if( config.mouseWheel ) {
                        document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
                        document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
                }
+               else {
+                       document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
+                       document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
+               }
 
                // 3D links
                if( config.rollingLinks ) {
-                       linkify();
+                       enable3DLinks();
+               }
+               else {
+                       disable3DLinks();
                }
 
                // Load the theme in the config, if it's not already loaded
@@ -356,61 +395,112 @@ var Reveal = (function(){
                                dom.theme.setAttribute( 'href', themeURL );
                        }
                }
+
+               postConfigure();
+
+       }
+
+       /**
+        * Updates various parts of the presentatio after the
+        * configuration has changed.
+        */
+       function postConfigure() {
+
+               // 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();
+
        }
 
        /**
         * Binds all event listeners.
         */
        function addEventListeners() {
-               document.addEventListener( 'touchstart', onDocumentTouchStart, false );
-               document.addEventListener( 'touchmove', onDocumentTouchMove, false );
-               document.addEventListener( 'touchend', onDocumentTouchEnd, false );
+
+               eventsAreBound = true;
+
                window.addEventListener( 'hashchange', onWindowHashChange, false );
                window.addEventListener( 'resize', onWindowResize, false );
 
+               if( config.touch ) {
+                       dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
+                       dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
+                       dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
+
+                       // Support pointer-style touch interaction as well
+                       if( window.navigator.msPointerEnabled ) {
+                               dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
+                               dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
+                               dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
+                       }
+               }
+
                if( config.keyboard ) {
                        document.addEventListener( 'keydown', onDocumentKeyDown, false );
                }
 
                if ( config.progress && dom.progress ) {
-                       dom.progress.addEventListener( 'click', preventAndForward( onProgressClick ), false );
+                       dom.progress.addEventListener( 'click', onProgressClicked, false );
                }
 
                if ( config.controls && dom.controls ) {
-                       var actionEvent = 'ontouchstart' in window ? 'touchstart' : 'click';
-                       dom.controlsLeft.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateLeft ), false ); } );
-                       dom.controlsRight.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateRight ), false ); } );
-                       dom.controlsUp.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateUp ), false ); } );
-                       dom.controlsDown.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateDown ), false ); } );
-                       dom.controlsPrev.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigatePrev ), false ); } );
-                       dom.controlsNext.forEach( function( el ) { el.addEventListener( actionEvent, preventAndForward( navigateNext ), false ); } );
+                       [ 'touchstart', 'click' ].forEach( function( eventName ) {
+                               dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
+                               dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
+                               dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
+                               dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
+                               dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
+                               dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
+                       } );
                }
+
        }
 
        /**
         * Unbinds all event listeners.
         */
        function removeEventListeners() {
+
+               eventsAreBound = false;
+
                document.removeEventListener( 'keydown', onDocumentKeyDown, false );
-               document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
-               document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
-               document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
                window.removeEventListener( 'hashchange', onWindowHashChange, false );
                window.removeEventListener( 'resize', onWindowResize, false );
 
+               dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
+               dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
+               dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
+
+               if( window.navigator.msPointerEnabled ) {
+                       dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
+                       dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
+                       dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
+               }
+
                if ( config.progress && dom.progress ) {
-                       dom.progress.removeEventListener( 'click', preventAndForward( onProgressClick ), false );
+                       dom.progress.removeEventListener( 'click', onProgressClicked, false );
                }
 
                if ( config.controls && dom.controls ) {
-                       var actionEvent = 'ontouchstart' in window ? 'touchstart' : 'click';
-                       dom.controlsLeft.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateLeft ), false ); } );
-                       dom.controlsRight.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateRight ), false ); } );
-                       dom.controlsUp.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateUp ), false ); } );
-                       dom.controlsDown.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateDown ), false ); } );
-                       dom.controlsPrev.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigatePrev ), false ); } );
-                       dom.controlsNext.forEach( function( el ) { el.removeEventListener( actionEvent, preventAndForward( navigateNext ), false ); } );
+                       [ 'touchstart', 'click' ].forEach( function( eventName ) {
+                               dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
+                               dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
+                               dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
+                               dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
+                               dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
+                               dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
+                       } );
                }
+
        }
 
        /**
@@ -418,22 +508,20 @@ var Reveal = (function(){
         * If there's a conflict, object b takes precedence.
         */
        function extend( a, b ) {
+
                for( var i in b ) {
                        a[ i ] = b[ i ];
                }
+
        }
 
        /**
         * Converts the target object to an array.
         */
        function toArray( o ) {
+
                return Array.prototype.slice.call( o );
-       }
 
-       function each( targets, method, args ) {
-               targets.forEach( function( el ) {
-                       el[method].apply( el, args );
-               } );
        }
 
        /**
@@ -444,24 +532,12 @@ var Reveal = (function(){
         * @param {Object} b point with x/y properties
         */
        function distanceBetween( a, b ) {
+
                var dx = a.x - b.x,
                        dy = a.y - b.y;
 
                return Math.sqrt( dx*dx + dy*dy );
-       }
 
-       /**
-        * Prevents an events defaults behavior calls the
-        * specified delegate.
-        *
-        * @param {Function} delegate The method to call
-        * after the wrapper has been executed
-        */
-       function preventAndForward( delegate ) {
-               return function( event ) {
-                       event.preventDefault();
-                       delegate.call( null, event );
-               };
        }
 
        /**
@@ -469,9 +545,20 @@ var Reveal = (function(){
         * more vertical space ftw.
         */
        function removeAddressBar() {
+
+               if( window.orientation === 0 ) {
+                       document.documentElement.style.overflow = 'scroll';
+                       document.body.style.height = '120%';
+               }
+               else {
+                       document.documentElement.style.overflow = '';
+                       document.body.style.height = '100%';
+               }
+
                setTimeout( function() {
                        window.scrollTo( 0, 1 );
-               }, 0 );
+               }, 10 );
+
        }
 
        /**
@@ -479,33 +566,88 @@ var Reveal = (function(){
         * reveal DOM element.
         */
        function dispatchEvent( type, properties ) {
+
                var event = document.createEvent( "HTMLEvents", 1, 2 );
                event.initEvent( type, true, true );
                extend( event, properties );
                dom.wrapper.dispatchEvent( event );
+
        }
 
        /**
         * Wrap all links in 3D goodness.
         */
-       function linkify() {
+       function enable3DLinks() {
+
                if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
-                       var nodes = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
+                       var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
 
-                       for( var i = 0, len = nodes.length; i < len; i++ ) {
-                               var node = nodes[i];
+                       for( var i = 0, len = anchors.length; i < len; i++ ) {
+                               var anchor = anchors[i];
 
-                               if( node.textContent && !node.querySelector( 'img' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) {
-                    var span = document.createElement('span');
-                    span.setAttribute('data-title', node.text);
-                    span.innerHTML = node.innerHTML;
+                               if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
+                                       var span = document.createElement('span');
+                                       span.setAttribute('data-title', anchor.text);
+                                       span.innerHTML = anchor.innerHTML;
 
-                                       node.classList.add( 'roll' );
-                    node.innerHTML = '';
-                    node.appendChild(span);
+                                       anchor.classList.add( 'roll' );
+                                       anchor.innerHTML = '';
+                                       anchor.appendChild(span);
                                }
                        }
                }
+
+       }
+
+       /**
+        * Unwrap all 3D links.
+        */
+       function disable3DLinks() {
+
+               var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
+
+               for( var i = 0, len = anchors.length; i < len; i++ ) {
+                       var anchor = anchors[i];
+                       var span = anchor.querySelector( 'span' );
+
+                       if( span ) {
+                               anchor.classList.remove( 'roll' );
+                               anchor.innerHTML = span.innerHTML;
+                       }
+               }
+
+       }
+
+       /**
+        * 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.
+        */
+       function sortFragments( fragments ) {
+
+               var a = toArray( fragments );
+
+               a.forEach( function( el, idx ) {
+                       if( !el.hasAttribute( 'data-fragment-index' ) ) {
+                               el.setAttribute( 'data-fragment-index', idx );
+                       }
+               } );
+
+               a.sort( function( l, r ) {
+                       return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
+               } );
+
+               return a;
+
        }
 
        /**
@@ -514,30 +656,81 @@ var Reveal = (function(){
         */
        function layout() {
 
-               if( config.center ) {
+               if( dom.wrapper ) {
+
+                       // Available space to scale within
+                       var availableWidth = dom.wrapper.offsetWidth,
+                               availableHeight = dom.wrapper.offsetHeight;
+
+                       // Reduce available space by margin
+                       availableWidth -= ( availableHeight * config.margin );
+                       availableHeight -= ( availableHeight * config.margin );
+
+                       // Dimensions of the content
+                       var slideWidth = config.width,
+                               slideHeight = config.height;
+
+                       // Slide width may be a percentage of available width
+                       if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
+                               slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
+                       }
+
+                       // Slide height may be a percentage of available height
+                       if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
+                               slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
+                       }
+
+                       dom.slides.style.width = slideWidth + 'px';
+                       dom.slides.style.height = slideHeight + 'px';
+
+                       // Determine scale of content to fit within available space
+                       scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
+
+                       // Respect max/min scale settings
+                       scale = Math.max( scale, config.minScale );
+                       scale = Math.min( scale, config.maxScale );
+
+                       // Prefer applying scale via zoom since Chrome blurs scaled content
+                       // with nested transforms
+                       if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
+                               dom.slides.style.zoom = scale;
+                       }
+                       // Apply scale transform as a fallback
+                       else {
+                               var transform = 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)';
+
+                               dom.slides.style.WebkitTransform = transform;
+                               dom.slides.style.MozTransform = transform;
+                               dom.slides.style.msTransform = transform;
+                               dom.slides.style.OTransform = transform;
+                               dom.slides.style.transform = transform;
+                       }
 
                        // Select all slides, vertical and horizontal
                        var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
 
-                       // Determine the minimum top offset for slides
-                       var minTop = -dom.wrapper.offsetHeight / 2;
-
                        for( var i = 0, len = slides.length; i < len; i++ ) {
                                var slide = slides[ i ];
 
-                               // Don't bother update invisible slides
+                               // Don't bother updating invisible slides
                                if( slide.style.display === 'none' ) {
                                        continue;
                                }
 
-                               // Vertical stacks are not centered since their section 
-                               // children will be
-                               if( slide.classList.contains( 'stack' ) ) {
-                                       slide.style.top = 0;
+                               if( config.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( - ( slide.offsetHeight / 2 ) - 20, -slideHeight / 2 ) + 'px';
+                                       }
                                }
                                else {
-                                       slide.style.top = Math.max( - ( slide.offsetHeight / 2 ) - 20, minTop ) + 'px';
+                                       slide.style.top = '';
                                }
+
                        }
 
                }
@@ -545,32 +738,36 @@ var Reveal = (function(){
        }
 
        /**
-        * Stores the vertical index of a stack so that the same 
-        * vertical slide can be selected when navigating to and 
+        * 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 {int} v Index to memorize
         */
        function setPreviousVerticalIndex( stack, v ) {
-               if( stack ) {
+
+               if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
                        stack.setAttribute( 'data-previous-indexv', v || 0 );
                }
+
        }
 
        /**
-        * Retrieves the vertical index which was stored using 
+        * 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( stack && stack.classList.contains( 'stack' ) ) {
+
+               if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
                        return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 );
                }
 
                return 0;
+
        }
 
        /**
@@ -585,14 +782,20 @@ var Reveal = (function(){
                // Only proceed if enabled in config
                if( config.overview ) {
 
+                       // Don't auto-slide while in overview mode
+                       cancelAutoSlide();
+
+                       var wasActive = dom.wrapper.classList.contains( 'overview' );
+
                        dom.wrapper.classList.add( 'overview' );
                        dom.wrapper.classList.remove( 'exit-overview' );
+
                        clearTimeout( activateOverviewTimeout );
                        clearTimeout( deactivateOverviewTimeout );
 
-                       // Not the pretties solution, but need to let the overview 
-                       // class apply first so that slides are measured accurately 
-                       // before we can positon them
+                       // Not the pretties solution, but need to let the overview
+                       // class apply first so that slides are measured accurately
+                       // before we can position them
                        activateOverviewTimeout = setTimeout( function(){
 
                                var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
@@ -643,6 +846,15 @@ var Reveal = (function(){
 
                                layout();
 
+                               if( !wasActive ) {
+                                       // Notify observers of the overview showing
+                                       dispatchEvent( 'overviewshown', {
+                                               'indexh': indexh,
+                                               'indexv': indexv,
+                                               'currentSlide': currentSlide
+                                       } );
+                               }
+
                        }, 10 );
 
                }
@@ -662,10 +874,12 @@ var Reveal = (function(){
                        clearTimeout( deactivateOverviewTimeout );
 
                        dom.wrapper.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
                        dom.wrapper.classList.add( 'exit-overview' );
+
                        deactivateOverviewTimeout = setTimeout( function () {
                                dom.wrapper.classList.remove( 'exit-overview' );
                        }, 10);
@@ -690,6 +904,15 @@ var Reveal = (function(){
 
                        slide( indexh, indexv );
 
+                       cueAutoSlide();
+
+                       // Notify observers of the overview hiding
+                       dispatchEvent( 'overviewhidden', {
+                               'indexh': indexh,
+                               'indexv': indexv,
+                               'currentSlide': currentSlide
+                       } );
+
                }
        }
 
@@ -701,12 +924,14 @@ var Reveal = (function(){
         * overview is open, false means it's closed.
         */
        function toggleOverview( override ) {
+
                if( typeof override === 'boolean' ) {
                        override ? activateOverview() : deactivateOverview();
                }
                else {
-                       isOverviewActive() ? deactivateOverview() : activateOverview();
+                       isOverview() ? deactivateOverview() : activateOverview();
                }
+
        }
 
        /**
@@ -715,8 +940,10 @@ var Reveal = (function(){
         * @return {Boolean} true if the overview is active,
         * false otherwise
         */
-       function isOverviewActive() {
+       function isOverview() {
+
                return dom.wrapper.classList.contains( 'overview' );
+
        }
 
        /**
@@ -726,6 +953,7 @@ var Reveal = (function(){
         * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
         */
        function enterFullscreen() {
+
                var element = document.body;
 
                // Check which implementation is available
@@ -737,6 +965,7 @@ var Reveal = (function(){
                if( requestMethod ) {
                        requestMethod.apply( element );
                }
+
        }
 
        /**
@@ -744,33 +973,55 @@ var Reveal = (function(){
         * black.
         */
        function pause() {
+
+               var wasPaused = dom.wrapper.classList.contains( 'paused' );
+
+               cancelAutoSlide();
                dom.wrapper.classList.add( 'paused' );
+
+               if( wasPaused === false ) {
+                       dispatchEvent( 'paused' );
+               }
+
        }
 
        /**
         * Exits from the paused mode.
         */
        function resume() {
+
+               var wasPaused = dom.wrapper.classList.contains( 'paused' );
                dom.wrapper.classList.remove( 'paused' );
+
+               cueAutoSlide();
+
+               if( wasPaused ) {
+                       dispatchEvent( 'resumed' );
+               }
+
        }
 
        /**
         * Toggles the paused mode on and off.
         */
        function togglePause() {
+
                if( isPaused() ) {
                        resume();
                }
                else {
                        pause();
                }
+
        }
 
        /**
         * Checks if we are currently in the paused mode.
         */
        function isPaused() {
+
                return dom.wrapper.classList.contains( 'paused' );
+
        }
 
        /**
@@ -780,23 +1031,25 @@ var Reveal = (function(){
         *
         * @param {int} h Horizontal index of the target slide
         * @param {int} v Vertical index of the target slide
-        * @param {int} f Optional index of a fragment within the 
+        * @param {int} f Optional index of a fragment within the
         * target slide to activate
+        * @param {int} o Optional origin for use in multimaster environments
         */
-       function slide( h, v, f ) {
+       function slide( h, v, f, o ) {
+
                // Remember where we were at before
                previousSlide = currentSlide;
 
                // Query all horizontal slides in the deck
                var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
 
-               // If no vertical index is specified and the upcoming slide is a 
+               // If no vertical index is specified and the upcoming slide is a
                // stack, resume at its previous vertical index
                if( v === undefined ) {
                        v = getPreviousVerticalIndex( horizontalSlides[ h ] );
                }
 
-               // If we were on a vertical stack, remember what vertical index 
+               // 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 );
@@ -834,13 +1087,13 @@ var Reveal = (function(){
                        dispatchEvent( state[i] );
                }
 
-               // Clean up the remaints of the previous state
+               // Clean up the remains of the previous state
                while( stateBefore.length ) {
                        document.documentElement.classList.remove( stateBefore.pop() );
                }
 
                // If the overview is active, re-activate it to update positions
-               if( isOverviewActive() ) {
+               if( isOverview() ) {
                        activateOverview();
                }
 
@@ -859,7 +1112,7 @@ var Reveal = (function(){
 
                // Show fragment, if specified
                if( typeof f !== 'undefined' ) {
-                       var fragments = currentSlide.querySelectorAll( '.fragment' );
+                       var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
 
                        toArray( fragments ).forEach( function( fragment, indexf ) {
                                if( indexf < f ) {
@@ -877,7 +1130,8 @@ var Reveal = (function(){
                                'indexh': indexh,
                                'indexv': indexv,
                                'previousSlide': previousSlide,
-                               'currentSlide': currentSlide
+                               'currentSlide': currentSlide,
+                               'origin': o
                        } );
                }
                else {
@@ -891,24 +1145,25 @@ var Reveal = (function(){
                if( previousSlide ) {
                        previousSlide.classList.remove( 'present' );
 
-            // Reset all slides upon navigate to home
-            // Issue: #285
-            if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
-                // Launch async task
-                setTimeout( function () {
-                    var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
-                    for( i in slides ) {
-                        if( slides[i] ) {
-                            // Reset stack
-                            setPreviousVerticalIndex( slides[i], 0 );
-                        }
-                    }
-                }, 0 );
-            }
+                       // Reset all slides upon navigate to home
+                       // Issue: #285
+                       if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
+                               // Launch async task
+                               setTimeout( function () {
+                                       var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
+                                       for( i in slides ) {
+                                               if( slides[i] ) {
+                                                       // Reset stack
+                                                       setPreviousVerticalIndex( slides[i], 0 );
+                                               }
+                                       }
+                               }, 0 );
+                       }
                }
 
                updateControls();
                updateProgress();
+
        }
 
        /**
@@ -925,6 +1180,7 @@ var Reveal = (function(){
         * bounds.
         */
        function updateSlides( selector, index ) {
+
                // Select all slides and convert the NodeList result to
                // an array
                var slides = toArray( document.querySelectorAll( selector ) ),
@@ -949,7 +1205,7 @@ var Reveal = (function(){
 
                                // Optimization; hide all slides that are three or more steps
                                // away from the present slide
-                               if( isOverviewActive() === false ) {
+                               if( isOverview() === false ) {
                                        // The distance loops so that it measures 1 between the first
                                        // and last slides
                                        var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
@@ -991,7 +1247,7 @@ var Reveal = (function(){
                        var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
                        if( slideAutoSlide ) {
                                autoSlide = parseInt( slideAutoSlide, 10 );
-                       } 
+                       }
                        else {
                                autoSlide = config.autoSlide;
                        }
@@ -1011,6 +1267,7 @@ var Reveal = (function(){
         * Updates the progress bar to reflect the current slide.
         */
        function updateProgress() {
+
                // Update progress if enabled
                if( config.progress && dom.progress ) {
 
@@ -1052,12 +1309,14 @@ var Reveal = (function(){
                        dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
 
                }
+
        }
 
        /**
         * Updates the state of all control/navigation arrows.
         */
        function updateControls() {
+
                if ( config.controls && dom.controls ) {
 
                        var routes = availableRoutes();
@@ -1082,6 +1341,7 @@ var Reveal = (function(){
                        if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
 
                }
+
        }
 
        /**
@@ -1090,21 +1350,24 @@ var Reveal = (function(){
         * @return {Object} containing four booleans: left/right/up/down
         */
        function availableRoutes() {
+
                var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
                        verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
 
                return {
-                       left: indexh > 0,
-                       right: indexh < horizontalSlides.length - 1,
+                       left: indexh > 0 || config.loop,
+                       right: indexh < horizontalSlides.length - 1 || config.loop,
                        up: indexv > 0,
                        down: indexv < verticalSlides.length - 1
                };
+
        }
 
        /**
         * Reads the current URL (hash) and navigates accordingly.
         */
        function readURL() {
+
                var hash = window.location.hash;
 
                // Attempt to parse the hash as either an index or name
@@ -1134,16 +1397,18 @@ var Reveal = (function(){
 
                        slide( h, v );
                }
+
        }
 
        /**
         * Updates the page URL (hash) to reflect the current
         * state.
         *
-        * @param {Number} delay The time in ms to wait before 
+        * @param {Number} delay The time in ms to wait before
         * writing the hash
         */
        function writeURL( delay ) {
+
                if( config.history ) {
 
                        // Make sure there's never more than one timeout running
@@ -1169,6 +1434,7 @@ var Reveal = (function(){
                                window.location.hash = url;
                        }
                }
+
        }
 
        /**
@@ -1182,6 +1448,7 @@ var Reveal = (function(){
         * @return {Object} { h: <int>, v: <int> }
         */
        function getIndices( slide ) {
+
                // By default, return the current indices
                var h = indexh,
                        v = indexv;
@@ -1204,6 +1471,7 @@ var Reveal = (function(){
                }
 
                return { h: h, v: v };
+
        }
 
        /**
@@ -1213,9 +1481,11 @@ var Reveal = (function(){
         * false otherwise
         */
        function nextFragment() {
+
                // Vertical slides:
                if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
-                       var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
+                       var verticalFragments = sortFragments( document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' ) );
+
                        if( verticalFragments.length ) {
                                verticalFragments[0].classList.add( 'visible' );
 
@@ -1226,7 +1496,8 @@ var Reveal = (function(){
                }
                // Horizontal slides:
                else {
-                       var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
+                       var horizontalFragments = sortFragments( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' ) );
+
                        if( horizontalFragments.length ) {
                                horizontalFragments[0].classList.add( 'visible' );
 
@@ -1237,6 +1508,7 @@ var Reveal = (function(){
                }
 
                return false;
+
        }
 
        /**
@@ -1246,9 +1518,11 @@ var Reveal = (function(){
         * false otherwise
         */
        function previousFragment() {
+
                // Vertical slides:
                if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
-                       var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' );
+                       var verticalFragments = sortFragments( document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' ) );
+
                        if( verticalFragments.length ) {
                                verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' );
 
@@ -1259,7 +1533,8 @@ var Reveal = (function(){
                }
                // Horizontal slides:
                else {
-                       var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' );
+                       var horizontalFragments = sortFragments( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' ) );
+
                        if( horizontalFragments.length ) {
                                horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' );
 
@@ -1270,46 +1545,66 @@ var Reveal = (function(){
                }
 
                return false;
+
        }
 
        /**
         * Cues a new automated slide if enabled in the config.
         */
        function cueAutoSlide() {
+
                clearTimeout( autoSlideTimeout );
 
                // Cue the next auto-slide if enabled
-               if( autoSlide ) {
+               if( autoSlide && !isPaused() && !isOverview() ) {
                        autoSlideTimeout = setTimeout( navigateNext, autoSlide );
                }
+
+       }
+
+       /**
+        * Cancels any ongoing request to auto-slide.
+        */
+       function cancelAutoSlide() {
+
+               clearTimeout( autoSlideTimeout );
+
        }
 
        function navigateLeft() {
+
                // Prioritize hiding fragments
-               if( availableRoutes().left && isOverviewActive() || previousFragment() === false ) {
+               if( availableRoutes().left && ( isOverview() || previousFragment() === false ) ) {
                        slide( indexh - 1 );
                }
+
        }
 
        function navigateRight() {
+
                // Prioritize revealing fragments
-               if( availableRoutes().right && isOverviewActive() || nextFragment() === false ) {
+               if( availableRoutes().right && ( isOverview() || nextFragment() === false ) ) {
                        slide( indexh + 1 );
                }
+
        }
 
        function navigateUp() {
+
                // Prioritize hiding fragments
-               if( availableRoutes().up && isOverviewActive() || previousFragment() === false ) {
+               if( availableRoutes().up && isOverview() || previousFragment() === false ) {
                        slide( indexh, indexv - 1 );
                }
+
        }
 
        function navigateDown() {
+
                // Prioritize revealing fragments
-               if( availableRoutes().down && isOverviewActive() || nextFragment() === false ) {
+               if( availableRoutes().down && isOverview() || nextFragment() === false ) {
                        slide( indexh, indexv + 1 );
                }
+
        }
 
        /**
@@ -1319,6 +1614,7 @@ var Reveal = (function(){
         * 3) Previous horizontal slide
         */
        function navigatePrev() {
+
                // Prioritize revealing fragments
                if( previousFragment() === false ) {
                        if( availableRoutes().up ) {
@@ -1331,16 +1627,18 @@ var Reveal = (function(){
                                if( previousSlide ) {
                                        indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
                                        indexh --;
-                                       slide();
+                                       slide( indexh, indexv );
                                }
                        }
                }
+
        }
 
        /**
         * Same as #navigatePrev() but navigates forwards.
         */
        function navigateNext() {
+
                // Prioritize revealing fragments
                if( nextFragment() === false ) {
                        availableRoutes().down ? navigateDown() : navigateRight();
@@ -1349,6 +1647,7 @@ var Reveal = (function(){
                // If auto-sliding is enabled we need to cue up
                // another timeout
                cueAutoSlide();
+
        }
 
 
@@ -1363,17 +1662,23 @@ var Reveal = (function(){
         * @param {Object} event
         */
        function onDocumentKeyDown( event ) {
-               // Check if there's a focused element that could be using 
+
+               // Check if there's a focused element that could be using
                // the keyboard
                var activeElement = document.activeElement;
                var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
 
-               // Disregard the event if there's a focused element or a 
+               // Disregard the event if there's a focused element or a
                // keyboard modifier key is present
-               if ( hasFocus || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
+               if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
 
                var triggered = true;
 
+               // while paused only allow "unpausing" keyboard events (b and .)
+               if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
+                       return false;
+               }
+
                switch( event.keyCode ) {
                        // p, page up
                        case 80: case 33: navigatePrev(); break;
@@ -1392,11 +1697,11 @@ var Reveal = (function(){
                        // end
                        case 35: slide( Number.MAX_VALUE ); break;
                        // space
-                       case 32: isOverviewActive() ? deactivateOverview() : navigateNext(); break;
+                       case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
                        // return
-                       case 13: isOverviewActive() ? deactivateOverview() : triggered = false; break;
-                       // b, period
-                       case 66: case 190: togglePause(); break;
+                       case 13: isOverview() ? deactivateOverview() : triggered = false; break;
+                       // b, period, Logitech presenter tools "black screen" button
+                       case 66: case 190: case 191: togglePause(); break;
                        // f
                        case 70: enterFullscreen(); break;
                        default:
@@ -1421,10 +1726,11 @@ var Reveal = (function(){
        }
 
        /**
-        * Handler for the document level 'touchstart' event,
-        * enables support for swipe and pinch gestures.
+        * Handler for the 'touchstart' event, enables support for
+        * swipe and pinch gestures.
         */
-       function onDocumentTouchStart( event ) {
+       function onTouchStart( event ) {
+
                touch.startX = event.touches[0].clientX;
                touch.startY = event.touches[0].clientY;
                touch.startCount = event.touches.length;
@@ -1440,12 +1746,14 @@ var Reveal = (function(){
                                y: touch.startY
                        } );
                }
+
        }
 
        /**
-        * Handler for the document level 'touchmove' event.
+        * Handler for the 'touchmove' event.
         */
-       function onDocumentTouchMove( event ) {
+       function onTouchMove( event ) {
+
                // Each touch should only trigger one action
                if( !touch.handled ) {
                        var currentX = event.touches[0].clientX;
@@ -1512,20 +1820,60 @@ var Reveal = (function(){
                else if( navigator.userAgent.match( /android/gi ) ) {
                        event.preventDefault();
                }
+
        }
 
        /**
-        * Handler for the document level 'touchend' event.
+        * Handler for the 'touchend' event.
         */
-       function onDocumentTouchEnd( event ) {
+       function onTouchEnd( event ) {
+
                touch.handled = false;
+
+       }
+
+       /**
+        * Convert pointer down to touch start.
+        */
+       function onPointerDown( event ) {
+
+               if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+                       event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+                       onTouchStart( event );
+               }
+
+       }
+
+       /**
+        * Convert pointer move to touch move.
+        */
+       function onPointerMove( event ) {
+
+               if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+                       event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+                       onTouchMove( event );
+               }
+
+       }
+
+       /**
+        * Convert pointer up to touch end.
+        */
+       function onPointerUp( event ) {
+
+               if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+                       event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+                       onTouchEnd( event );
+               }
+
        }
 
        /**
         * Handles mouse wheel scrolling, throttled to avoid skipping
         * multiple slides.
         */
-       function onDocumentMouseScroll( event ){
+       function onDocumentMouseScroll( event ) {
+
                clearTimeout( mouseWheelTimeout );
 
                mouseWheelTimeout = setTimeout( function() {
@@ -1537,6 +1885,7 @@ var Reveal = (function(){
                                navigatePrev();
                        }
                }, 100 );
+
        }
 
        /**
@@ -1545,51 +1894,75 @@ var Reveal = (function(){
         *
         * ( clickX / presentationWidth ) * numberOfSlides
         */
-       function onProgressClick( event ) {
+       function onProgressClicked( event ) {
+
+               event.preventDefault();
+
                var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
                var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
 
                slide( slideIndex );
+
        }
 
+       /**
+        * Event handler for navigation control buttons.
+        */
+       function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
+       function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
+       function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
+       function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
+       function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
+       function onNavigateNextClicked( event ) { event.preventDefault(); navigateNext(); }
+
        /**
         * Handler for the window level 'hashchange' event.
         */
        function onWindowHashChange( event ) {
+
                readURL();
+
        }
 
        /**
         * Handler for the window level 'resize' event.
         */
        function onWindowResize( event ) {
+
                layout();
+
        }
 
        /**
         * Invoked when a slide is and we're in the overview.
         */
        function onOverviewSlideClicked( event ) {
+
                // TODO There's a bug here where the event listeners are not
                // removed after deactivating the overview.
-               if( isOverviewActive() ) {
+               if( eventsAreBound && isOverview() ) {
                        event.preventDefault();
 
-                       deactivateOverview();
-
                        var element = event.target;
 
                        while( element && !element.nodeName.match( /section/gi ) ) {
                                element = element.parentNode;
                        }
 
-                       if( element.nodeName.match( /section/gi ) ) {
-                               var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
-                                       v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
+                       if( element && !element.classList.contains( 'disabled' ) ) {
+
+                               deactivateOverview();
+
+                               if( element.nodeName.match( /section/gi ) ) {
+                                       var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
+                                               v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
+
+                                       slide( h, v );
+                               }
 
-                               slide( h, v );
                        }
                }
+
        }
 
 
@@ -1600,6 +1973,7 @@ var Reveal = (function(){
 
        return {
                initialize: initialize,
+               configure: configure,
 
                // Navigation methods
                slide: slide,
@@ -1621,9 +1995,22 @@ var Reveal = (function(){
                navigatePrev: navigatePrev,
                navigateNext: navigateNext,
 
+               // Forces an update in slide layout
+               layout: layout,
+
+               // Returns an object with the available routes as booleans (left/right/top/bottom)
+               availableRoutes: availableRoutes,
+
                // Toggles the overview mode on/off
                toggleOverview: toggleOverview,
 
+               // Toggles the "black screen" mode on/off
+               togglePause: togglePause,
+
+               // State checks
+               isOverview: isOverview,
+               isPaused: isPaused,
+
                // Adds or removes all internal event listeners (such as keyboard)
                addEventListeners: addEventListeners,
                removeEventListeners: removeEventListeners,
@@ -1631,6 +2018,18 @@ var Reveal = (function(){
                // Returns the indices of the current, or specified, slide
                getIndices: getIndices,
 
+               // Returns the slide at the specified index, y is optional
+               getSlide: function( x, y ) {
+                       var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
+                       var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
+
+                       if( typeof y !== 'undefined' ) {
+                               return verticalSlides ? verticalSlides[ y ] : undefined;
+                       }
+
+                       return horizontalSlide;
+               },
+
                // Returns the previous slide element, may be null
                getPreviousSlide: function() {
                        return previousSlide;
@@ -1641,6 +2040,27 @@ var Reveal = (function(){
                        return currentSlide;
                },
 
+               // Returns the current scale of the presentation content
+               getScale: function() {
+                       return scale;
+               },
+
+               // Returns the current configuration object
+               getConfig: function() {
+                       return config;
+               },
+
+               // Returns an index (1-based) of the current fragment
+               getCurrentFragmentIndex : function() {
+                       if( currentSlide ) {
+                               var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
+
+                               if( visibleFragments.length ) {
+                                       return visibleFragments.length;
+                               }
+                       }
+               },
+
                // Helper method, retrieves query string as a key/value hash
                getQueryHash: function() {
                        var query = {};
@@ -1652,6 +2072,21 @@ var Reveal = (function(){
                        return query;
                },
 
+               // Returns true if we're currently on the first slide
+               isFirstSlide: function() {
+                       return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
+               },
+
+               // Returns true if we're currently on the last slide
+               isLastSlide: function() {
+                       if( currentSlide && currentSlide.classList.contains( '.stack' ) ) {
+                               return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
+                       }
+                       else {
+                               return document.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
+                       }
+               },
+
                // Forward event binding to the reveal DOM element
                addEventListener: function( type, listener, useCapture ) {
                        if( 'addEventListener' in window ) {
@@ -1665,4 +2100,4 @@ var Reveal = (function(){
                }
        };
 
-})();
\ No newline at end of file
+})();