Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
| Download
Project: KOB1
Views: 16973/*!1* reveal.js2* http://revealjs.com3* MIT licensed4*5* Copyright (C) 2017 Hakim El Hattab, http://hakim.se6*/7(function( root, factory ) {8if( typeof define === 'function' && define.amd ) {9// AMD. Register as an anonymous module.10define( function() {11root.Reveal = factory();12return root.Reveal;13} );14} else if( typeof exports === 'object' ) {15// Node. Does not work with strict CommonJS.16module.exports = factory();17} else {18// Browser globals.19root.Reveal = factory();20}21}( this, function() {2223'use strict';2425var Reveal;2627// The reveal.js version28var VERSION = '3.6.0';2930var SLIDES_SELECTOR = '.slides section',31HORIZONTAL_SLIDES_SELECTOR = '.slides>section',32VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',33HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',34UA = navigator.userAgent,3536// Configuration defaults, can be overridden at initialization time37config = {3839// The "normal" size of the presentation, aspect ratio will be preserved40// when the presentation is scaled to fit different resolutions41width: 960,42height: 700,4344// Factor of the display size that should remain empty around the content45margin: 0.04,4647// Bounds for smallest/largest possible scale to apply to content48minScale: 0.2,49maxScale: 2.0,5051// Display presentation control arrows52controls: true,5354// Help the user learn the controls by providing hints, for example by55// bouncing the down arrow when they first encounter a vertical slide56controlsTutorial: true,5758// Determines where controls appear, "edges" or "bottom-right"59controlsLayout: 'bottom-right',6061// Visibility rule for backwards navigation arrows; "faded", "hidden"62// or "visible"63controlsBackArrows: 'faded',6465// Display a presentation progress bar66progress: true,6768// Display the page number of the current slide69slideNumber: false,7071// Determine which displays to show the slide number on72showSlideNumber: 'all',7374// Push each slide change to the browser history75history: false,7677// Enable keyboard shortcuts for navigation78keyboard: true,7980// Optional function that blocks keyboard events when retuning false81keyboardCondition: null,8283// Enable the slide overview mode84overview: true,8586// Vertical centering of slides87center: true,8889// Enables touch navigation on devices with touch input90touch: true,9192// Loop the presentation93loop: false,9495// Change the presentation direction to be RTL96rtl: false,9798// Randomizes the order of slides each time the presentation loads99shuffle: false,100101// Turns fragments on and off globally102fragments: true,103104// Flags if the presentation is running in an embedded mode,105// i.e. contained within a limited portion of the screen106embedded: false,107108// Flags if we should show a help overlay when the question-mark109// key is pressed110help: true,111112// Flags if it should be possible to pause the presentation (blackout)113pause: true,114115// Flags if speaker notes should be visible to all viewers116showNotes: false,117118// Global override for autolaying embedded media (video/audio/iframe)119// - null: Media will only autoplay if data-autoplay is present120// - true: All media will autoplay, regardless of individual setting121// - false: No media will autoplay, regardless of individual setting122autoPlayMedia: null,123124// Controls automatic progression to the next slide125// - 0: Auto-sliding only happens if the data-autoslide HTML attribute126// is present on the current slide or fragment127// - 1+: All slides will progress automatically at the given interval128// - false: No auto-sliding, even if data-autoslide is present129autoSlide: 0,130131// Stop auto-sliding after user input132autoSlideStoppable: true,133134// Use this method for navigation when auto-sliding (defaults to navigateNext)135autoSlideMethod: null,136137// Enable slide navigation via mouse wheel138mouseWheel: false,139140// Apply a 3D roll to links on hover141rollingLinks: false,142143// Hides the address bar on mobile devices144hideAddressBar: true,145146// Opens links in an iframe preview overlay147previewLinks: false,148149// Exposes the reveal.js API through window.postMessage150postMessage: true,151152// Dispatches all reveal.js events to the parent window through postMessage153postMessageEvents: false,154155// Focuses body when page changes visibility to ensure keyboard shortcuts work156focusBodyOnPageVisibilityChange: true,157158// Transition style159transition: 'slide', // none/fade/slide/convex/concave/zoom160161// Transition speed162transitionSpeed: 'default', // default/fast/slow163164// Transition style for full page slide backgrounds165backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom166167// Parallax background image168parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"169170// Parallax background size171parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"172173// Amount of pixels to move the parallax background per slide step174parallaxBackgroundHorizontal: null,175parallaxBackgroundVertical: null,176177// The maximum number of pages a single slide can expand onto when printing178// to PDF, unlimited by default179pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,180181// Offset used to reduce the height of content within exported PDF pages.182// This exists to account for environment differences based on how you183// print to PDF. CLI printing options, like phantomjs and wkpdf, can end184// on precisely the total height of the document whereas in-browser185// printing has to end one pixel before.186pdfPageHeightOffset: -1,187188// Number of slides away from the current that are visible189viewDistance: 3,190191// The display mode that will be used to show slides192display: 'block',193194// Script dependencies to load195dependencies: []196197},198199// Flags if Reveal.initialize() has been called200initialized = false,201202// Flags if reveal.js is loaded (has dispatched the 'ready' event)203loaded = false,204205// Flags if the overview mode is currently active206overview = false,207208// Holds the dimensions of our overview slides, including margins209overviewSlideWidth = null,210overviewSlideHeight = null,211212// The horizontal and vertical index of the currently active slide213indexh,214indexv,215216// The previous and current slide HTML elements217previousSlide,218currentSlide,219220previousBackground,221222// Remember which directions that the user has navigated towards223hasNavigatedRight = false,224hasNavigatedDown = false,225226// Slides may hold a data-state attribute which we pick up and apply227// as a class to the body. This list contains the combined state of228// all current slides.229state = [],230231// The current scale of the presentation (see width/height config)232scale = 1,233234// CSS transform that is currently applied to the slides container,235// split into two groups236slidesTransform = { layout: '', overview: '' },237238// Cached references to DOM elements239dom = {},240241// Features supported by the browser, see #checkCapabilities()242features = {},243244// Client is a mobile device, see #checkCapabilities()245isMobileDevice,246247// Client is a desktop Chrome, see #checkCapabilities()248isChrome,249250// Throttles mouse wheel navigation251lastMouseWheelStep = 0,252253// Delays updates to the URL due to a Chrome thumbnailer bug254writeURLTimeout = 0,255256// Flags if the interaction event listeners are bound257eventsAreBound = false,258259// The current auto-slide duration260autoSlide = 0,261262// Auto slide properties263autoSlidePlayer,264autoSlideTimeout = 0,265autoSlideStartTime = -1,266autoSlidePaused = false,267268// Holds information about the currently ongoing touch input269touch = {270startX: 0,271startY: 0,272startSpan: 0,273startCount: 0,274captured: false,275threshold: 40276},277278// Holds information about the keyboard shortcuts279keyboardShortcuts = {280'N , SPACE': 'Next slide',281'P': 'Previous slide',282'← , H': 'Navigate left',283'→ , L': 'Navigate right',284'↑ , K': 'Navigate up',285'↓ , J': 'Navigate down',286'Home': 'First slide',287'End': 'Last slide',288'B , .': 'Pause',289'F': 'Fullscreen',290'ESC, O': 'Slide overview'291};292293/**294* Starts up the presentation if the client is capable.295*/296function initialize( options ) {297298// Make sure we only initialize once299if( initialized === true ) return;300301initialized = true;302303checkCapabilities();304305if( !features.transforms2d && !features.transforms3d ) {306document.body.setAttribute( 'class', 'no-transforms' );307308// Since JS won't be running any further, we load all lazy309// loading elements upfront310var images = toArray( document.getElementsByTagName( 'img' ) ),311iframes = toArray( document.getElementsByTagName( 'iframe' ) );312313var lazyLoadable = images.concat( iframes );314315for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {316var element = lazyLoadable[i];317if( element.getAttribute( 'data-src' ) ) {318element.setAttribute( 'src', element.getAttribute( 'data-src' ) );319element.removeAttribute( 'data-src' );320}321}322323// If the browser doesn't support core features we won't be324// using JavaScript to control the presentation325return;326}327328// Cache references to key DOM elements329dom.wrapper = document.querySelector( '.reveal' );330dom.slides = document.querySelector( '.reveal .slides' );331332// Force a layout when the whole page, incl fonts, has loaded333window.addEventListener( 'load', layout, false );334335var query = Reveal.getQueryHash();336337// Do not accept new dependencies via query config to avoid338// the potential of malicious script injection339if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];340341// Copy options over to our config object342extend( config, options );343extend( config, query );344345// Hide the address bar in mobile browsers346hideAddressBar();347348// Loads the dependencies and continues to #start() once done349load();350351}352353/**354* Inspect the client to see what it's capable of, this355* should only happens once per runtime.356*/357function checkCapabilities() {358359isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( UA );360isChrome = /chrome/i.test( UA ) && !/edge/i.test( UA );361362var testElement = document.createElement( 'div' );363364features.transforms3d = 'WebkitPerspective' in testElement.style ||365'MozPerspective' in testElement.style ||366'msPerspective' in testElement.style ||367'OPerspective' in testElement.style ||368'perspective' in testElement.style;369370features.transforms2d = 'WebkitTransform' in testElement.style ||371'MozTransform' in testElement.style ||372'msTransform' in testElement.style ||373'OTransform' in testElement.style ||374'transform' in testElement.style;375376features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;377features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';378379features.canvas = !!document.createElement( 'canvas' ).getContext;380381// Transitions in the overview are disabled in desktop and382// Safari due to lag383features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( UA );384385// Flags if we should use zoom instead of transform to scale386// up slides. Zoom produces crisper results but has a lot of387// xbrowser quirks so we only use it in whitelsited browsers.388features.zoom = 'zoom' in testElement.style && !isMobileDevice &&389( isChrome || /Version\/[\d\.]+.*Safari/.test( UA ) );390391}392393/**394* Loads the dependencies of reveal.js. Dependencies are395* defined via the configuration option 'dependencies'396* and will be loaded prior to starting/binding reveal.js.397* Some dependencies may have an 'async' flag, if so they398* will load after reveal.js has been started up.399*/400function load() {401402var scripts = [],403scriptsAsync = [],404scriptsToPreload = 0;405406// Called once synchronous scripts finish loading407function proceed() {408if( scriptsAsync.length ) {409// Load asynchronous scripts410head.js.apply( null, scriptsAsync );411}412413start();414}415416function loadScript( s ) {417head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {418// Extension may contain callback functions419if( typeof s.callback === 'function' ) {420s.callback.apply( this );421}422423if( --scriptsToPreload === 0 ) {424proceed();425}426});427}428429for( var i = 0, len = config.dependencies.length; i < len; i++ ) {430var s = config.dependencies[i];431432// Load if there's no condition or the condition is truthy433if( !s.condition || s.condition() ) {434if( s.async ) {435scriptsAsync.push( s.src );436}437else {438scripts.push( s.src );439}440441loadScript( s );442}443}444445if( scripts.length ) {446scriptsToPreload = scripts.length;447448// Load synchronous scripts449head.js.apply( null, scripts );450}451else {452proceed();453}454455}456457/**458* Starts up reveal.js by binding input events and navigating459* to the current URL deeplink if there is one.460*/461function start() {462463loaded = true;464465// Make sure we've got all the DOM elements we need466setupDOM();467468// Listen to messages posted to this window469setupPostMessage();470471// Prevent the slides from being scrolled out of view472setupScrollPrevention();473474// Resets all vertical slides so that only the first is visible475resetVerticalSlides();476477// Updates the presentation to match the current configuration values478configure();479480// Read the initial hash481readURL();482483// Update all backgrounds484updateBackground( true );485486// Notify listeners that the presentation is ready but use a 1ms487// timeout to ensure it's not fired synchronously after #initialize()488setTimeout( function() {489// Enable transitions now that we're loaded490dom.slides.classList.remove( 'no-transition' );491492dom.wrapper.classList.add( 'ready' );493494dispatchEvent( 'ready', {495'indexh': indexh,496'indexv': indexv,497'currentSlide': currentSlide498} );499}, 1 );500501// Special setup and config is required when printing to PDF502if( isPrintingPDF() ) {503removeEventListeners();504505// The document needs to have loaded for the PDF layout506// measurements to be accurate507if( document.readyState === 'complete' ) {508setupPDF();509}510else {511window.addEventListener( 'load', setupPDF );512}513}514515}516517/**518* Finds and stores references to DOM elements which are519* required by the presentation. If a required element is520* not found, it is created.521*/522function setupDOM() {523524// Prevent transitions while we're loading525dom.slides.classList.add( 'no-transition' );526527if( isMobileDevice ) {528dom.wrapper.classList.add( 'no-hover' );529}530else {531dom.wrapper.classList.remove( 'no-hover' );532}533534if( /iphone/gi.test( UA ) ) {535dom.wrapper.classList.add( 'ua-iphone' );536}537else {538dom.wrapper.classList.remove( 'ua-iphone' );539}540541// Background element542dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );543544// Progress bar545dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );546dom.progressbar = dom.progress.querySelector( 'span' );547548// Arrow controls549dom.controls = createSingletonNode( dom.wrapper, 'aside', 'controls',550'<button class="navigate-left" aria-label="previous slide"><div class="controls-arrow"></div></button>' +551'<button class="navigate-right" aria-label="next slide"><div class="controls-arrow"></div></button>' +552'<button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>' +553'<button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>' );554555// Slide number556dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );557558// Element containing notes that are visible to the audience559dom.speakerNotes = createSingletonNode( dom.wrapper, 'div', 'speaker-notes', null );560dom.speakerNotes.setAttribute( 'data-prevent-swipe', '' );561dom.speakerNotes.setAttribute( 'tabindex', '0' );562563// Overlay graphic which is displayed during the paused mode564createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );565566dom.wrapper.setAttribute( 'role', 'application' );567568// There can be multiple instances of controls throughout the page569dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );570dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );571dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );572dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );573dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );574dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );575576// The right and down arrows in the standard reveal.js controls577dom.controlsRightArrow = dom.controls.querySelector( '.navigate-right' );578dom.controlsDownArrow = dom.controls.querySelector( '.navigate-down' );579580dom.statusDiv = createStatusDiv();581}582583/**584* Creates a hidden div with role aria-live to announce the585* current slide content. Hide the div off-screen to make it586* available only to Assistive Technologies.587*588* @return {HTMLElement}589*/590function createStatusDiv() {591592var statusDiv = document.getElementById( 'aria-status-div' );593if( !statusDiv ) {594statusDiv = document.createElement( 'div' );595statusDiv.style.position = 'absolute';596statusDiv.style.height = '1px';597statusDiv.style.width = '1px';598statusDiv.style.overflow = 'hidden';599statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';600statusDiv.setAttribute( 'id', 'aria-status-div' );601statusDiv.setAttribute( 'aria-live', 'polite' );602statusDiv.setAttribute( 'aria-atomic','true' );603dom.wrapper.appendChild( statusDiv );604}605return statusDiv;606607}608609/**610* Converts the given HTML element into a string of text611* that can be announced to a screen reader. Hidden612* elements are excluded.613*/614function getStatusText( node ) {615616var text = '';617618// Text node619if( node.nodeType === 3 ) {620text += node.textContent;621}622// Element node623else if( node.nodeType === 1 ) {624625var isAriaHidden = node.getAttribute( 'aria-hidden' );626var isDisplayHidden = window.getComputedStyle( node )['display'] === 'none';627if( isAriaHidden !== 'true' && !isDisplayHidden ) {628629toArray( node.childNodes ).forEach( function( child ) {630text += getStatusText( child );631} );632633}634635}636637return text;638639}640641/**642* Configures the presentation for printing to a static643* PDF.644*/645function setupPDF() {646647var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );648649// Dimensions of the PDF pages650var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),651pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );652653// Dimensions of slides within the pages654var slideWidth = slideSize.width,655slideHeight = slideSize.height;656657// Let the browser know what page size we want to print658injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0px;}' );659660// Limit the size of certain elements to the dimensions of the slide661injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );662663document.body.classList.add( 'print-pdf' );664document.body.style.width = pageWidth + 'px';665document.body.style.height = pageHeight + 'px';666667// Make sure stretch elements fit on slide668layoutSlideContents( slideWidth, slideHeight );669670// Add each slide's index as attributes on itself, we need these671// indices to generate slide numbers below672toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {673hslide.setAttribute( 'data-index-h', h );674675if( hslide.classList.contains( 'stack' ) ) {676toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {677vslide.setAttribute( 'data-index-h', h );678vslide.setAttribute( 'data-index-v', v );679} );680}681} );682683// Slide and slide background layout684toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {685686// Vertical stacks are not centred since their section687// children will be688if( slide.classList.contains( 'stack' ) === false ) {689// Center the slide inside of the page, giving the slide some margin690var left = ( pageWidth - slideWidth ) / 2,691top = ( pageHeight - slideHeight ) / 2;692693var contentHeight = slide.scrollHeight;694var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );695696// Adhere to configured pages per slide limit697numberOfPages = Math.min( numberOfPages, config.pdfMaxPagesPerSlide );698699// Center slides vertically700if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {701top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );702}703704// Wrap the slide in a page element and hide its overflow705// so that no page ever flows onto another706var page = document.createElement( 'div' );707page.className = 'pdf-page';708page.style.height = ( ( pageHeight + config.pdfPageHeightOffset ) * numberOfPages ) + 'px';709slide.parentNode.insertBefore( page, slide );710page.appendChild( slide );711712// Position the slide inside of the page713slide.style.left = left + 'px';714slide.style.top = top + 'px';715slide.style.width = slideWidth + 'px';716717if( slide.slideBackgroundElement ) {718page.insertBefore( slide.slideBackgroundElement, slide );719}720721// Inject notes if `showNotes` is enabled722if( config.showNotes ) {723724// Are there notes for this slide?725var notes = getSlideNotes( slide );726if( notes ) {727728var notesSpacing = 8;729var notesLayout = typeof config.showNotes === 'string' ? config.showNotes : 'inline';730var notesElement = document.createElement( 'div' );731notesElement.classList.add( 'speaker-notes' );732notesElement.classList.add( 'speaker-notes-pdf' );733notesElement.setAttribute( 'data-layout', notesLayout );734notesElement.innerHTML = notes;735736if( notesLayout === 'separate-page' ) {737page.parentNode.insertBefore( notesElement, page.nextSibling );738}739else {740notesElement.style.left = notesSpacing + 'px';741notesElement.style.bottom = notesSpacing + 'px';742notesElement.style.width = ( pageWidth - notesSpacing*2 ) + 'px';743page.appendChild( notesElement );744}745746}747748}749750// Inject slide numbers if `slideNumbers` are enabled751if( config.slideNumber && /all|print/i.test( config.showSlideNumber ) ) {752var slideNumberH = parseInt( slide.getAttribute( 'data-index-h' ), 10 ) + 1,753slideNumberV = parseInt( slide.getAttribute( 'data-index-v' ), 10 ) + 1;754755var numberElement = document.createElement( 'div' );756numberElement.classList.add( 'slide-number' );757numberElement.classList.add( 'slide-number-pdf' );758numberElement.innerHTML = formatSlideNumber( slideNumberH, '.', slideNumberV );759page.appendChild( numberElement );760}761}762763} );764765// Show all fragments766toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {767fragment.classList.add( 'visible' );768} );769770// Notify subscribers that the PDF layout is good to go771dispatchEvent( 'pdf-ready' );772773}774775/**776* This is an unfortunate necessity. Some actions – such as777* an input field being focused in an iframe or using the778* keyboard to expand text selection beyond the bounds of779* a slide – can trigger our content to be pushed out of view.780* This scrolling can not be prevented by hiding overflow in781* CSS (we already do) so we have to resort to repeatedly782* checking if the slides have been offset :(783*/784function setupScrollPrevention() {785786setInterval( function() {787if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {788dom.wrapper.scrollTop = 0;789dom.wrapper.scrollLeft = 0;790}791}, 1000 );792793}794795/**796* Creates an HTML element and returns a reference to it.797* If the element already exists the existing instance will798* be returned.799*800* @param {HTMLElement} container801* @param {string} tagname802* @param {string} classname803* @param {string} innerHTML804*805* @return {HTMLElement}806*/807function createSingletonNode( container, tagname, classname, innerHTML ) {808809// Find all nodes matching the description810var nodes = container.querySelectorAll( '.' + classname );811812// Check all matches to find one which is a direct child of813// the specified container814for( var i = 0; i < nodes.length; i++ ) {815var testNode = nodes[i];816if( testNode.parentNode === container ) {817return testNode;818}819}820821// If no node was found, create it now822var node = document.createElement( tagname );823node.className = classname;824if( typeof innerHTML === 'string' ) {825node.innerHTML = innerHTML;826}827container.appendChild( node );828829return node;830831}832833/**834* Creates the slide background elements and appends them835* to the background container. One element is created per836* slide no matter if the given slide has visible background.837*/838function createBackgrounds() {839840var printMode = isPrintingPDF();841842// Clear prior backgrounds843dom.background.innerHTML = '';844dom.background.classList.add( 'no-transition' );845846// Iterate over all horizontal slides847toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {848849var backgroundStack = createBackground( slideh, dom.background );850851// Iterate over all vertical slides852toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {853854createBackground( slidev, backgroundStack );855856backgroundStack.classList.add( 'stack' );857858} );859860} );861862// Add parallax background if specified863if( config.parallaxBackgroundImage ) {864865dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';866dom.background.style.backgroundSize = config.parallaxBackgroundSize;867868// Make sure the below properties are set on the element - these properties are869// needed for proper transitions to be set on the element via CSS. To remove870// annoying background slide-in effect when the presentation starts, apply871// these properties after short time delay872setTimeout( function() {873dom.wrapper.classList.add( 'has-parallax-background' );874}, 1 );875876}877else {878879dom.background.style.backgroundImage = '';880dom.wrapper.classList.remove( 'has-parallax-background' );881882}883884}885886/**887* Creates a background for the given slide.888*889* @param {HTMLElement} slide890* @param {HTMLElement} container The element that the background891* should be appended to892* @return {HTMLElement} New background div893*/894function createBackground( slide, container ) {895896var data = {897background: slide.getAttribute( 'data-background' ),898backgroundSize: slide.getAttribute( 'data-background-size' ),899backgroundImage: slide.getAttribute( 'data-background-image' ),900backgroundVideo: slide.getAttribute( 'data-background-video' ),901backgroundIframe: slide.getAttribute( 'data-background-iframe' ),902backgroundColor: slide.getAttribute( 'data-background-color' ),903backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),904backgroundPosition: slide.getAttribute( 'data-background-position' ),905backgroundTransition: slide.getAttribute( 'data-background-transition' )906};907908var element = document.createElement( 'div' );909910// Carry over custom classes from the slide to the background911element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );912913if( data.background ) {914// Auto-wrap image urls in url(...)915if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)([?#]|$)/gi.test( data.background ) ) {916slide.setAttribute( 'data-background-image', data.background );917}918else {919element.style.background = data.background;920}921}922923// Create a hash for this combination of background settings.924// This is used to determine when two slide backgrounds are925// the same.926if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {927element.setAttribute( 'data-background-hash', data.background +928data.backgroundSize +929data.backgroundImage +930data.backgroundVideo +931data.backgroundIframe +932data.backgroundColor +933data.backgroundRepeat +934data.backgroundPosition +935data.backgroundTransition );936}937938// Additional and optional background properties939if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;940if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize );941if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;942if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;943if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;944if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );945946container.appendChild( element );947948// If backgrounds are being recreated, clear old classes949slide.classList.remove( 'has-dark-background' );950slide.classList.remove( 'has-light-background' );951952slide.slideBackgroundElement = element;953954// If this slide has a background color, add a class that955// signals if it is light or dark. If the slide has no background956// color, no class will be set957var computedBackgroundStyle = window.getComputedStyle( element );958if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) {959var rgb = colorToRgb( computedBackgroundStyle.backgroundColor );960961// Ignore fully transparent backgrounds. Some browsers return962// rgba(0,0,0,0) when reading the computed background color of963// an element with no background964if( rgb && rgb.a !== 0 ) {965if( colorBrightness( computedBackgroundStyle.backgroundColor ) < 128 ) {966slide.classList.add( 'has-dark-background' );967}968else {969slide.classList.add( 'has-light-background' );970}971}972}973974return element;975976}977978/**979* Registers a listener to postMessage events, this makes it980* possible to call all reveal.js API methods from another981* window. For example:982*983* revealWindow.postMessage( JSON.stringify({984* method: 'slide',985* args: [ 2 ]986* }), '*' );987*/988function setupPostMessage() {989990if( config.postMessage ) {991window.addEventListener( 'message', function ( event ) {992var data = event.data;993994// Make sure we're dealing with JSON995if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {996data = JSON.parse( data );997998// Check if the requested method can be found999if( data.method && typeof Reveal[data.method] === 'function' ) {1000Reveal[data.method].apply( Reveal, data.args );1001}1002}1003}, false );1004}10051006}10071008/**1009* Applies the configuration settings from the config1010* object. May be called multiple times.1011*1012* @param {object} options1013*/1014function configure( options ) {10151016var oldTransition = config.transition;10171018// New config options may be passed when this method1019// is invoked through the API after initialization1020if( typeof options === 'object' ) extend( config, options );10211022// Abort if reveal.js hasn't finished loading, config1023// changes will be applied automatically once loading1024// finishes1025if( loaded === false ) return;10261027var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;10281029// Remove the previously configured transition class1030dom.wrapper.classList.remove( oldTransition );10311032// Force linear transition based on browser capabilities1033if( features.transforms3d === false ) config.transition = 'linear';10341035dom.wrapper.classList.add( config.transition );10361037dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );1038dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );10391040dom.controls.style.display = config.controls ? 'block' : 'none';1041dom.progress.style.display = config.progress ? 'block' : 'none';10421043dom.controls.setAttribute( 'data-controls-layout', config.controlsLayout );1044dom.controls.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );10451046if( config.shuffle ) {1047shuffle();1048}10491050if( config.rtl ) {1051dom.wrapper.classList.add( 'rtl' );1052}1053else {1054dom.wrapper.classList.remove( 'rtl' );1055}10561057if( config.center ) {1058dom.wrapper.classList.add( 'center' );1059}1060else {1061dom.wrapper.classList.remove( 'center' );1062}10631064// Exit the paused mode if it was configured off1065if( config.pause === false ) {1066resume();1067}10681069if( config.showNotes ) {1070dom.speakerNotes.setAttribute( 'data-layout', typeof config.showNotes === 'string' ? config.showNotes : 'inline' );1071}10721073if( config.mouseWheel ) {1074document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF1075document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );1076}1077else {1078document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF1079document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );1080}10811082// Rolling 3D links1083if( config.rollingLinks ) {1084enableRollingLinks();1085}1086else {1087disableRollingLinks();1088}10891090// Iframe link previews1091if( config.previewLinks ) {1092enablePreviewLinks();1093disablePreviewLinks( '[data-preview-link=false]' );1094}1095else {1096disablePreviewLinks();1097enablePreviewLinks( '[data-preview-link]:not([data-preview-link=false])' );1098}10991100// Remove existing auto-slide controls1101if( autoSlidePlayer ) {1102autoSlidePlayer.destroy();1103autoSlidePlayer = null;1104}11051106// Generate auto-slide controls if needed1107if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {1108autoSlidePlayer = new Playback( dom.wrapper, function() {1109return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );1110} );11111112autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );1113autoSlidePaused = false;1114}11151116// When fragments are turned off they should be visible1117if( config.fragments === false ) {1118toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {1119element.classList.add( 'visible' );1120element.classList.remove( 'current-fragment' );1121} );1122}11231124// Slide numbers1125var slideNumberDisplay = 'none';1126if( config.slideNumber && !isPrintingPDF() ) {1127if( config.showSlideNumber === 'all' ) {1128slideNumberDisplay = 'block';1129}1130else if( config.showSlideNumber === 'speaker' && isSpeakerNotes() ) {1131slideNumberDisplay = 'block';1132}1133}11341135dom.slideNumber.style.display = slideNumberDisplay;11361137sync();11381139}11401141/**1142* Binds all event listeners.1143*/1144function addEventListeners() {11451146eventsAreBound = true;11471148window.addEventListener( 'hashchange', onWindowHashChange, false );1149window.addEventListener( 'resize', onWindowResize, false );11501151if( config.touch ) {1152dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );1153dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );1154dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );11551156// Support pointer-style touch interaction as well1157if( window.navigator.pointerEnabled ) {1158// IE 11 uses un-prefixed version of pointer events1159dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );1160dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );1161dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );1162}1163else if( window.navigator.msPointerEnabled ) {1164// IE 10 uses prefixed version of pointer events1165dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );1166dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );1167dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );1168}1169}11701171if( config.keyboard ) {1172document.addEventListener( 'keydown', onDocumentKeyDown, false );1173document.addEventListener( 'keypress', onDocumentKeyPress, false );1174}11751176if( config.progress && dom.progress ) {1177dom.progress.addEventListener( 'click', onProgressClicked, false );1178}11791180if( config.focusBodyOnPageVisibilityChange ) {1181var visibilityChange;11821183if( 'hidden' in document ) {1184visibilityChange = 'visibilitychange';1185}1186else if( 'msHidden' in document ) {1187visibilityChange = 'msvisibilitychange';1188}1189else if( 'webkitHidden' in document ) {1190visibilityChange = 'webkitvisibilitychange';1191}11921193if( visibilityChange ) {1194document.addEventListener( visibilityChange, onPageVisibilityChange, false );1195}1196}11971198// Listen to both touch and click events, in case the device1199// supports both1200var pointerEvents = [ 'touchstart', 'click' ];12011202// Only support touch for Android, fixes double navigations in1203// stock browser1204if( UA.match( /android/gi ) ) {1205pointerEvents = [ 'touchstart' ];1206}12071208pointerEvents.forEach( function( eventName ) {1209dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );1210dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );1211dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );1212dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );1213dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );1214dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );1215} );12161217}12181219/**1220* Unbinds all event listeners.1221*/1222function removeEventListeners() {12231224eventsAreBound = false;12251226document.removeEventListener( 'keydown', onDocumentKeyDown, false );1227document.removeEventListener( 'keypress', onDocumentKeyPress, false );1228window.removeEventListener( 'hashchange', onWindowHashChange, false );1229window.removeEventListener( 'resize', onWindowResize, false );12301231dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );1232dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );1233dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );12341235// IE111236if( window.navigator.pointerEnabled ) {1237dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );1238dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );1239dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );1240}1241// IE101242else if( window.navigator.msPointerEnabled ) {1243dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );1244dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );1245dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );1246}12471248if ( config.progress && dom.progress ) {1249dom.progress.removeEventListener( 'click', onProgressClicked, false );1250}12511252[ 'touchstart', 'click' ].forEach( function( eventName ) {1253dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );1254dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );1255dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );1256dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );1257dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );1258dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );1259} );12601261}12621263/**1264* Extend object a with the properties of object b.1265* If there's a conflict, object b takes precedence.1266*1267* @param {object} a1268* @param {object} b1269*/1270function extend( a, b ) {12711272for( var i in b ) {1273a[ i ] = b[ i ];1274}12751276return a;12771278}12791280/**1281* Converts the target object to an array.1282*1283* @param {object} o1284* @return {object[]}1285*/1286function toArray( o ) {12871288return Array.prototype.slice.call( o );12891290}12911292/**1293* Utility for deserializing a value.1294*1295* @param {*} value1296* @return {*}1297*/1298function deserialize( value ) {12991300if( typeof value === 'string' ) {1301if( value === 'null' ) return null;1302else if( value === 'true' ) return true;1303else if( value === 'false' ) return false;1304else if( value.match( /^-?[\d\.]+$/ ) ) return parseFloat( value );1305}13061307return value;13081309}13101311/**1312* Measures the distance in pixels between point a1313* and point b.1314*1315* @param {object} a point with x/y properties1316* @param {object} b point with x/y properties1317*1318* @return {number}1319*/1320function distanceBetween( a, b ) {13211322var dx = a.x - b.x,1323dy = a.y - b.y;13241325return Math.sqrt( dx*dx + dy*dy );13261327}13281329/**1330* Applies a CSS transform to the target element.1331*1332* @param {HTMLElement} element1333* @param {string} transform1334*/1335function transformElement( element, transform ) {13361337element.style.WebkitTransform = transform;1338element.style.MozTransform = transform;1339element.style.msTransform = transform;1340element.style.transform = transform;13411342}13431344/**1345* Applies CSS transforms to the slides container. The container1346* is transformed from two separate sources: layout and the overview1347* mode.1348*1349* @param {object} transforms1350*/1351function transformSlides( transforms ) {13521353// Pick up new transforms from arguments1354if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;1355if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;13561357// Apply the transforms to the slides container1358if( slidesTransform.layout ) {1359transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );1360}1361else {1362transformElement( dom.slides, slidesTransform.overview );1363}13641365}13661367/**1368* Injects the given CSS styles into the DOM.1369*1370* @param {string} value1371*/1372function injectStyleSheet( value ) {13731374var tag = document.createElement( 'style' );1375tag.type = 'text/css';1376if( tag.styleSheet ) {1377tag.styleSheet.cssText = value;1378}1379else {1380tag.appendChild( document.createTextNode( value ) );1381}1382document.getElementsByTagName( 'head' )[0].appendChild( tag );13831384}13851386/**1387* Find the closest parent that matches the given1388* selector.1389*1390* @param {HTMLElement} target The child element1391* @param {String} selector The CSS selector to match1392* the parents against1393*1394* @return {HTMLElement} The matched parent or null1395* if no matching parent was found1396*/1397function closestParent( target, selector ) {13981399var parent = target.parentNode;14001401while( parent ) {14021403// There's some overhead doing this each time, we don't1404// want to rewrite the element prototype but should still1405// be enough to feature detect once at startup...1406var matchesMethod = parent.matches || parent.matchesSelector || parent.msMatchesSelector;14071408// If we find a match, we're all set1409if( matchesMethod && matchesMethod.call( parent, selector ) ) {1410return parent;1411}14121413// Keep searching1414parent = parent.parentNode;14151416}14171418return null;14191420}14211422/**1423* Converts various color input formats to an {r:0,g:0,b:0} object.1424*1425* @param {string} color The string representation of a color1426* @example1427* colorToRgb('#000');1428* @example1429* colorToRgb('#000000');1430* @example1431* colorToRgb('rgb(0,0,0)');1432* @example1433* colorToRgb('rgba(0,0,0)');1434*1435* @return {{r: number, g: number, b: number, [a]: number}|null}1436*/1437function colorToRgb( color ) {14381439var hex3 = color.match( /^#([0-9a-f]{3})$/i );1440if( hex3 && hex3[1] ) {1441hex3 = hex3[1];1442return {1443r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,1444g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,1445b: parseInt( hex3.charAt( 2 ), 16 ) * 0x111446};1447}14481449var hex6 = color.match( /^#([0-9a-f]{6})$/i );1450if( hex6 && hex6[1] ) {1451hex6 = hex6[1];1452return {1453r: parseInt( hex6.substr( 0, 2 ), 16 ),1454g: parseInt( hex6.substr( 2, 2 ), 16 ),1455b: parseInt( hex6.substr( 4, 2 ), 16 )1456};1457}14581459var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );1460if( rgb ) {1461return {1462r: parseInt( rgb[1], 10 ),1463g: parseInt( rgb[2], 10 ),1464b: parseInt( rgb[3], 10 )1465};1466}14671468var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );1469if( rgba ) {1470return {1471r: parseInt( rgba[1], 10 ),1472g: parseInt( rgba[2], 10 ),1473b: parseInt( rgba[3], 10 ),1474a: parseFloat( rgba[4] )1475};1476}14771478return null;14791480}14811482/**1483* Calculates brightness on a scale of 0-255.1484*1485* @param {string} color See colorToRgb for supported formats.1486* @see {@link colorToRgb}1487*/1488function colorBrightness( color ) {14891490if( typeof color === 'string' ) color = colorToRgb( color );14911492if( color ) {1493return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;1494}14951496return null;14971498}14991500/**1501* Returns the remaining height within the parent of the1502* target element.1503*1504* remaining height = [ configured parent height ] - [ current parent height ]1505*1506* @param {HTMLElement} element1507* @param {number} [height]1508*/1509function getRemainingHeight( element, height ) {15101511height = height || 0;15121513if( element ) {1514var newHeight, oldHeight = element.style.height;15151516// Change the .stretch element height to 0 in order find the height of all1517// the other elements1518element.style.height = '0px';1519newHeight = height - element.parentNode.offsetHeight;15201521// Restore the old height, just in case1522element.style.height = oldHeight + 'px';15231524return newHeight;1525}15261527return height;15281529}15301531/**1532* Checks if this instance is being used to print a PDF.1533*/1534function isPrintingPDF() {15351536return ( /print-pdf/gi ).test( window.location.search );15371538}15391540/**1541* Hides the address bar if we're on a mobile device.1542*/1543function hideAddressBar() {15441545if( config.hideAddressBar && isMobileDevice ) {1546// Events that should trigger the address bar to hide1547window.addEventListener( 'load', removeAddressBar, false );1548window.addEventListener( 'orientationchange', removeAddressBar, false );1549}15501551}15521553/**1554* Causes the address bar to hide on mobile devices,1555* more vertical space ftw.1556*/1557function removeAddressBar() {15581559setTimeout( function() {1560window.scrollTo( 0, 1 );1561}, 10 );15621563}15641565/**1566* Dispatches an event of the specified type from the1567* reveal DOM element.1568*/1569function dispatchEvent( type, args ) {15701571var event = document.createEvent( 'HTMLEvents', 1, 2 );1572event.initEvent( type, true, true );1573extend( event, args );1574dom.wrapper.dispatchEvent( event );15751576// If we're in an iframe, post each reveal.js event to the1577// parent window. Used by the notes plugin1578if( config.postMessageEvents && window.parent !== window.self ) {1579window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );1580}15811582}15831584/**1585* Wrap all links in 3D goodness.1586*/1587function enableRollingLinks() {15881589if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {1590var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );15911592for( var i = 0, len = anchors.length; i < len; i++ ) {1593var anchor = anchors[i];15941595if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {1596var span = document.createElement('span');1597span.setAttribute('data-title', anchor.text);1598span.innerHTML = anchor.innerHTML;15991600anchor.classList.add( 'roll' );1601anchor.innerHTML = '';1602anchor.appendChild(span);1603}1604}1605}16061607}16081609/**1610* Unwrap all 3D links.1611*/1612function disableRollingLinks() {16131614var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );16151616for( var i = 0, len = anchors.length; i < len; i++ ) {1617var anchor = anchors[i];1618var span = anchor.querySelector( 'span' );16191620if( span ) {1621anchor.classList.remove( 'roll' );1622anchor.innerHTML = span.innerHTML;1623}1624}16251626}16271628/**1629* Bind preview frame links.1630*1631* @param {string} [selector=a] - selector for anchors1632*/1633function enablePreviewLinks( selector ) {16341635var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );16361637anchors.forEach( function( element ) {1638if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {1639element.addEventListener( 'click', onPreviewLinkClicked, false );1640}1641} );16421643}16441645/**1646* Unbind preview frame links.1647*/1648function disablePreviewLinks( selector ) {16491650var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );16511652anchors.forEach( function( element ) {1653if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {1654element.removeEventListener( 'click', onPreviewLinkClicked, false );1655}1656} );16571658}16591660/**1661* Opens a preview window for the target URL.1662*1663* @param {string} url - url for preview iframe src1664*/1665function showPreview( url ) {16661667closeOverlay();16681669dom.overlay = document.createElement( 'div' );1670dom.overlay.classList.add( 'overlay' );1671dom.overlay.classList.add( 'overlay-preview' );1672dom.wrapper.appendChild( dom.overlay );16731674dom.overlay.innerHTML = [1675'<header>',1676'<a class="close" href="#"><span class="icon"></span></a>',1677'<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',1678'</header>',1679'<div class="spinner"></div>',1680'<div class="viewport">',1681'<iframe src="'+ url +'"></iframe>',1682'<small class="viewport-inner">',1683'<span class="x-frame-error">Unable to load iframe. This is likely due to the site\'s policy (x-frame-options).</span>',1684'</small>',1685'</div>'1686].join('');16871688dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {1689dom.overlay.classList.add( 'loaded' );1690}, false );16911692dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {1693closeOverlay();1694event.preventDefault();1695}, false );16961697dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {1698closeOverlay();1699}, false );17001701setTimeout( function() {1702dom.overlay.classList.add( 'visible' );1703}, 1 );17041705}17061707/**1708* Open or close help overlay window.1709*1710* @param {Boolean} [override] Flag which overrides the1711* toggle logic and forcibly sets the desired state. True means1712* help is open, false means it's closed.1713*/1714function toggleHelp( override ){17151716if( typeof override === 'boolean' ) {1717override ? showHelp() : closeOverlay();1718}1719else {1720if( dom.overlay ) {1721closeOverlay();1722}1723else {1724showHelp();1725}1726}1727}17281729/**1730* Opens an overlay window with help material.1731*/1732function showHelp() {17331734if( config.help ) {17351736closeOverlay();17371738dom.overlay = document.createElement( 'div' );1739dom.overlay.classList.add( 'overlay' );1740dom.overlay.classList.add( 'overlay-help' );1741dom.wrapper.appendChild( dom.overlay );17421743var html = '<p class="title">Keyboard Shortcuts</p><br/>';17441745html += '<table><th>KEY</th><th>ACTION</th>';1746for( var key in keyboardShortcuts ) {1747html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';1748}17491750html += '</table>';17511752dom.overlay.innerHTML = [1753'<header>',1754'<a class="close" href="#"><span class="icon"></span></a>',1755'</header>',1756'<div class="viewport">',1757'<div class="viewport-inner">'+ html +'</div>',1758'</div>'1759].join('');17601761dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {1762closeOverlay();1763event.preventDefault();1764}, false );17651766setTimeout( function() {1767dom.overlay.classList.add( 'visible' );1768}, 1 );17691770}17711772}17731774/**1775* Closes any currently open overlay.1776*/1777function closeOverlay() {17781779if( dom.overlay ) {1780dom.overlay.parentNode.removeChild( dom.overlay );1781dom.overlay = null;1782}17831784}17851786/**1787* Applies JavaScript-controlled layout rules to the1788* presentation.1789*/1790function layout() {17911792if( dom.wrapper && !isPrintingPDF() ) {17931794var size = getComputedSlideSize();17951796// Layout the contents of the slides1797layoutSlideContents( config.width, config.height );17981799dom.slides.style.width = size.width + 'px';1800dom.slides.style.height = size.height + 'px';18011802// Determine scale of content to fit within available space1803scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );18041805// Respect max/min scale settings1806scale = Math.max( scale, config.minScale );1807scale = Math.min( scale, config.maxScale );18081809// Don't apply any scaling styles if scale is 11810if( scale === 1 ) {1811dom.slides.style.zoom = '';1812dom.slides.style.left = '';1813dom.slides.style.top = '';1814dom.slides.style.bottom = '';1815dom.slides.style.right = '';1816transformSlides( { layout: '' } );1817}1818else {1819// Prefer zoom for scaling up so that content remains crisp.1820// Don't use zoom to scale down since that can lead to shifts1821// in text layout/line breaks.1822if( scale > 1 && features.zoom ) {1823dom.slides.style.zoom = scale;1824dom.slides.style.left = '';1825dom.slides.style.top = '';1826dom.slides.style.bottom = '';1827dom.slides.style.right = '';1828transformSlides( { layout: '' } );1829}1830// Apply scale transform as a fallback1831else {1832dom.slides.style.zoom = '';1833dom.slides.style.left = '50%';1834dom.slides.style.top = '50%';1835dom.slides.style.bottom = 'auto';1836dom.slides.style.right = 'auto';1837transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );1838}1839}18401841// Select all slides, vertical and horizontal1842var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );18431844for( var i = 0, len = slides.length; i < len; i++ ) {1845var slide = slides[ i ];18461847// Don't bother updating invisible slides1848if( slide.style.display === 'none' ) {1849continue;1850}18511852if( config.center || slide.classList.contains( 'center' ) ) {1853// Vertical stacks are not centred since their section1854// children will be1855if( slide.classList.contains( 'stack' ) ) {1856slide.style.top = 0;1857}1858else {1859slide.style.top = Math.max( ( size.height - slide.scrollHeight ) / 2, 0 ) + 'px';1860}1861}1862else {1863slide.style.top = '';1864}18651866}18671868updateProgress();1869updateParallax();18701871if( isOverview() ) {1872updateOverview();1873}18741875}18761877}18781879/**1880* Applies layout logic to the contents of all slides in1881* the presentation.1882*1883* @param {string|number} width1884* @param {string|number} height1885*/1886function layoutSlideContents( width, height ) {18871888// Handle sizing of elements with the 'stretch' class1889toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {18901891// Determine how much vertical space we can use1892var remainingHeight = getRemainingHeight( element, height );18931894// Consider the aspect ratio of media elements1895if( /(img|video)/gi.test( element.nodeName ) ) {1896var nw = element.naturalWidth || element.videoWidth,1897nh = element.naturalHeight || element.videoHeight;18981899var es = Math.min( width / nw, remainingHeight / nh );19001901element.style.width = ( nw * es ) + 'px';1902element.style.height = ( nh * es ) + 'px';19031904}1905else {1906element.style.width = width + 'px';1907element.style.height = remainingHeight + 'px';1908}19091910} );19111912}19131914/**1915* Calculates the computed pixel size of our slides. These1916* values are based on the width and height configuration1917* options.1918*1919* @param {number} [presentationWidth=dom.wrapper.offsetWidth]1920* @param {number} [presentationHeight=dom.wrapper.offsetHeight]1921*/1922function getComputedSlideSize( presentationWidth, presentationHeight ) {19231924var size = {1925// Slide size1926width: config.width,1927height: config.height,19281929// Presentation size1930presentationWidth: presentationWidth || dom.wrapper.offsetWidth,1931presentationHeight: presentationHeight || dom.wrapper.offsetHeight1932};19331934// Reduce available space by margin1935size.presentationWidth -= ( size.presentationWidth * config.margin );1936size.presentationHeight -= ( size.presentationHeight * config.margin );19371938// Slide width may be a percentage of available width1939if( typeof size.width === 'string' && /%$/.test( size.width ) ) {1940size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;1941}19421943// Slide height may be a percentage of available height1944if( typeof size.height === 'string' && /%$/.test( size.height ) ) {1945size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;1946}19471948return size;19491950}19511952/**1953* Stores the vertical index of a stack so that the same1954* vertical slide can be selected when navigating to and1955* from the stack.1956*1957* @param {HTMLElement} stack The vertical stack element1958* @param {string|number} [v=0] Index to memorize1959*/1960function setPreviousVerticalIndex( stack, v ) {19611962if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {1963stack.setAttribute( 'data-previous-indexv', v || 0 );1964}19651966}19671968/**1969* Retrieves the vertical index which was stored using1970* #setPreviousVerticalIndex() or 0 if no previous index1971* exists.1972*1973* @param {HTMLElement} stack The vertical stack element1974*/1975function getPreviousVerticalIndex( stack ) {19761977if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {1978// Prefer manually defined start-indexv1979var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';19801981return parseInt( stack.getAttribute( attributeName ) || 0, 10 );1982}19831984return 0;19851986}19871988/**1989* Displays the overview of slides (quick nav) by scaling1990* down and arranging all slide elements.1991*/1992function activateOverview() {19931994// Only proceed if enabled in config1995if( config.overview && !isOverview() ) {19961997overview = true;19981999dom.wrapper.classList.add( 'overview' );2000dom.wrapper.classList.remove( 'overview-deactivating' );20012002if( features.overviewTransitions ) {2003setTimeout( function() {2004dom.wrapper.classList.add( 'overview-animated' );2005}, 1 );2006}20072008// Don't auto-slide while in overview mode2009cancelAutoSlide();20102011// Move the backgrounds element into the slide container to2012// that the same scaling is applied2013dom.slides.appendChild( dom.background );20142015// Clicking on an overview slide navigates to it2016toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {2017if( !slide.classList.contains( 'stack' ) ) {2018slide.addEventListener( 'click', onOverviewSlideClicked, true );2019}2020} );20212022// Calculate slide sizes2023var margin = 70;2024var slideSize = getComputedSlideSize();2025overviewSlideWidth = slideSize.width + margin;2026overviewSlideHeight = slideSize.height + margin;20272028// Reverse in RTL mode2029if( config.rtl ) {2030overviewSlideWidth = -overviewSlideWidth;2031}20322033updateSlidesVisibility();2034layoutOverview();2035updateOverview();20362037layout();20382039// Notify observers of the overview showing2040dispatchEvent( 'overviewshown', {2041'indexh': indexh,2042'indexv': indexv,2043'currentSlide': currentSlide2044} );20452046}20472048}20492050/**2051* Uses CSS transforms to position all slides in a grid for2052* display inside of the overview mode.2053*/2054function layoutOverview() {20552056// Layout slides2057toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {2058hslide.setAttribute( 'data-index-h', h );2059transformElement( hslide, 'translate3d(' + ( h * overviewSlideWidth ) + 'px, 0, 0)' );20602061if( hslide.classList.contains( 'stack' ) ) {20622063toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {2064vslide.setAttribute( 'data-index-h', h );2065vslide.setAttribute( 'data-index-v', v );20662067transformElement( vslide, 'translate3d(0, ' + ( v * overviewSlideHeight ) + 'px, 0)' );2068} );20692070}2071} );20722073// Layout slide backgrounds2074toArray( dom.background.childNodes ).forEach( function( hbackground, h ) {2075transformElement( hbackground, 'translate3d(' + ( h * overviewSlideWidth ) + 'px, 0, 0)' );20762077toArray( hbackground.querySelectorAll( '.slide-background' ) ).forEach( function( vbackground, v ) {2078transformElement( vbackground, 'translate3d(0, ' + ( v * overviewSlideHeight ) + 'px, 0)' );2079} );2080} );20812082}20832084/**2085* Moves the overview viewport to the current slides.2086* Called each time the current slide changes.2087*/2088function updateOverview() {20892090var vmin = Math.min( window.innerWidth, window.innerHeight );2091var scale = Math.max( vmin / 5, 150 ) / vmin;20922093transformSlides( {2094overview: [2095'scale('+ scale +')',2096'translateX('+ ( -indexh * overviewSlideWidth ) +'px)',2097'translateY('+ ( -indexv * overviewSlideHeight ) +'px)'2098].join( ' ' )2099} );21002101}21022103/**2104* Exits the slide overview and enters the currently2105* active slide.2106*/2107function deactivateOverview() {21082109// Only proceed if enabled in config2110if( config.overview ) {21112112overview = false;21132114dom.wrapper.classList.remove( 'overview' );2115dom.wrapper.classList.remove( 'overview-animated' );21162117// Temporarily add a class so that transitions can do different things2118// depending on whether they are exiting/entering overview, or just2119// moving from slide to slide2120dom.wrapper.classList.add( 'overview-deactivating' );21212122setTimeout( function () {2123dom.wrapper.classList.remove( 'overview-deactivating' );2124}, 1 );21252126// Move the background element back out2127dom.wrapper.appendChild( dom.background );21282129// Clean up changes made to slides2130toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {2131transformElement( slide, '' );21322133slide.removeEventListener( 'click', onOverviewSlideClicked, true );2134} );21352136// Clean up changes made to backgrounds2137toArray( dom.background.querySelectorAll( '.slide-background' ) ).forEach( function( background ) {2138transformElement( background, '' );2139} );21402141transformSlides( { overview: '' } );21422143slide( indexh, indexv );21442145layout();21462147cueAutoSlide();21482149// Notify observers of the overview hiding2150dispatchEvent( 'overviewhidden', {2151'indexh': indexh,2152'indexv': indexv,2153'currentSlide': currentSlide2154} );21552156}2157}21582159/**2160* Toggles the slide overview mode on and off.2161*2162* @param {Boolean} [override] Flag which overrides the2163* toggle logic and forcibly sets the desired state. True means2164* overview is open, false means it's closed.2165*/2166function toggleOverview( override ) {21672168if( typeof override === 'boolean' ) {2169override ? activateOverview() : deactivateOverview();2170}2171else {2172isOverview() ? deactivateOverview() : activateOverview();2173}21742175}21762177/**2178* Checks if the overview is currently active.2179*2180* @return {Boolean} true if the overview is active,2181* false otherwise2182*/2183function isOverview() {21842185return overview;21862187}21882189/**2190* Checks if the current or specified slide is vertical2191* (nested within another slide).2192*2193* @param {HTMLElement} [slide=currentSlide] The slide to check2194* orientation of2195* @return {Boolean}2196*/2197function isVerticalSlide( slide ) {21982199// Prefer slide argument, otherwise use current slide2200slide = slide ? slide : currentSlide;22012202return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );22032204}22052206/**2207* Handling the fullscreen functionality via the fullscreen API2208*2209* @see http://fullscreen.spec.whatwg.org/2210* @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode2211*/2212function enterFullscreen() {22132214var element = document.documentElement;22152216// Check which implementation is available2217var requestMethod = element.requestFullscreen ||2218element.webkitRequestFullscreen ||2219element.webkitRequestFullScreen ||2220element.mozRequestFullScreen ||2221element.msRequestFullscreen;22222223if( requestMethod ) {2224requestMethod.apply( element );2225}22262227}22282229/**2230* Enters the paused mode which fades everything on screen to2231* black.2232*/2233function pause() {22342235if( config.pause ) {2236var wasPaused = dom.wrapper.classList.contains( 'paused' );22372238cancelAutoSlide();2239dom.wrapper.classList.add( 'paused' );22402241if( wasPaused === false ) {2242dispatchEvent( 'paused' );2243}2244}22452246}22472248/**2249* Exits from the paused mode.2250*/2251function resume() {22522253var wasPaused = dom.wrapper.classList.contains( 'paused' );2254dom.wrapper.classList.remove( 'paused' );22552256cueAutoSlide();22572258if( wasPaused ) {2259dispatchEvent( 'resumed' );2260}22612262}22632264/**2265* Toggles the paused mode on and off.2266*/2267function togglePause( override ) {22682269if( typeof override === 'boolean' ) {2270override ? pause() : resume();2271}2272else {2273isPaused() ? resume() : pause();2274}22752276}22772278/**2279* Checks if we are currently in the paused mode.2280*2281* @return {Boolean}2282*/2283function isPaused() {22842285return dom.wrapper.classList.contains( 'paused' );22862287}22882289/**2290* Toggles the auto slide mode on and off.2291*2292* @param {Boolean} [override] Flag which sets the desired state.2293* True means autoplay starts, false means it stops.2294*/22952296function toggleAutoSlide( override ) {22972298if( typeof override === 'boolean' ) {2299override ? resumeAutoSlide() : pauseAutoSlide();2300}23012302else {2303autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();2304}23052306}23072308/**2309* Checks if the auto slide mode is currently on.2310*2311* @return {Boolean}2312*/2313function isAutoSliding() {23142315return !!( autoSlide && !autoSlidePaused );23162317}23182319/**2320* Steps from the current point in the presentation to the2321* slide which matches the specified horizontal and vertical2322* indices.2323*2324* @param {number} [h=indexh] Horizontal index of the target slide2325* @param {number} [v=indexv] Vertical index of the target slide2326* @param {number} [f] Index of a fragment within the2327* target slide to activate2328* @param {number} [o] Origin for use in multimaster environments2329*/2330function slide( h, v, f, o ) {23312332// Remember where we were at before2333previousSlide = currentSlide;23342335// Query all horizontal slides in the deck2336var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );23372338// Abort if there are no slides2339if( horizontalSlides.length === 0 ) return;23402341// If no vertical index is specified and the upcoming slide is a2342// stack, resume at its previous vertical index2343if( v === undefined && !isOverview() ) {2344v = getPreviousVerticalIndex( horizontalSlides[ h ] );2345}23462347// If we were on a vertical stack, remember what vertical index2348// it was on so we can resume at the same position when returning2349if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {2350setPreviousVerticalIndex( previousSlide.parentNode, indexv );2351}23522353// Remember the state before this slide2354var stateBefore = state.concat();23552356// Reset the state array2357state.length = 0;23582359var indexhBefore = indexh || 0,2360indexvBefore = indexv || 0;23612362// Activate and transition to the new slide2363indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );2364indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );23652366// Update the visibility of slides now that the indices have changed2367updateSlidesVisibility();23682369layout();23702371// Apply the new state2372stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {2373// Check if this state existed on the previous slide. If it2374// did, we will avoid adding it repeatedly2375for( var j = 0; j < stateBefore.length; j++ ) {2376if( stateBefore[j] === state[i] ) {2377stateBefore.splice( j, 1 );2378continue stateLoop;2379}2380}23812382document.documentElement.classList.add( state[i] );23832384// Dispatch custom event matching the state's name2385dispatchEvent( state[i] );2386}23872388// Clean up the remains of the previous state2389while( stateBefore.length ) {2390document.documentElement.classList.remove( stateBefore.pop() );2391}23922393// Update the overview if it's currently active2394if( isOverview() ) {2395updateOverview();2396}23972398// Find the current horizontal slide and any possible vertical slides2399// within it2400var currentHorizontalSlide = horizontalSlides[ indexh ],2401currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );24022403// Store references to the previous and current slides2404currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;24052406// Show fragment, if specified2407if( typeof f !== 'undefined' ) {2408navigateFragment( f );2409}24102411// Dispatch an event if the slide changed2412var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );2413if( slideChanged ) {2414dispatchEvent( 'slidechanged', {2415'indexh': indexh,2416'indexv': indexv,2417'previousSlide': previousSlide,2418'currentSlide': currentSlide,2419'origin': o2420} );2421}2422else {2423// Ensure that the previous slide is never the same as the current2424previousSlide = null;2425}24262427// Solves an edge case where the previous slide maintains the2428// 'present' class when navigating between adjacent vertical2429// stacks2430if( previousSlide ) {2431previousSlide.classList.remove( 'present' );2432previousSlide.setAttribute( 'aria-hidden', 'true' );24332434// Reset all slides upon navigate to home2435// Issue: #2852436if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {2437// Launch async task2438setTimeout( function () {2439var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;2440for( i in slides ) {2441if( slides[i] ) {2442// Reset stack2443setPreviousVerticalIndex( slides[i], 0 );2444}2445}2446}, 0 );2447}2448}24492450// Handle embedded content2451if( slideChanged || !previousSlide ) {2452stopEmbeddedContent( previousSlide );2453startEmbeddedContent( currentSlide );2454}24552456// Announce the current slide contents, for screen readers2457dom.statusDiv.textContent = getStatusText( currentSlide );24582459updateControls();2460updateProgress();2461updateBackground();2462updateParallax();2463updateSlideNumber();2464updateNotes();24652466// Update the URL hash2467writeURL();24682469cueAutoSlide();24702471}24722473/**2474* Syncs the presentation with the current DOM. Useful2475* when new slides or control elements are added or when2476* the configuration has changed.2477*/2478function sync() {24792480// Subscribe to input2481removeEventListeners();2482addEventListeners();24832484// Force a layout to make sure the current config is accounted for2485layout();24862487// Reflect the current autoSlide value2488autoSlide = config.autoSlide;24892490// Start auto-sliding if it's enabled2491cueAutoSlide();24922493// Re-create the slide backgrounds2494createBackgrounds();24952496// Write the current hash to the URL2497writeURL();24982499sortAllFragments();25002501updateControls();2502updateProgress();2503updateSlideNumber();2504updateSlidesVisibility();2505updateBackground( true );2506updateNotesVisibility();2507updateNotes();25082509formatEmbeddedContent();25102511// Start or stop embedded content depending on global config2512if( config.autoPlayMedia === false ) {2513stopEmbeddedContent( currentSlide, { unloadIframes: false } );2514}2515else {2516startEmbeddedContent( currentSlide );2517}25182519if( isOverview() ) {2520layoutOverview();2521}25222523}25242525/**2526* Resets all vertical slides so that only the first2527* is visible.2528*/2529function resetVerticalSlides() {25302531var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );2532horizontalSlides.forEach( function( horizontalSlide ) {25332534var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );2535verticalSlides.forEach( function( verticalSlide, y ) {25362537if( y > 0 ) {2538verticalSlide.classList.remove( 'present' );2539verticalSlide.classList.remove( 'past' );2540verticalSlide.classList.add( 'future' );2541verticalSlide.setAttribute( 'aria-hidden', 'true' );2542}25432544} );25452546} );25472548}25492550/**2551* Sorts and formats all of fragments in the2552* presentation.2553*/2554function sortAllFragments() {25552556var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );2557horizontalSlides.forEach( function( horizontalSlide ) {25582559var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );2560verticalSlides.forEach( function( verticalSlide, y ) {25612562sortFragments( verticalSlide.querySelectorAll( '.fragment' ) );25632564} );25652566if( verticalSlides.length === 0 ) sortFragments( horizontalSlide.querySelectorAll( '.fragment' ) );25672568} );25692570}25712572/**2573* Randomly shuffles all slides in the deck.2574*/2575function shuffle() {25762577var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );25782579slides.forEach( function( slide ) {25802581// Insert this slide next to another random slide. This may2582// cause the slide to insert before itself but that's fine.2583dom.slides.insertBefore( slide, slides[ Math.floor( Math.random() * slides.length ) ] );25842585} );25862587}25882589/**2590* Updates one dimension of slides by showing the slide2591* with the specified index.2592*2593* @param {string} selector A CSS selector that will fetch2594* the group of slides we are working with2595* @param {number} index The index of the slide that should be2596* shown2597*2598* @return {number} The index of the slide that is now shown,2599* might differ from the passed in index if it was out of2600* bounds.2601*/2602function updateSlides( selector, index ) {26032604// Select all slides and convert the NodeList result to2605// an array2606var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),2607slidesLength = slides.length;26082609var printMode = isPrintingPDF();26102611if( slidesLength ) {26122613// Should the index loop?2614if( config.loop ) {2615index %= slidesLength;26162617if( index < 0 ) {2618index = slidesLength + index;2619}2620}26212622// Enforce max and minimum index bounds2623index = Math.max( Math.min( index, slidesLength - 1 ), 0 );26242625for( var i = 0; i < slidesLength; i++ ) {2626var element = slides[i];26272628var reverse = config.rtl && !isVerticalSlide( element );26292630element.classList.remove( 'past' );2631element.classList.remove( 'present' );2632element.classList.remove( 'future' );26332634// http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute2635element.setAttribute( 'hidden', '' );2636element.setAttribute( 'aria-hidden', 'true' );26372638// If this element contains vertical slides2639if( element.querySelector( 'section' ) ) {2640element.classList.add( 'stack' );2641}26422643// If we're printing static slides, all slides are "present"2644if( printMode ) {2645element.classList.add( 'present' );2646continue;2647}26482649if( i < index ) {2650// Any element previous to index is given the 'past' class2651element.classList.add( reverse ? 'future' : 'past' );26522653if( config.fragments ) {2654var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );26552656// Show all fragments on prior slides2657while( pastFragments.length ) {2658var pastFragment = pastFragments.pop();2659pastFragment.classList.add( 'visible' );2660pastFragment.classList.remove( 'current-fragment' );2661}2662}2663}2664else if( i > index ) {2665// Any element subsequent to index is given the 'future' class2666element.classList.add( reverse ? 'past' : 'future' );26672668if( config.fragments ) {2669var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );26702671// No fragments in future slides should be visible ahead of time2672while( futureFragments.length ) {2673var futureFragment = futureFragments.pop();2674futureFragment.classList.remove( 'visible' );2675futureFragment.classList.remove( 'current-fragment' );2676}2677}2678}2679}26802681// Mark the current slide as present2682slides[index].classList.add( 'present' );2683slides[index].removeAttribute( 'hidden' );2684slides[index].removeAttribute( 'aria-hidden' );26852686// If this slide has a state associated with it, add it2687// onto the current state of the deck2688var slideState = slides[index].getAttribute( 'data-state' );2689if( slideState ) {2690state = state.concat( slideState.split( ' ' ) );2691}26922693}2694else {2695// Since there are no slides we can't be anywhere beyond the2696// zeroth index2697index = 0;2698}26992700return index;27012702}27032704/**2705* Optimization method; hide all slides that are far away2706* from the present slide.2707*/2708function updateSlidesVisibility() {27092710// Select all slides and convert the NodeList result to2711// an array2712var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),2713horizontalSlidesLength = horizontalSlides.length,2714distanceX,2715distanceY;27162717if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {27182719// The number of steps away from the present slide that will2720// be visible2721var viewDistance = isOverview() ? 10 : config.viewDistance;27222723// Limit view distance on weaker devices2724if( isMobileDevice ) {2725viewDistance = isOverview() ? 6 : 2;2726}27272728// All slides need to be visible when exporting to PDF2729if( isPrintingPDF() ) {2730viewDistance = Number.MAX_VALUE;2731}27322733for( var x = 0; x < horizontalSlidesLength; x++ ) {2734var horizontalSlide = horizontalSlides[x];27352736var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),2737verticalSlidesLength = verticalSlides.length;27382739// Determine how far away this slide is from the present2740distanceX = Math.abs( ( indexh || 0 ) - x ) || 0;27412742// If the presentation is looped, distance should measure2743// 1 between the first and last slides2744if( config.loop ) {2745distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;2746}27472748// Show the horizontal slide if it's within the view distance2749if( distanceX < viewDistance ) {2750loadSlide( horizontalSlide );2751}2752else {2753unloadSlide( horizontalSlide );2754}27552756if( verticalSlidesLength ) {27572758var oy = getPreviousVerticalIndex( horizontalSlide );27592760for( var y = 0; y < verticalSlidesLength; y++ ) {2761var verticalSlide = verticalSlides[y];27622763distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );27642765if( distanceX + distanceY < viewDistance ) {2766loadSlide( verticalSlide );2767}2768else {2769unloadSlide( verticalSlide );2770}2771}27722773}2774}27752776// Flag if there are ANY vertical slides, anywhere in the deck2777if( dom.wrapper.querySelectorAll( '.slides>section>section' ).length ) {2778dom.wrapper.classList.add( 'has-vertical-slides' );2779}2780else {2781dom.wrapper.classList.remove( 'has-vertical-slides' );2782}27832784// Flag if there are ANY horizontal slides, anywhere in the deck2785if( dom.wrapper.querySelectorAll( '.slides>section' ).length > 1 ) {2786dom.wrapper.classList.add( 'has-horizontal-slides' );2787}2788else {2789dom.wrapper.classList.remove( 'has-horizontal-slides' );2790}27912792}27932794}27952796/**2797* Pick up notes from the current slide and display them2798* to the viewer.2799*2800* @see {@link config.showNotes}2801*/2802function updateNotes() {28032804if( config.showNotes && dom.speakerNotes && currentSlide && !isPrintingPDF() ) {28052806dom.speakerNotes.innerHTML = getSlideNotes() || '<span class="notes-placeholder">No notes on this slide.</span>';28072808}28092810}28112812/**2813* Updates the visibility of the speaker notes sidebar that2814* is used to share annotated slides. The notes sidebar is2815* only visible if showNotes is true and there are notes on2816* one or more slides in the deck.2817*/2818function updateNotesVisibility() {28192820if( config.showNotes && hasNotes() ) {2821dom.wrapper.classList.add( 'show-notes' );2822}2823else {2824dom.wrapper.classList.remove( 'show-notes' );2825}28262827}28282829/**2830* Checks if there are speaker notes for ANY slide in the2831* presentation.2832*/2833function hasNotes() {28342835return dom.slides.querySelectorAll( '[data-notes], aside.notes' ).length > 0;28362837}28382839/**2840* Updates the progress bar to reflect the current slide.2841*/2842function updateProgress() {28432844// Update progress if enabled2845if( config.progress && dom.progressbar ) {28462847dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';28482849}28502851}28522853/**2854* Updates the slide number div to reflect the current slide.2855*2856* The following slide number formats are available:2857* "h.v": horizontal . vertical slide number (default)2858* "h/v": horizontal / vertical slide number2859* "c": flattened slide number2860* "c/t": flattened slide number / total slides2861*/2862function updateSlideNumber() {28632864// Update slide number if enabled2865if( config.slideNumber && dom.slideNumber ) {28662867var value = [];2868var format = 'h.v';28692870// Check if a custom number format is available2871if( typeof config.slideNumber === 'string' ) {2872format = config.slideNumber;2873}28742875switch( format ) {2876case 'c':2877value.push( getSlidePastCount() + 1 );2878break;2879case 'c/t':2880value.push( getSlidePastCount() + 1, '/', getTotalSlides() );2881break;2882case 'h/v':2883value.push( indexh + 1 );2884if( isVerticalSlide() ) value.push( '/', indexv + 1 );2885break;2886default:2887value.push( indexh + 1 );2888if( isVerticalSlide() ) value.push( '.', indexv + 1 );2889}28902891dom.slideNumber.innerHTML = formatSlideNumber( value[0], value[1], value[2] );2892}28932894}28952896/**2897* Applies HTML formatting to a slide number before it's2898* written to the DOM.2899*2900* @param {number} a Current slide2901* @param {string} delimiter Character to separate slide numbers2902* @param {(number|*)} b Total slides2903* @return {string} HTML string fragment2904*/2905function formatSlideNumber( a, delimiter, b ) {29062907if( typeof b === 'number' && !isNaN( b ) ) {2908return '<span class="slide-number-a">'+ a +'</span>' +2909'<span class="slide-number-delimiter">'+ delimiter +'</span>' +2910'<span class="slide-number-b">'+ b +'</span>';2911}2912else {2913return '<span class="slide-number-a">'+ a +'</span>';2914}29152916}29172918/**2919* Updates the state of all control/navigation arrows.2920*/2921function updateControls() {29222923var routes = availableRoutes();2924var fragments = availableFragments();29252926// Remove the 'enabled' class from all directions2927dom.controlsLeft.concat( dom.controlsRight )2928.concat( dom.controlsUp )2929.concat( dom.controlsDown )2930.concat( dom.controlsPrev )2931.concat( dom.controlsNext ).forEach( function( node ) {2932node.classList.remove( 'enabled' );2933node.classList.remove( 'fragmented' );29342935// Set 'disabled' attribute on all directions2936node.setAttribute( 'disabled', 'disabled' );2937} );29382939// Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons2940if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );2941if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );2942if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );2943if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );29442945// Prev/next buttons2946if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );2947if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );29482949// Highlight fragment directions2950if( currentSlide ) {29512952// Always apply fragment decorator to prev/next buttons2953if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );2954if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );29552956// Apply fragment decorators to directional buttons based on2957// what slide axis they are in2958if( isVerticalSlide( currentSlide ) ) {2959if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );2960if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );2961}2962else {2963if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );2964if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );2965}29662967}29682969if( config.controlsTutorial ) {29702971// Highlight control arrows with an animation to ensure2972// that the viewer knows how to navigate2973if( !hasNavigatedDown && routes.down ) {2974dom.controlsDownArrow.classList.add( 'highlight' );2975}2976else {2977dom.controlsDownArrow.classList.remove( 'highlight' );29782979if( !hasNavigatedRight && routes.right && indexv === 0 ) {2980dom.controlsRightArrow.classList.add( 'highlight' );2981}2982else {2983dom.controlsRightArrow.classList.remove( 'highlight' );2984}2985}29862987}29882989}29902991/**2992* Updates the background elements to reflect the current2993* slide.2994*2995* @param {boolean} includeAll If true, the backgrounds of2996* all vertical slides (not just the present) will be updated.2997*/2998function updateBackground( includeAll ) {29993000var currentBackground = null;30013002// Reverse past/future classes when in RTL mode3003var horizontalPast = config.rtl ? 'future' : 'past',3004horizontalFuture = config.rtl ? 'past' : 'future';30053006// Update the classes of all backgrounds to match the3007// states of their slides (past/present/future)3008toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {30093010backgroundh.classList.remove( 'past' );3011backgroundh.classList.remove( 'present' );3012backgroundh.classList.remove( 'future' );30133014if( h < indexh ) {3015backgroundh.classList.add( horizontalPast );3016}3017else if ( h > indexh ) {3018backgroundh.classList.add( horizontalFuture );3019}3020else {3021backgroundh.classList.add( 'present' );30223023// Store a reference to the current background element3024currentBackground = backgroundh;3025}30263027if( includeAll || h === indexh ) {3028toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {30293030backgroundv.classList.remove( 'past' );3031backgroundv.classList.remove( 'present' );3032backgroundv.classList.remove( 'future' );30333034if( v < indexv ) {3035backgroundv.classList.add( 'past' );3036}3037else if ( v > indexv ) {3038backgroundv.classList.add( 'future' );3039}3040else {3041backgroundv.classList.add( 'present' );30423043// Only if this is the present horizontal and vertical slide3044if( h === indexh ) currentBackground = backgroundv;3045}30463047} );3048}30493050} );30513052// Stop content inside of previous backgrounds3053if( previousBackground ) {30543055stopEmbeddedContent( previousBackground );30563057}30583059// Start content in the current background3060if( currentBackground ) {30613062startEmbeddedContent( currentBackground );30633064var backgroundImageURL = currentBackground.style.backgroundImage || '';30653066// Restart GIFs (doesn't work in Firefox)3067if( /\.gif/i.test( backgroundImageURL ) ) {3068currentBackground.style.backgroundImage = '';3069window.getComputedStyle( currentBackground ).opacity;3070currentBackground.style.backgroundImage = backgroundImageURL;3071}30723073// Don't transition between identical backgrounds. This3074// prevents unwanted flicker.3075var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;3076var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );3077if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {3078dom.background.classList.add( 'no-transition' );3079}30803081previousBackground = currentBackground;30823083}30843085// If there's a background brightness flag for this slide,3086// bubble it to the .reveal container3087if( currentSlide ) {3088[ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {3089if( currentSlide.classList.contains( classToBubble ) ) {3090dom.wrapper.classList.add( classToBubble );3091}3092else {3093dom.wrapper.classList.remove( classToBubble );3094}3095} );3096}30973098// Allow the first background to apply without transition3099setTimeout( function() {3100dom.background.classList.remove( 'no-transition' );3101}, 1 );31023103}31043105/**3106* Updates the position of the parallax background based3107* on the current slide index.3108*/3109function updateParallax() {31103111if( config.parallaxBackgroundImage ) {31123113var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),3114verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );31153116var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),3117backgroundWidth, backgroundHeight;31183119if( backgroundSize.length === 1 ) {3120backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );3121}3122else {3123backgroundWidth = parseInt( backgroundSize[0], 10 );3124backgroundHeight = parseInt( backgroundSize[1], 10 );3125}31263127var slideWidth = dom.background.offsetWidth,3128horizontalSlideCount = horizontalSlides.length,3129horizontalOffsetMultiplier,3130horizontalOffset;31313132if( typeof config.parallaxBackgroundHorizontal === 'number' ) {3133horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;3134}3135else {3136horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0;3137}31383139horizontalOffset = horizontalOffsetMultiplier * indexh * -1;31403141var slideHeight = dom.background.offsetHeight,3142verticalSlideCount = verticalSlides.length,3143verticalOffsetMultiplier,3144verticalOffset;31453146if( typeof config.parallaxBackgroundVertical === 'number' ) {3147verticalOffsetMultiplier = config.parallaxBackgroundVertical;3148}3149else {3150verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );3151}31523153verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv : 0;31543155dom.background.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';31563157}31583159}31603161/**3162* Called when the given slide is within the configured view3163* distance. Shows the slide element and loads any content3164* that is set to load lazily (data-src).3165*3166* @param {HTMLElement} slide Slide to show3167*/3168function loadSlide( slide, options ) {31693170options = options || {};31713172// Show the slide element3173slide.style.display = config.display;31743175// Media elements with data-src attributes3176toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {3177element.setAttribute( 'src', element.getAttribute( 'data-src' ) );3178element.setAttribute( 'data-lazy-loaded', '' );3179element.removeAttribute( 'data-src' );3180} );31813182// Media elements with <source> children3183toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {3184var sources = 0;31853186toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {3187source.setAttribute( 'src', source.getAttribute( 'data-src' ) );3188source.removeAttribute( 'data-src' );3189source.setAttribute( 'data-lazy-loaded', '' );3190sources += 1;3191} );31923193// If we rewrote sources for this video/audio element, we need3194// to manually tell it to load from its new origin3195if( sources > 0 ) {3196media.load();3197}3198} );319932003201// Show the corresponding background element3202var indices = getIndices( slide );3203var background = getSlideBackground( indices.h, indices.v );3204if( background ) {3205background.style.display = 'block';32063207// If the background contains media, load it3208if( background.hasAttribute( 'data-loaded' ) === false ) {3209background.setAttribute( 'data-loaded', 'true' );32103211var backgroundImage = slide.getAttribute( 'data-background-image' ),3212backgroundVideo = slide.getAttribute( 'data-background-video' ),3213backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),3214backgroundVideoMuted = slide.hasAttribute( 'data-background-video-muted' ),3215backgroundIframe = slide.getAttribute( 'data-background-iframe' );32163217// Images3218if( backgroundImage ) {3219background.style.backgroundImage = 'url('+ backgroundImage +')';3220}3221// Videos3222else if ( backgroundVideo && !isSpeakerNotes() ) {3223var video = document.createElement( 'video' );32243225if( backgroundVideoLoop ) {3226video.setAttribute( 'loop', '' );3227}32283229if( backgroundVideoMuted ) {3230video.muted = true;3231}32323233// Inline video playback works (at least in Mobile Safari) as3234// long as the video is muted and the `playsinline` attribute is3235// present3236if( isMobileDevice ) {3237video.muted = true;3238video.autoplay = true;3239video.setAttribute( 'playsinline', '' );3240}32413242// Support comma separated lists of video sources3243backgroundVideo.split( ',' ).forEach( function( source ) {3244video.innerHTML += '<source src="'+ source +'">';3245} );32463247background.appendChild( video );3248}3249// Iframes3250else if( backgroundIframe && options.excludeIframes !== true ) {3251var iframe = document.createElement( 'iframe' );3252iframe.setAttribute( 'allowfullscreen', '' );3253iframe.setAttribute( 'mozallowfullscreen', '' );3254iframe.setAttribute( 'webkitallowfullscreen', '' );32553256// Only load autoplaying content when the slide is shown to3257// avoid having it play in the background3258if( /autoplay=(1|true|yes)/gi.test( backgroundIframe ) ) {3259iframe.setAttribute( 'data-src', backgroundIframe );3260}3261else {3262iframe.setAttribute( 'src', backgroundIframe );3263}32643265iframe.style.width = '100%';3266iframe.style.height = '100%';3267iframe.style.maxHeight = '100%';3268iframe.style.maxWidth = '100%';32693270background.appendChild( iframe );3271}3272}32733274}32753276}32773278/**3279* Unloads and hides the given slide. This is called when the3280* slide is moved outside of the configured view distance.3281*3282* @param {HTMLElement} slide3283*/3284function unloadSlide( slide ) {32853286// Hide the slide element3287slide.style.display = 'none';32883289// Hide the corresponding background element3290var indices = getIndices( slide );3291var background = getSlideBackground( indices.h, indices.v );3292if( background ) {3293background.style.display = 'none';3294}32953296// Reset lazy-loaded media elements with src attributes3297toArray( slide.querySelectorAll( 'video[data-lazy-loaded][src], audio[data-lazy-loaded][src]' ) ).forEach( function( element ) {3298element.setAttribute( 'data-src', element.getAttribute( 'src' ) );3299element.removeAttribute( 'src' );3300} );33013302// Reset lazy-loaded media elements with <source> children3303toArray( slide.querySelectorAll( 'video[data-lazy-loaded] source[src], audio source[src]' ) ).forEach( function( source ) {3304source.setAttribute( 'data-src', source.getAttribute( 'src' ) );3305source.removeAttribute( 'src' );3306} );33073308}33093310/**3311* Determine what available routes there are for navigation.3312*3313* @return {{left: boolean, right: boolean, up: boolean, down: boolean}}3314*/3315function availableRoutes() {33163317var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),3318verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );33193320var routes = {3321left: indexh > 0 || config.loop,3322right: indexh < horizontalSlides.length - 1 || config.loop,3323up: indexv > 0,3324down: indexv < verticalSlides.length - 13325};33263327// reverse horizontal controls for rtl3328if( config.rtl ) {3329var left = routes.left;3330routes.left = routes.right;3331routes.right = left;3332}33333334return routes;33353336}33373338/**3339* Returns an object describing the available fragment3340* directions.3341*3342* @return {{prev: boolean, next: boolean}}3343*/3344function availableFragments() {33453346if( currentSlide && config.fragments ) {3347var fragments = currentSlide.querySelectorAll( '.fragment' );3348var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );33493350return {3351prev: fragments.length - hiddenFragments.length > 0,3352next: !!hiddenFragments.length3353};3354}3355else {3356return { prev: false, next: false };3357}33583359}33603361/**3362* Enforces origin-specific format rules for embedded media.3363*/3364function formatEmbeddedContent() {33653366var _appendParamToIframeSource = function( sourceAttribute, sourceURL, param ) {3367toArray( dom.slides.querySelectorAll( 'iframe['+ sourceAttribute +'*="'+ sourceURL +'"]' ) ).forEach( function( el ) {3368var src = el.getAttribute( sourceAttribute );3369if( src && src.indexOf( param ) === -1 ) {3370el.setAttribute( sourceAttribute, src + ( !/\?/.test( src ) ? '?' : '&' ) + param );3371}3372});3373};33743375// YouTube frames must include "?enablejsapi=1"3376_appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );3377_appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );33783379// Vimeo frames must include "?api=1"3380_appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );3381_appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );33823383// Always show media controls on mobile devices3384if( isMobileDevice ) {3385toArray( dom.slides.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {3386el.controls = true;3387} );3388}33893390}33913392/**3393* Start playback of any embedded content inside of3394* the given element.3395*3396* @param {HTMLElement} element3397*/3398function startEmbeddedContent( element ) {33993400if( element && !isSpeakerNotes() ) {34013402// Restart GIFs3403toArray( element.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {3404// Setting the same unchanged source like this was confirmed3405// to work in Chrome, FF & Safari3406el.setAttribute( 'src', el.getAttribute( 'src' ) );3407} );34083409// HTML5 media elements3410toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {3411if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {3412return;3413}34143415// Prefer an explicit global autoplay setting3416var autoplay = config.autoPlayMedia;34173418// If no global setting is available, fall back on the element's3419// own autoplay setting3420if( typeof autoplay !== 'boolean' ) {3421autoplay = el.hasAttribute( 'data-autoplay' ) || !!closestParent( el, '.slide-background' );3422}34233424if( autoplay && typeof el.play === 'function' ) {34253426if( el.readyState > 1 ) {3427startEmbeddedMedia( { target: el } );3428}3429else {3430el.removeEventListener( 'loadeddata', startEmbeddedMedia ); // remove first to avoid dupes3431el.addEventListener( 'loadeddata', startEmbeddedMedia );3432}34333434}3435} );34363437// Normal iframes3438toArray( element.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {3439if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {3440return;3441}34423443startEmbeddedIframe( { target: el } );3444} );34453446// Lazy loading iframes3447toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {3448if( closestParent( el, '.fragment' ) && !closestParent( el, '.fragment.visible' ) ) {3449return;3450}34513452if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {3453el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes3454el.addEventListener( 'load', startEmbeddedIframe );3455el.setAttribute( 'src', el.getAttribute( 'data-src' ) );3456}3457} );34583459}34603461}34623463/**3464* Starts playing an embedded video/audio element after3465* it has finished loading.3466*3467* @param {object} event3468*/3469function startEmbeddedMedia( event ) {34703471var isAttachedToDOM = !!closestParent( event.target, 'html' ),3472isVisible = !!closestParent( event.target, '.present' );34733474if( isAttachedToDOM && isVisible ) {3475event.target.currentTime = 0;3476event.target.play();3477}34783479event.target.removeEventListener( 'loadeddata', startEmbeddedMedia );34803481}34823483/**3484* "Starts" the content of an embedded iframe using the3485* postMessage API.3486*3487* @param {object} event3488*/3489function startEmbeddedIframe( event ) {34903491var iframe = event.target;34923493if( iframe && iframe.contentWindow ) {34943495var isAttachedToDOM = !!closestParent( event.target, 'html' ),3496isVisible = !!closestParent( event.target, '.present' );34973498if( isAttachedToDOM && isVisible ) {34993500// Prefer an explicit global autoplay setting3501var autoplay = config.autoPlayMedia;35023503// If no global setting is available, fall back on the element's3504// own autoplay setting3505if( typeof autoplay !== 'boolean' ) {3506autoplay = iframe.hasAttribute( 'data-autoplay' ) || !!closestParent( iframe, '.slide-background' );3507}35083509// YouTube postMessage API3510if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {3511iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );3512}3513// Vimeo postMessage API3514else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && autoplay ) {3515iframe.contentWindow.postMessage( '{"method":"play"}', '*' );3516}3517// Generic postMessage API3518else {3519iframe.contentWindow.postMessage( 'slide:start', '*' );3520}35213522}35233524}35253526}35273528/**3529* Stop playback of any embedded content inside of3530* the targeted slide.3531*3532* @param {HTMLElement} element3533*/3534function stopEmbeddedContent( element, options ) {35353536options = extend( {3537// Defaults3538unloadIframes: true3539}, options || {} );35403541if( element && element.parentNode ) {3542// HTML5 media elements3543toArray( element.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {3544if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {3545el.setAttribute('data-paused-by-reveal', '');3546el.pause();3547}3548} );35493550// Generic postMessage API for non-lazy loaded iframes3551toArray( element.querySelectorAll( 'iframe' ) ).forEach( function( el ) {3552if( el.contentWindow ) el.contentWindow.postMessage( 'slide:stop', '*' );3553el.removeEventListener( 'load', startEmbeddedIframe );3554});35553556// YouTube postMessage API3557toArray( element.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {3558if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {3559el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );3560}3561});35623563// Vimeo postMessage API3564toArray( element.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {3565if( !el.hasAttribute( 'data-ignore' ) && el.contentWindow && typeof el.contentWindow.postMessage === 'function' ) {3566el.contentWindow.postMessage( '{"method":"pause"}', '*' );3567}3568});35693570if( options.unloadIframes === true ) {3571// Unload lazy-loaded iframes3572toArray( element.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {3573// Only removing the src doesn't actually unload the frame3574// in all browsers (Firefox) so we set it to blank first3575el.setAttribute( 'src', 'about:blank' );3576el.removeAttribute( 'src' );3577} );3578}3579}35803581}35823583/**3584* Returns the number of past slides. This can be used as a global3585* flattened index for slides.3586*3587* @return {number} Past slide count3588*/3589function getSlidePastCount() {35903591var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );35923593// The number of past slides3594var pastCount = 0;35953596// Step through all slides and count the past ones3597mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {35983599var horizontalSlide = horizontalSlides[i];3600var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );36013602for( var j = 0; j < verticalSlides.length; j++ ) {36033604// Stop as soon as we arrive at the present3605if( verticalSlides[j].classList.contains( 'present' ) ) {3606break mainLoop;3607}36083609pastCount++;36103611}36123613// Stop as soon as we arrive at the present3614if( horizontalSlide.classList.contains( 'present' ) ) {3615break;3616}36173618// Don't count the wrapping section for vertical slides3619if( horizontalSlide.classList.contains( 'stack' ) === false ) {3620pastCount++;3621}36223623}36243625return pastCount;36263627}36283629/**3630* Returns a value ranging from 0-1 that represents3631* how far into the presentation we have navigated.3632*3633* @return {number}3634*/3635function getProgress() {36363637// The number of past and total slides3638var totalCount = getTotalSlides();3639var pastCount = getSlidePastCount();36403641if( currentSlide ) {36423643var allFragments = currentSlide.querySelectorAll( '.fragment' );36443645// If there are fragments in the current slide those should be3646// accounted for in the progress.3647if( allFragments.length > 0 ) {3648var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );36493650// This value represents how big a portion of the slide progress3651// that is made up by its fragments (0-1)3652var fragmentWeight = 0.9;36533654// Add fragment progress to the past slide count3655pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;3656}36573658}36593660return pastCount / ( totalCount - 1 );36613662}36633664/**3665* Checks if this presentation is running inside of the3666* speaker notes window.3667*3668* @return {boolean}3669*/3670function isSpeakerNotes() {36713672return !!window.location.search.match( /receiver/gi );36733674}36753676/**3677* Reads the current URL (hash) and navigates accordingly.3678*/3679function readURL() {36803681var hash = window.location.hash;36823683// Attempt to parse the hash as either an index or name3684var bits = hash.slice( 2 ).split( '/' ),3685name = hash.replace( /#|\//gi, '' );36863687// If the first bit is invalid and there is a name we can3688// assume that this is a named link3689if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {3690var element;36913692// Ensure the named link is a valid HTML ID attribute3693if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {3694// Find the slide with the specified ID3695element = document.getElementById( name );3696}36973698if( element ) {3699// Find the position of the named slide and navigate to it3700var indices = Reveal.getIndices( element );3701slide( indices.h, indices.v );3702}3703// If the slide doesn't exist, navigate to the current slide3704else {3705slide( indexh || 0, indexv || 0 );3706}3707}3708else {3709// Read the index components of the hash3710var h = parseInt( bits[0], 10 ) || 0,3711v = parseInt( bits[1], 10 ) || 0;37123713if( h !== indexh || v !== indexv ) {3714slide( h, v );3715}3716}37173718}37193720/**3721* Updates the page URL (hash) to reflect the current3722* state.3723*3724* @param {number} delay The time in ms to wait before3725* writing the hash3726*/3727function writeURL( delay ) {37283729if( config.history ) {37303731// Make sure there's never more than one timeout running3732clearTimeout( writeURLTimeout );37333734// If a delay is specified, timeout this call3735if( typeof delay === 'number' ) {3736writeURLTimeout = setTimeout( writeURL, delay );3737}3738else if( currentSlide ) {3739var url = '/';37403741// Attempt to create a named link based on the slide's ID3742var id = currentSlide.getAttribute( 'id' );3743if( id ) {3744id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );3745}37463747// If the current slide has an ID, use that as a named link3748if( typeof id === 'string' && id.length ) {3749url = '/' + id;3750}3751// Otherwise use the /h/v index3752else {3753if( indexh > 0 || indexv > 0 ) url += indexh;3754if( indexv > 0 ) url += '/' + indexv;3755}37563757window.location.hash = url;3758}3759}37603761}3762/**3763* Retrieves the h/v location and fragment of the current,3764* or specified, slide.3765*3766* @param {HTMLElement} [slide] If specified, the returned3767* index will be for this slide rather than the currently3768* active one3769*3770* @return {{h: number, v: number, f: number}}3771*/3772function getIndices( slide ) {37733774// By default, return the current indices3775var h = indexh,3776v = indexv,3777f;37783779// If a slide is specified, return the indices of that slide3780if( slide ) {3781var isVertical = isVerticalSlide( slide );3782var slideh = isVertical ? slide.parentNode : slide;37833784// Select all horizontal slides3785var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );37863787// Now that we know which the horizontal slide is, get its index3788h = Math.max( horizontalSlides.indexOf( slideh ), 0 );37893790// Assume we're not vertical3791v = undefined;37923793// If this is a vertical slide, grab the vertical index3794if( isVertical ) {3795v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );3796}3797}37983799if( !slide && currentSlide ) {3800var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;3801if( hasFragments ) {3802var currentFragment = currentSlide.querySelector( '.current-fragment' );3803if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {3804f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );3805}3806else {3807f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;3808}3809}3810}38113812return { h: h, v: v, f: f };38133814}38153816/**3817* Retrieves all slides in this presentation.3818*/3819function getSlides() {38203821return toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ));38223823}38243825/**3826* Retrieves the total number of slides in this presentation.3827*3828* @return {number}3829*/3830function getTotalSlides() {38313832return getSlides().length;38333834}38353836/**3837* Returns the slide element matching the specified index.3838*3839* @return {HTMLElement}3840*/3841function getSlide( x, y ) {38423843var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];3844var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );38453846if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {3847return verticalSlides ? verticalSlides[ y ] : undefined;3848}38493850return horizontalSlide;38513852}38533854/**3855* Returns the background element for the given slide.3856* All slides, even the ones with no background properties3857* defined, have a background element so as long as the3858* index is valid an element will be returned.3859*3860* @param {number} x Horizontal background index3861* @param {number} y Vertical background index3862* @return {(HTMLElement[]|*)}3863*/3864function getSlideBackground( x, y ) {38653866var slide = getSlide( x, y );3867if( slide ) {3868return slide.slideBackgroundElement;3869}38703871return undefined;38723873}38743875/**3876* Retrieves the speaker notes from a slide. Notes can be3877* defined in two ways:3878* 1. As a data-notes attribute on the slide <section>3879* 2. As an <aside class="notes"> inside of the slide3880*3881* @param {HTMLElement} [slide=currentSlide]3882* @return {(string|null)}3883*/3884function getSlideNotes( slide ) {38853886// Default to the current slide3887slide = slide || currentSlide;38883889// Notes can be specified via the data-notes attribute...3890if( slide.hasAttribute( 'data-notes' ) ) {3891return slide.getAttribute( 'data-notes' );3892}38933894// ... or using an <aside class="notes"> element3895var notesElement = slide.querySelector( 'aside.notes' );3896if( notesElement ) {3897return notesElement.innerHTML;3898}38993900return null;39013902}39033904/**3905* Retrieves the current state of the presentation as3906* an object. This state can then be restored at any3907* time.3908*3909* @return {{indexh: number, indexv: number, indexf: number, paused: boolean, overview: boolean}}3910*/3911function getState() {39123913var indices = getIndices();39143915return {3916indexh: indices.h,3917indexv: indices.v,3918indexf: indices.f,3919paused: isPaused(),3920overview: isOverview()3921};39223923}39243925/**3926* Restores the presentation to the given state.3927*3928* @param {object} state As generated by getState()3929* @see {@link getState} generates the parameter `state`3930*/3931function setState( state ) {39323933if( typeof state === 'object' ) {3934slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );39353936var pausedFlag = deserialize( state.paused ),3937overviewFlag = deserialize( state.overview );39383939if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {3940togglePause( pausedFlag );3941}39423943if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {3944toggleOverview( overviewFlag );3945}3946}39473948}39493950/**3951* Return a sorted fragments list, ordered by an increasing3952* "data-fragment-index" attribute.3953*3954* Fragments will be revealed in the order that they are returned by3955* this function, so you can use the index attributes to control the3956* order of fragment appearance.3957*3958* To maintain a sensible default fragment order, fragments are presumed3959* to be passed in document order. This function adds a "fragment-index"3960* attribute to each node if such an attribute is not already present,3961* and sets that attribute to an integer value which is the position of3962* the fragment within the fragments list.3963*3964* @param {object[]|*} fragments3965* @return {object[]} sorted Sorted array of fragments3966*/3967function sortFragments( fragments ) {39683969fragments = toArray( fragments );39703971var ordered = [],3972unordered = [],3973sorted = [];39743975// Group ordered and unordered elements3976fragments.forEach( function( fragment, i ) {3977if( fragment.hasAttribute( 'data-fragment-index' ) ) {3978var index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );39793980if( !ordered[index] ) {3981ordered[index] = [];3982}39833984ordered[index].push( fragment );3985}3986else {3987unordered.push( [ fragment ] );3988}3989} );39903991// Append fragments without explicit indices in their3992// DOM order3993ordered = ordered.concat( unordered );39943995// Manually count the index up per group to ensure there3996// are no gaps3997var index = 0;39983999// Push all fragments in their sorted order to an array,4000// this flattens the groups4001ordered.forEach( function( group ) {4002group.forEach( function( fragment ) {4003sorted.push( fragment );4004fragment.setAttribute( 'data-fragment-index', index );4005} );40064007index ++;4008} );40094010return sorted;40114012}40134014/**4015* Navigate to the specified slide fragment.4016*4017* @param {?number} index The index of the fragment that4018* should be shown, -1 means all are invisible4019* @param {number} offset Integer offset to apply to the4020* fragment index4021*4022* @return {boolean} true if a change was made in any4023* fragments visibility as part of this call4024*/4025function navigateFragment( index, offset ) {40264027if( currentSlide && config.fragments ) {40284029var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );4030if( fragments.length ) {40314032// If no index is specified, find the current4033if( typeof index !== 'number' ) {4034var lastVisibleFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();40354036if( lastVisibleFragment ) {4037index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );4038}4039else {4040index = -1;4041}4042}40434044// If an offset is specified, apply it to the index4045if( typeof offset === 'number' ) {4046index += offset;4047}40484049var fragmentsShown = [],4050fragmentsHidden = [];40514052toArray( fragments ).forEach( function( element, i ) {40534054if( element.hasAttribute( 'data-fragment-index' ) ) {4055i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );4056}40574058// Visible fragments4059if( i <= index ) {4060if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );4061element.classList.add( 'visible' );4062element.classList.remove( 'current-fragment' );40634064// Announce the fragments one by one to the Screen Reader4065dom.statusDiv.textContent = getStatusText( element );40664067if( i === index ) {4068element.classList.add( 'current-fragment' );4069startEmbeddedContent( element );4070}4071}4072// Hidden fragments4073else {4074if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );4075element.classList.remove( 'visible' );4076element.classList.remove( 'current-fragment' );4077}40784079} );40804081if( fragmentsHidden.length ) {4082dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );4083}40844085if( fragmentsShown.length ) {4086dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );4087}40884089updateControls();4090updateProgress();40914092return !!( fragmentsShown.length || fragmentsHidden.length );40934094}40954096}40974098return false;40994100}41014102/**4103* Navigate to the next slide fragment.4104*4105* @return {boolean} true if there was a next fragment,4106* false otherwise4107*/4108function nextFragment() {41094110return navigateFragment( null, 1 );41114112}41134114/**4115* Navigate to the previous slide fragment.4116*4117* @return {boolean} true if there was a previous fragment,4118* false otherwise4119*/4120function previousFragment() {41214122return navigateFragment( null, -1 );41234124}41254126/**4127* Cues a new automated slide if enabled in the config.4128*/4129function cueAutoSlide() {41304131cancelAutoSlide();41324133if( currentSlide && config.autoSlide !== false ) {41344135var fragment = currentSlide.querySelector( '.current-fragment' );41364137// When the slide first appears there is no "current" fragment so4138// we look for a data-autoslide timing on the first fragment4139if( !fragment ) fragment = currentSlide.querySelector( '.fragment' );41404141var fragmentAutoSlide = fragment ? fragment.getAttribute( 'data-autoslide' ) : null;4142var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;4143var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );41444145// Pick value in the following priority order:4146// 1. Current fragment's data-autoslide4147// 2. Current slide's data-autoslide4148// 3. Parent slide's data-autoslide4149// 4. Global autoSlide setting4150if( fragmentAutoSlide ) {4151autoSlide = parseInt( fragmentAutoSlide, 10 );4152}4153else if( slideAutoSlide ) {4154autoSlide = parseInt( slideAutoSlide, 10 );4155}4156else if( parentAutoSlide ) {4157autoSlide = parseInt( parentAutoSlide, 10 );4158}4159else {4160autoSlide = config.autoSlide;4161}41624163// If there are media elements with data-autoplay,4164// automatically set the autoSlide duration to the4165// length of that media. Not applicable if the slide4166// is divided up into fragments.4167// playbackRate is accounted for in the duration.4168if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {4169toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {4170if( el.hasAttribute( 'data-autoplay' ) ) {4171if( autoSlide && (el.duration * 1000 / el.playbackRate ) > autoSlide ) {4172autoSlide = ( el.duration * 1000 / el.playbackRate ) + 1000;4173}4174}4175} );4176}41774178// Cue the next auto-slide if:4179// - There is an autoSlide value4180// - Auto-sliding isn't paused by the user4181// - The presentation isn't paused4182// - The overview isn't active4183// - The presentation isn't over4184if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {4185autoSlideTimeout = setTimeout( function() {4186typeof config.autoSlideMethod === 'function' ? config.autoSlideMethod() : navigateNext();4187cueAutoSlide();4188}, autoSlide );4189autoSlideStartTime = Date.now();4190}41914192if( autoSlidePlayer ) {4193autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );4194}41954196}41974198}41994200/**4201* Cancels any ongoing request to auto-slide.4202*/4203function cancelAutoSlide() {42044205clearTimeout( autoSlideTimeout );4206autoSlideTimeout = -1;42074208}42094210function pauseAutoSlide() {42114212if( autoSlide && !autoSlidePaused ) {4213autoSlidePaused = true;4214dispatchEvent( 'autoslidepaused' );4215clearTimeout( autoSlideTimeout );42164217if( autoSlidePlayer ) {4218autoSlidePlayer.setPlaying( false );4219}4220}42214222}42234224function resumeAutoSlide() {42254226if( autoSlide && autoSlidePaused ) {4227autoSlidePaused = false;4228dispatchEvent( 'autoslideresumed' );4229cueAutoSlide();4230}42314232}42334234function navigateLeft() {42354236// Reverse for RTL4237if( config.rtl ) {4238if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {4239slide( indexh + 1 );4240}4241}4242// Normal navigation4243else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {4244slide( indexh - 1 );4245}42464247}42484249function navigateRight() {42504251hasNavigatedRight = true;42524253// Reverse for RTL4254if( config.rtl ) {4255if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {4256slide( indexh - 1 );4257}4258}4259// Normal navigation4260else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {4261slide( indexh + 1 );4262}42634264}42654266function navigateUp() {42674268// Prioritize hiding fragments4269if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {4270slide( indexh, indexv - 1 );4271}42724273}42744275function navigateDown() {42764277hasNavigatedDown = true;42784279// Prioritize revealing fragments4280if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {4281slide( indexh, indexv + 1 );4282}42834284}42854286/**4287* Navigates backwards, prioritized in the following order:4288* 1) Previous fragment4289* 2) Previous vertical slide4290* 3) Previous horizontal slide4291*/4292function navigatePrev() {42934294// Prioritize revealing fragments4295if( previousFragment() === false ) {4296if( availableRoutes().up ) {4297navigateUp();4298}4299else {4300// Fetch the previous horizontal slide, if there is one4301var previousSlide;43024303if( config.rtl ) {4304previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();4305}4306else {4307previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();4308}43094310if( previousSlide ) {4311var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;4312var h = indexh - 1;4313slide( h, v );4314}4315}4316}43174318}43194320/**4321* The reverse of #navigatePrev().4322*/4323function navigateNext() {43244325hasNavigatedRight = true;4326hasNavigatedDown = true;43274328// Prioritize revealing fragments4329if( nextFragment() === false ) {4330if( availableRoutes().down ) {4331navigateDown();4332}4333else if( config.rtl ) {4334navigateLeft();4335}4336else {4337navigateRight();4338}4339}43404341}43424343/**4344* Checks if the target element prevents the triggering of4345* swipe navigation.4346*/4347function isSwipePrevented( target ) {43484349while( target && typeof target.hasAttribute === 'function' ) {4350if( target.hasAttribute( 'data-prevent-swipe' ) ) return true;4351target = target.parentNode;4352}43534354return false;43554356}435743584359// --------------------------------------------------------------------//4360// ----------------------------- EVENTS -------------------------------//4361// --------------------------------------------------------------------//43624363/**4364* Called by all event handlers that are based on user4365* input.4366*4367* @param {object} [event]4368*/4369function onUserInput( event ) {43704371if( config.autoSlideStoppable ) {4372pauseAutoSlide();4373}43744375}43764377/**4378* Handler for the document level 'keypress' event.4379*4380* @param {object} event4381*/4382function onDocumentKeyPress( event ) {43834384// Check if the pressed key is question mark4385if( event.shiftKey && event.charCode === 63 ) {4386toggleHelp();4387}43884389}43904391/**4392* Handler for the document level 'keydown' event.4393*4394* @param {object} event4395*/4396function onDocumentKeyDown( event ) {43974398// If there's a condition specified and it returns false,4399// ignore this event4400if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {4401return true;4402}44034404// Remember if auto-sliding was paused so we can toggle it4405var autoSlideWasPaused = autoSlidePaused;44064407onUserInput( event );44084409// Check if there's a focused element that could be using4410// the keyboard4411var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';4412var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );4413var activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);44144415// Disregard the event if there's a focused element or a4416// keyboard modifier key is present4417if( activeElementIsCE || activeElementIsInput || activeElementIsNotes || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;44184419// While paused only allow resume keyboard events; 'b', 'v', '.'4420var resumeKeyCodes = [66,86,190,191];4421var key;44224423// Custom key bindings for togglePause should be able to resume4424if( typeof config.keyboard === 'object' ) {4425for( key in config.keyboard ) {4426if( config.keyboard[key] === 'togglePause' ) {4427resumeKeyCodes.push( parseInt( key, 10 ) );4428}4429}4430}44314432if( isPaused() && resumeKeyCodes.indexOf( event.keyCode ) === -1 ) {4433return false;4434}44354436var triggered = false;44374438// 1. User defined key bindings4439if( typeof config.keyboard === 'object' ) {44404441for( key in config.keyboard ) {44424443// Check if this binding matches the pressed key4444if( parseInt( key, 10 ) === event.keyCode ) {44454446var value = config.keyboard[ key ];44474448// Callback function4449if( typeof value === 'function' ) {4450value.apply( null, [ event ] );4451}4452// String shortcuts to reveal.js API4453else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {4454Reveal[ value ].call();4455}44564457triggered = true;44584459}44604461}44624463}44644465// 2. System defined key bindings4466if( triggered === false ) {44674468// Assume true and try to prove false4469triggered = true;44704471switch( event.keyCode ) {4472// p, page up4473case 80: case 33: navigatePrev(); break;4474// n, page down4475case 78: case 34: navigateNext(); break;4476// h, left4477case 72: case 37: navigateLeft(); break;4478// l, right4479case 76: case 39: navigateRight(); break;4480// k, up4481case 75: case 38: navigateUp(); break;4482// j, down4483case 74: case 40: navigateDown(); break;4484// home4485case 36: slide( 0 ); break;4486// end4487case 35: slide( Number.MAX_VALUE ); break;4488// space4489case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;4490// return4491case 13: isOverview() ? deactivateOverview() : triggered = false; break;4492// two-spot, semicolon, b, v, period, Logitech presenter tools "black screen" button4493case 58: case 59: case 66: case 86: case 190: case 191: togglePause(); break;4494// f4495case 70: enterFullscreen(); break;4496// a4497case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;4498default:4499triggered = false;4500}45014502}45034504// If the input resulted in a triggered action we should prevent4505// the browsers default behavior4506if( triggered ) {4507event.preventDefault && event.preventDefault();4508}4509// ESC or O key4510else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {4511if( dom.overlay ) {4512closeOverlay();4513}4514else {4515toggleOverview();4516}45174518event.preventDefault && event.preventDefault();4519}45204521// If auto-sliding is enabled we need to cue up4522// another timeout4523cueAutoSlide();45244525}45264527/**4528* Handler for the 'touchstart' event, enables support for4529* swipe and pinch gestures.4530*4531* @param {object} event4532*/4533function onTouchStart( event ) {45344535if( isSwipePrevented( event.target ) ) return true;45364537touch.startX = event.touches[0].clientX;4538touch.startY = event.touches[0].clientY;4539touch.startCount = event.touches.length;45404541// If there's two touches we need to memorize the distance4542// between those two points to detect pinching4543if( event.touches.length === 2 && config.overview ) {4544touch.startSpan = distanceBetween( {4545x: event.touches[1].clientX,4546y: event.touches[1].clientY4547}, {4548x: touch.startX,4549y: touch.startY4550} );4551}45524553}45544555/**4556* Handler for the 'touchmove' event.4557*4558* @param {object} event4559*/4560function onTouchMove( event ) {45614562if( isSwipePrevented( event.target ) ) return true;45634564// Each touch should only trigger one action4565if( !touch.captured ) {4566onUserInput( event );45674568var currentX = event.touches[0].clientX;4569var currentY = event.touches[0].clientY;45704571// If the touch started with two points and still has4572// two active touches; test for the pinch gesture4573if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {45744575// The current distance in pixels between the two touch points4576var currentSpan = distanceBetween( {4577x: event.touches[1].clientX,4578y: event.touches[1].clientY4579}, {4580x: touch.startX,4581y: touch.startY4582} );45834584// If the span is larger than the desire amount we've got4585// ourselves a pinch4586if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {4587touch.captured = true;45884589if( currentSpan < touch.startSpan ) {4590activateOverview();4591}4592else {4593deactivateOverview();4594}4595}45964597event.preventDefault();45984599}4600// There was only one touch point, look for a swipe4601else if( event.touches.length === 1 && touch.startCount !== 2 ) {46024603var deltaX = currentX - touch.startX,4604deltaY = currentY - touch.startY;46054606if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {4607touch.captured = true;4608navigateLeft();4609}4610else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {4611touch.captured = true;4612navigateRight();4613}4614else if( deltaY > touch.threshold ) {4615touch.captured = true;4616navigateUp();4617}4618else if( deltaY < -touch.threshold ) {4619touch.captured = true;4620navigateDown();4621}46224623// If we're embedded, only block touch events if they have4624// triggered an action4625if( config.embedded ) {4626if( touch.captured || isVerticalSlide( currentSlide ) ) {4627event.preventDefault();4628}4629}4630// Not embedded? Block them all to avoid needless tossing4631// around of the viewport in iOS4632else {4633event.preventDefault();4634}46354636}4637}4638// There's a bug with swiping on some Android devices unless4639// the default action is always prevented4640else if( UA.match( /android/gi ) ) {4641event.preventDefault();4642}46434644}46454646/**4647* Handler for the 'touchend' event.4648*4649* @param {object} event4650*/4651function onTouchEnd( event ) {46524653touch.captured = false;46544655}46564657/**4658* Convert pointer down to touch start.4659*4660* @param {object} event4661*/4662function onPointerDown( event ) {46634664if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {4665event.touches = [{ clientX: event.clientX, clientY: event.clientY }];4666onTouchStart( event );4667}46684669}46704671/**4672* Convert pointer move to touch move.4673*4674* @param {object} event4675*/4676function onPointerMove( event ) {46774678if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {4679event.touches = [{ clientX: event.clientX, clientY: event.clientY }];4680onTouchMove( event );4681}46824683}46844685/**4686* Convert pointer up to touch end.4687*4688* @param {object} event4689*/4690function onPointerUp( event ) {46914692if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {4693event.touches = [{ clientX: event.clientX, clientY: event.clientY }];4694onTouchEnd( event );4695}46964697}46984699/**4700* Handles mouse wheel scrolling, throttled to avoid skipping4701* multiple slides.4702*4703* @param {object} event4704*/4705function onDocumentMouseScroll( event ) {47064707if( Date.now() - lastMouseWheelStep > 600 ) {47084709lastMouseWheelStep = Date.now();47104711var delta = event.detail || -event.wheelDelta;4712if( delta > 0 ) {4713navigateNext();4714}4715else if( delta < 0 ) {4716navigatePrev();4717}47184719}47204721}47224723/**4724* Clicking on the progress bar results in a navigation to the4725* closest approximate horizontal slide using this equation:4726*4727* ( clickX / presentationWidth ) * numberOfSlides4728*4729* @param {object} event4730*/4731function onProgressClicked( event ) {47324733onUserInput( event );47344735event.preventDefault();47364737var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;4738var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );47394740if( config.rtl ) {4741slideIndex = slidesTotal - slideIndex;4742}47434744slide( slideIndex );47454746}47474748/**4749* Event handler for navigation control buttons.4750*/4751function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }4752function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }4753function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }4754function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }4755function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }4756function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }47574758/**4759* Handler for the window level 'hashchange' event.4760*4761* @param {object} [event]4762*/4763function onWindowHashChange( event ) {47644765readURL();47664767}47684769/**4770* Handler for the window level 'resize' event.4771*4772* @param {object} [event]4773*/4774function onWindowResize( event ) {47754776layout();47774778}47794780/**4781* Handle for the window level 'visibilitychange' event.4782*4783* @param {object} [event]4784*/4785function onPageVisibilityChange( event ) {47864787var isHidden = document.webkitHidden ||4788document.msHidden ||4789document.hidden;47904791// If, after clicking a link or similar and we're coming back,4792// focus the document.body to ensure we can use keyboard shortcuts4793if( isHidden === false && document.activeElement !== document.body ) {4794// Not all elements support .blur() - SVGs among them.4795if( typeof document.activeElement.blur === 'function' ) {4796document.activeElement.blur();4797}4798document.body.focus();4799}48004801}48024803/**4804* Invoked when a slide is and we're in the overview.4805*4806* @param {object} event4807*/4808function onOverviewSlideClicked( event ) {48094810// TODO There's a bug here where the event listeners are not4811// removed after deactivating the overview.4812if( eventsAreBound && isOverview() ) {4813event.preventDefault();48144815var element = event.target;48164817while( element && !element.nodeName.match( /section/gi ) ) {4818element = element.parentNode;4819}48204821if( element && !element.classList.contains( 'disabled' ) ) {48224823deactivateOverview();48244825if( element.nodeName.match( /section/gi ) ) {4826var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),4827v = parseInt( element.getAttribute( 'data-index-v' ), 10 );48284829slide( h, v );4830}48314832}4833}48344835}48364837/**4838* Handles clicks on links that are set to preview in the4839* iframe overlay.4840*4841* @param {object} event4842*/4843function onPreviewLinkClicked( event ) {48444845if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {4846var url = event.currentTarget.getAttribute( 'href' );4847if( url ) {4848showPreview( url );4849event.preventDefault();4850}4851}48524853}48544855/**4856* Handles click on the auto-sliding controls element.4857*4858* @param {object} [event]4859*/4860function onAutoSlidePlayerClick( event ) {48614862// Replay4863if( Reveal.isLastSlide() && config.loop === false ) {4864slide( 0, 0 );4865resumeAutoSlide();4866}4867// Resume4868else if( autoSlidePaused ) {4869resumeAutoSlide();4870}4871// Pause4872else {4873pauseAutoSlide();4874}48754876}487748784879// --------------------------------------------------------------------//4880// ------------------------ PLAYBACK COMPONENT ------------------------//4881// --------------------------------------------------------------------//488248834884/**4885* Constructor for the playback component, which displays4886* play/pause/progress controls.4887*4888* @param {HTMLElement} container The component will append4889* itself to this4890* @param {function} progressCheck A method which will be4891* called frequently to get the current progress on a range4892* of 0-14893*/4894function Playback( container, progressCheck ) {48954896// Cosmetics4897this.diameter = 100;4898this.diameter2 = this.diameter/2;4899this.thickness = 6;49004901// Flags if we are currently playing4902this.playing = false;49034904// Current progress on a 0-1 range4905this.progress = 0;49064907// Used to loop the animation smoothly4908this.progressOffset = 1;49094910this.container = container;4911this.progressCheck = progressCheck;49124913this.canvas = document.createElement( 'canvas' );4914this.canvas.className = 'playback';4915this.canvas.width = this.diameter;4916this.canvas.height = this.diameter;4917this.canvas.style.width = this.diameter2 + 'px';4918this.canvas.style.height = this.diameter2 + 'px';4919this.context = this.canvas.getContext( '2d' );49204921this.container.appendChild( this.canvas );49224923this.render();49244925}49264927/**4928* @param value4929*/4930Playback.prototype.setPlaying = function( value ) {49314932var wasPlaying = this.playing;49334934this.playing = value;49354936// Start repainting if we weren't already4937if( !wasPlaying && this.playing ) {4938this.animate();4939}4940else {4941this.render();4942}49434944};49454946Playback.prototype.animate = function() {49474948var progressBefore = this.progress;49494950this.progress = this.progressCheck();49514952// When we loop, offset the progress so that it eases4953// smoothly rather than immediately resetting4954if( progressBefore > 0.8 && this.progress < 0.2 ) {4955this.progressOffset = this.progress;4956}49574958this.render();49594960if( this.playing ) {4961features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );4962}49634964};49654966/**4967* Renders the current progress and playback state.4968*/4969Playback.prototype.render = function() {49704971var progress = this.playing ? this.progress : 0,4972radius = ( this.diameter2 ) - this.thickness,4973x = this.diameter2,4974y = this.diameter2,4975iconSize = 28;49764977// Ease towards 14978this.progressOffset += ( 1 - this.progressOffset ) * 0.1;49794980var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );4981var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );49824983this.context.save();4984this.context.clearRect( 0, 0, this.diameter, this.diameter );49854986// Solid background color4987this.context.beginPath();4988this.context.arc( x, y, radius + 4, 0, Math.PI * 2, false );4989this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';4990this.context.fill();49914992// Draw progress track4993this.context.beginPath();4994this.context.arc( x, y, radius, 0, Math.PI * 2, false );4995this.context.lineWidth = this.thickness;4996this.context.strokeStyle = 'rgba( 255, 255, 255, 0.2 )';4997this.context.stroke();49984999if( this.playing ) {5000// Draw progress on top of track5001this.context.beginPath();5002this.context.arc( x, y, radius, startAngle, endAngle, false );5003this.context.lineWidth = this.thickness;5004this.context.strokeStyle = '#fff';5005this.context.stroke();5006}50075008this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );50095010// Draw play/pause icons5011if( this.playing ) {5012this.context.fillStyle = '#fff';5013this.context.fillRect( 0, 0, iconSize / 2 - 4, iconSize );5014this.context.fillRect( iconSize / 2 + 4, 0, iconSize / 2 - 4, iconSize );5015}5016else {5017this.context.beginPath();5018this.context.translate( 4, 0 );5019this.context.moveTo( 0, 0 );5020this.context.lineTo( iconSize - 4, iconSize / 2 );5021this.context.lineTo( 0, iconSize );5022this.context.fillStyle = '#fff';5023this.context.fill();5024}50255026this.context.restore();50275028};50295030Playback.prototype.on = function( type, listener ) {5031this.canvas.addEventListener( type, listener, false );5032};50335034Playback.prototype.off = function( type, listener ) {5035this.canvas.removeEventListener( type, listener, false );5036};50375038Playback.prototype.destroy = function() {50395040this.playing = false;50415042if( this.canvas.parentNode ) {5043this.container.removeChild( this.canvas );5044}50455046};504750485049// --------------------------------------------------------------------//5050// ------------------------------- API --------------------------------//5051// --------------------------------------------------------------------//505250535054Reveal = {5055VERSION: VERSION,50565057initialize: initialize,5058configure: configure,5059sync: sync,50605061// Navigation methods5062slide: slide,5063left: navigateLeft,5064right: navigateRight,5065up: navigateUp,5066down: navigateDown,5067prev: navigatePrev,5068next: navigateNext,50695070// Fragment methods5071navigateFragment: navigateFragment,5072prevFragment: previousFragment,5073nextFragment: nextFragment,50745075// Deprecated aliases5076navigateTo: slide,5077navigateLeft: navigateLeft,5078navigateRight: navigateRight,5079navigateUp: navigateUp,5080navigateDown: navigateDown,5081navigatePrev: navigatePrev,5082navigateNext: navigateNext,50835084// Forces an update in slide layout5085layout: layout,50865087// Randomizes the order of slides5088shuffle: shuffle,50895090// Returns an object with the available routes as booleans (left/right/top/bottom)5091availableRoutes: availableRoutes,50925093// Returns an object with the available fragments as booleans (prev/next)5094availableFragments: availableFragments,50955096// Toggles a help overlay with keyboard shortcuts5097toggleHelp: toggleHelp,50985099// Toggles the overview mode on/off5100toggleOverview: toggleOverview,51015102// Toggles the "black screen" mode on/off5103togglePause: togglePause,51045105// Toggles the auto slide mode on/off5106toggleAutoSlide: toggleAutoSlide,51075108// State checks5109isOverview: isOverview,5110isPaused: isPaused,5111isAutoSliding: isAutoSliding,5112isSpeakerNotes: isSpeakerNotes,51135114// Slide preloading5115loadSlide: loadSlide,5116unloadSlide: unloadSlide,51175118// Adds or removes all internal event listeners (such as keyboard)5119addEventListeners: addEventListeners,5120removeEventListeners: removeEventListeners,51215122// Facility for persisting and restoring the presentation state5123getState: getState,5124setState: setState,51255126// Presentation progress5127getSlidePastCount: getSlidePastCount,51285129// Presentation progress on range of 0-15130getProgress: getProgress,51315132// Returns the indices of the current, or specified, slide5133getIndices: getIndices,51345135// Returns an Array of all slides5136getSlides: getSlides,51375138// Returns the total number of slides5139getTotalSlides: getTotalSlides,51405141// Returns the slide element at the specified index5142getSlide: getSlide,51435144// Returns the slide background element at the specified index5145getSlideBackground: getSlideBackground,51465147// Returns the speaker notes string for a slide, or null5148getSlideNotes: getSlideNotes,51495150// Returns the previous slide element, may be null5151getPreviousSlide: function() {5152return previousSlide;5153},51545155// Returns the current slide element5156getCurrentSlide: function() {5157return currentSlide;5158},51595160// Returns the current scale of the presentation content5161getScale: function() {5162return scale;5163},51645165// Returns the current configuration object5166getConfig: function() {5167return config;5168},51695170// Helper method, retrieves query string as a key/value hash5171getQueryHash: function() {5172var query = {};51735174location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {5175query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();5176} );51775178// Basic deserialization5179for( var i in query ) {5180var value = query[ i ];51815182query[ i ] = deserialize( unescape( value ) );5183}51845185return query;5186},51875188// Returns true if we're currently on the first slide5189isFirstSlide: function() {5190return ( indexh === 0 && indexv === 0 );5191},51925193// Returns true if we're currently on the last slide5194isLastSlide: function() {5195if( currentSlide ) {5196// Does this slide has next a sibling?5197if( currentSlide.nextElementSibling ) return false;51985199// If it's vertical, does its parent have a next sibling?5200if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;52015202return true;5203}52045205return false;5206},52075208// Checks if reveal.js has been loaded and is ready for use5209isReady: function() {5210return loaded;5211},52125213// Forward event binding to the reveal DOM element5214addEventListener: function( type, listener, useCapture ) {5215if( 'addEventListener' in window ) {5216( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );5217}5218},5219removeEventListener: function( type, listener, useCapture ) {5220if( 'addEventListener' in window ) {5221( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );5222}5223},52245225// Programatically triggers a keyboard event5226triggerKey: function( keyCode ) {5227onDocumentKeyDown( { keyCode: keyCode } );5228},52295230// Registers a new shortcut to include in the help overlay5231registerKeyboardShortcut: function( key, value ) {5232keyboardShortcuts[key] = value;5233}5234};52355236return Reveal;52375238}));523952405241