hyla 1.0.7 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +8 -8
  2. data/Rakefile +6 -2
  3. data/bin/hyla +3 -2
  4. data/lib/hyla/commands/generate.rb +43 -41
  5. data/lib/hyla/configuration.rb +1 -1
  6. data/lib/hyla/project.rb +1 -1
  7. data/lib/resources/assets/revealjs-redhat/image/collapsed.png +0 -0
  8. data/lib/resources/assets/revealjs-redhat/image/expanded.png +0 -0
  9. data/lib/resources/assets/revealjs-redhat/lib/css/conference.css +663 -0
  10. data/lib/resources/assets/revealjs-redhat/lib/css/font-awesome-4.3.0.css +2886 -1098
  11. data/lib/resources/assets/revealjs-redhat/lib/css/gpe.css +746 -180
  12. data/lib/resources/assets/revealjs-redhat/lib/css/print/pdf.css +32 -65
  13. data/lib/resources/assets/revealjs-redhat/lib/css/theme-output.css +1509 -395
  14. data/lib/resources/assets/revealjs-redhat/lib/css/theme-v2-liberation.css +4332 -1366
  15. data/lib/resources/assets/revealjs-redhat/lib/css/theme-v2-overpass.css +4320 -1364
  16. data/lib/resources/assets/revealjs-redhat/lib/js/debug/gpe.js +8 -8
  17. data/lib/resources/assets/revealjs-redhat/lib/js/debug/reveal.js +129 -91
  18. data/lib/resources/assets/revealjs-redhat/lib/js/gpe.min.js +3 -3
  19. data/lib/resources/assets/revealjs-redhat/lib/js/reveal.min.js +18 -13
  20. data/lib/resources/assets/revealjs/css/theme/conference-redhat.css +14 -6
  21. data/lib/resources/assets/revealjs/css/theme/old-gpe.css +670 -181
  22. data/lib/resources/assets/revealjs/js/{reveal.js → debug/reveal.js} +1619 -492
  23. data/lib/resources/assets/revealjs/js/reveal.min.js +342 -9
  24. data/lib/resources/assets/revealjs/lib/css/font-awesome-4.3.0.css +2886 -1098
  25. data/lib/resources/assets/sass/conference.scss +589 -0
  26. data/lib/resources/assets/sass/new-gpe.scss +79 -0
  27. data/lib/resources/backends/slim/revealjs-redhat/block_paragraph.html.slim +18 -6
  28. data/lib/resources/backends/slim/revealjs-redhat/block_ulist.html.slim +25 -9
  29. data/lib/resources/backends/slim/revealjs/document.html.slim +1 -1
  30. data/lib/templates/course/audio.txt +2 -2
  31. data/lib/templates/course/cover.txt +19 -7
  32. data/lib/templates/course/footer.txt +1 -3
  33. data/lib/templates/course/index.txt +2 -2
  34. data/lib/templates/course/labinstructions.txt +1 -3
  35. data/lib/templates/course/objectives.txt +1 -12
  36. data/lib/templates/course/summary.txt +1 -8
  37. metadata +7 -3
@@ -2,19 +2,36 @@
2
2
  * reveal.js
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
+ * Version 3.1.0
5
6
  *
6
- * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
7
+ * Copyright (C) 2015 Hakim El Hattab, http://hakim.se
7
8
  */
8
- var Reveal = (function(){
9
+ (function( root, factory ) {
10
+ if( typeof define === 'function' && define.amd ) {
11
+ // AMD. Register as an anonymous module.
12
+ define( function() {
13
+ root.Reveal = factory();
14
+ return root.Reveal;
15
+ } );
16
+ } else if( typeof exports === 'object' ) {
17
+ // Node. Does not work with strict CommonJS.
18
+ module.exports = factory();
19
+ } else {
20
+ // Browser globals.
21
+ root.Reveal = factory();
22
+ }
23
+ }( this, function() {
9
24
 
10
25
  'use strict';
11
26
 
12
- var SLIDES_SELECTOR = '.reveal .slides section',
13
- HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
- VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
27
+ var Reveal;
16
28
 
17
- // Configurations defaults, can be overridden at initialization time
29
+ var SLIDES_SELECTOR = '.slides section',
30
+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
31
+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
32
+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
33
+
34
+ // Configuration defaults, can be overridden at initialization time
18
35
  config = {
19
36
 
20
37
  // The "normal" size of the presentation, aspect ratio will be preserved
@@ -27,7 +44,7 @@ var Reveal = (function(){
27
44
 
28
45
  // Bounds for smallest/largest possible scale to apply to content
29
46
  minScale: 0.2,
30
- maxScale: 1.0,
47
+ maxScale: 1.5,
31
48
 
32
49
  // Display controls in the bottom right corner
33
50
  controls: true,
@@ -44,6 +61,9 @@ var Reveal = (function(){
44
61
  // Enable keyboard shortcuts for navigation
45
62
  keyboard: true,
46
63
 
64
+ // Optional function that blocks keyboard events when retuning false
65
+ keyboardCondition: null,
66
+
47
67
  // Enable the slide overview mode
48
68
  overview: true,
49
69
 
@@ -66,6 +86,13 @@ var Reveal = (function(){
66
86
  // i.e. contained within a limited portion of the screen
67
87
  embedded: false,
68
88
 
89
+ // Flags if we should show a help overlay when the questionmark
90
+ // key is pressed
91
+ help: true,
92
+
93
+ // Flags if it should be possible to pause the presentation (blackout)
94
+ pause: true,
95
+
69
96
  // Number of milliseconds between automatically proceeding to the
70
97
  // next slide, disabled when set to 0, this value can be overwritten
71
98
  // by using a data-autoslide attribute on your slides
@@ -86,20 +113,23 @@ var Reveal = (function(){
86
113
  // Opens links in an iframe preview overlay
87
114
  previewLinks: false,
88
115
 
89
- // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
- focusBodyOnPageVisiblityChange: true,
116
+ // Exposes the reveal.js API through window.postMessage
117
+ postMessage: true,
91
118
 
92
- // Theme (see /css/theme)
93
- theme: null,
119
+ // Dispatches all reveal.js events to the parent window through postMessage
120
+ postMessageEvents: false,
121
+
122
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
123
+ focusBodyOnPageVisibilityChange: true,
94
124
 
95
125
  // Transition style
96
- transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
126
+ transition: 'slide', // none/fade/slide/convex/concave/zoom
97
127
 
98
128
  // Transition speed
99
129
  transitionSpeed: 'default', // default/fast/slow
100
130
 
101
131
  // Transition style for full page slide backgrounds
102
- backgroundTransition: 'default', // default/linear/none
132
+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
103
133
 
104
134
  // Parallax background image
105
135
  parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
@@ -107,6 +137,10 @@ var Reveal = (function(){
107
137
  // Parallax background size
108
138
  parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
109
139
 
140
+ // Amount of pixels to move the parallax background per slide step
141
+ parallaxBackgroundHorizontal: null,
142
+ parallaxBackgroundVertical: null,
143
+
110
144
  // Number of slides away from the current that are visible
111
145
  viewDistance: 3,
112
146
 
@@ -115,61 +149,62 @@ var Reveal = (function(){
115
149
 
116
150
  },
117
151
 
118
- // Flags if reveal.js is loaded (has dispatched the 'ready' event)
152
+ // Flags if reveal.js is loaded (has dispatched the 'ready' event)
119
153
  loaded = false,
120
154
 
121
- // The horizontal and vertical index of the currently active slide
155
+ // Flags if the overview mode is currently active
156
+ overview = false,
157
+
158
+ // The horizontal and vertical index of the currently active slide
122
159
  indexh,
123
160
  indexv,
124
161
 
125
- // The previous and current slide HTML elements
162
+ // The previous and current slide HTML elements
126
163
  previousSlide,
127
164
  currentSlide,
128
165
 
129
166
  previousBackground,
130
167
 
131
- // Slides may hold a data-state attribute which we pick up and apply
132
- // as a class to the body. This list contains the combined state of
133
- // all current slides.
168
+ // Slides may hold a data-state attribute which we pick up and apply
169
+ // as a class to the body. This list contains the combined state of
170
+ // all current slides.
134
171
  state = [],
135
172
 
136
- // The current scale of the presentation (see width/height config)
173
+ // The current scale of the presentation (see width/height config)
137
174
  scale = 1,
138
175
 
139
- // Cached references to DOM elements
176
+ // CSS transform that is currently applied to the slides container,
177
+ // split into two groups
178
+ slidesTransform = { layout: '', overview: '' },
179
+
180
+ // Cached references to DOM elements
140
181
  dom = {},
141
182
 
142
- // Features supported by the browser, see #checkCapabilities()
183
+ // Features supported by the browser, see #checkCapabilities()
143
184
  features = {},
144
185
 
145
- // Client is a mobile device, see #checkCapabilities()
186
+ // Client is a mobile device, see #checkCapabilities()
146
187
  isMobileDevice,
147
188
 
148
- // Throttles mouse wheel navigation
189
+ // Throttles mouse wheel navigation
149
190
  lastMouseWheelStep = 0,
150
191
 
151
- // Delays updates to the URL due to a Chrome thumbnailer bug
192
+ // Delays updates to the URL due to a Chrome thumbnailer bug
152
193
  writeURLTimeout = 0,
153
194
 
154
- // A delay used to activate the overview mode
155
- activateOverviewTimeout = 0,
156
-
157
- // A delay used to deactivate the overview mode
158
- deactivateOverviewTimeout = 0,
159
-
160
- // Flags if the interaction event listeners are bound
195
+ // Flags if the interaction event listeners are bound
161
196
  eventsAreBound = false,
162
197
 
163
- // The current auto-slide duration
198
+ // The current auto-slide duration
164
199
  autoSlide = 0,
165
200
 
166
- // Auto slide properties
201
+ // Auto slide properties
167
202
  autoSlidePlayer,
168
203
  autoSlideTimeout = 0,
169
204
  autoSlideStartTime = -1,
170
205
  autoSlidePaused = false,
171
206
 
172
- // Holds information about the currently ongoing touch input
207
+ // Holds information about the currently ongoing touch input
173
208
  touch = {
174
209
  startX: 0,
175
210
  startY: 0,
@@ -177,6 +212,21 @@ var Reveal = (function(){
177
212
  startCount: 0,
178
213
  captured: false,
179
214
  threshold: 40
215
+ },
216
+
217
+ // Holds information about the keyboard shortcuts
218
+ keyboardShortcuts = {
219
+ 'N , SPACE': 'Next slide',
220
+ 'P': 'Previous slide',
221
+ '← , H': 'Navigate left',
222
+ '→ , L': 'Navigate right',
223
+ '↑ , K': 'Navigate up',
224
+ '↓ , J': 'Navigate down',
225
+ 'Home': 'First slide',
226
+ 'End': 'Last slide',
227
+ 'B , .': 'Pause',
228
+ 'F': 'Fullscreen',
229
+ 'ESC, O': 'Slide overview'
180
230
  };
181
231
 
182
232
  /**
@@ -189,11 +239,30 @@ var Reveal = (function(){
189
239
  if( !features.transforms2d && !features.transforms3d ) {
190
240
  document.body.setAttribute( 'class', 'no-transforms' );
191
241
 
242
+ // Since JS won't be running any further, we load all lazy
243
+ // loading elements upfront
244
+ var images = toArray( document.getElementsByTagName( 'img' ) ),
245
+ iframes = toArray( document.getElementsByTagName( 'iframe' ) );
246
+
247
+ var lazyLoadable = images.concat( iframes );
248
+
249
+ for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {
250
+ var element = lazyLoadable[i];
251
+ if( element.getAttribute( 'data-src' ) ) {
252
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
253
+ element.removeAttribute( 'data-src' );
254
+ }
255
+ }
256
+
192
257
  // If the browser doesn't support core features we won't be
193
258
  // using JavaScript to control the presentation
194
259
  return;
195
260
  }
196
261
 
262
+ // Cache references to key DOM elements
263
+ dom.wrapper = document.querySelector( '.reveal' );
264
+ dom.slides = document.querySelector( '.reveal .slides' );
265
+
197
266
  // Force a layout when the whole page, incl fonts, has loaded
198
267
  window.addEventListener( 'load', layout, false );
199
268
 
@@ -222,34 +291,39 @@ var Reveal = (function(){
222
291
  function checkCapabilities() {
223
292
 
224
293
  features.transforms3d = 'WebkitPerspective' in document.body.style ||
225
- 'MozPerspective' in document.body.style ||
226
- 'msPerspective' in document.body.style ||
227
- 'OPerspective' in document.body.style ||
228
- 'perspective' in document.body.style;
294
+ 'MozPerspective' in document.body.style ||
295
+ 'msPerspective' in document.body.style ||
296
+ 'OPerspective' in document.body.style ||
297
+ 'perspective' in document.body.style;
229
298
 
230
299
  features.transforms2d = 'WebkitTransform' in document.body.style ||
231
- 'MozTransform' in document.body.style ||
232
- 'msTransform' in document.body.style ||
233
- 'OTransform' in document.body.style ||
234
- 'transform' in document.body.style;
300
+ 'MozTransform' in document.body.style ||
301
+ 'msTransform' in document.body.style ||
302
+ 'OTransform' in document.body.style ||
303
+ 'transform' in document.body.style;
235
304
 
236
305
  features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
237
306
  features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
238
307
 
239
308
  features.canvas = !!document.createElement( 'canvas' ).getContext;
240
309
 
241
- isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
310
+ features.touch = !!( 'ontouchstart' in window );
242
311
 
243
- }
312
+ // Transitions in the overview are disabled in desktop and
313
+ // mobile Safari due to lag
314
+ features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( navigator.userAgent );
244
315
 
316
+ isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( navigator.userAgent );
245
317
 
246
- /**
247
- * Loads the dependencies of reveal.js. Dependencies are
248
- * defined via the configuration option 'dependencies'
249
- * and will be loaded prior to starting/binding reveal.js.
250
- * Some dependencies may have an 'async' flag, if so they
251
- * will load after reveal.js has been started up.
252
- */
318
+ }
319
+
320
+ /**
321
+ * Loads the dependencies of reveal.js. Dependencies are
322
+ * defined via the configuration option 'dependencies'
323
+ * and will be loaded prior to starting/binding reveal.js.
324
+ * Some dependencies may have an 'async' flag, if so they
325
+ * will load after reveal.js has been started up.
326
+ */
253
327
  function load() {
254
328
 
255
329
  var scripts = [],
@@ -316,6 +390,12 @@ var Reveal = (function(){
316
390
  // Make sure we've got all the DOM elements we need
317
391
  setupDOM();
318
392
 
393
+ // Listen to messages posted to this window
394
+ setupPostMessage();
395
+
396
+ // Prevent iframes from scrolling the slides out of view
397
+ setupIframeScrollPrevention();
398
+
319
399
  // Resets all vertical slides so that only the first is visible
320
400
  resetVerticalSlides();
321
401
 
@@ -343,6 +423,20 @@ var Reveal = (function(){
343
423
  } );
344
424
  }, 1 );
345
425
 
426
+ // Special setup and config is required when printing to PDF
427
+ if( isPrintingPDF() ) {
428
+ removeEventListeners();
429
+
430
+ // The document needs to have loaded for the PDF layout
431
+ // measurements to be accurate
432
+ if( document.readyState === 'complete' ) {
433
+ setupPDF();
434
+ }
435
+ else {
436
+ window.addEventListener( 'load', setupPDF );
437
+ }
438
+ }
439
+
346
440
  }
347
441
 
348
442
  /**
@@ -352,11 +446,6 @@ var Reveal = (function(){
352
446
  */
353
447
  function setupDOM() {
354
448
 
355
- // Cache references to key DOM elements
356
- dom.theme = document.querySelector( '#theme' );
357
- dom.wrapper = document.querySelector( '.reveal' );
358
- dom.slides = document.querySelector( '.reveal .slides' );
359
-
360
449
  // Prevent transitions while we're loading
361
450
  dom.slides.classList.add( 'no-transition' );
362
451
 
@@ -377,14 +466,14 @@ var Reveal = (function(){
377
466
  // Slide number
378
467
  dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
468
 
380
- // State background element [DEPRECATED]
381
- createSingletonNode( dom.wrapper, 'div', 'state-background', null );
382
-
383
469
  // Overlay graphic which is displayed during the paused mode
384
470
  createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
385
471
 
386
472
  // Cache references to elements
387
473
  dom.controls = document.querySelector( '.reveal .controls' );
474
+ dom.theme = document.querySelector( '#theme' );
475
+
476
+ dom.wrapper.setAttribute( 'role', 'application' );
388
477
 
389
478
  // There can be multiple instances of controls throughout the page
390
479
  dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
@@ -394,6 +483,120 @@ var Reveal = (function(){
394
483
  dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
484
  dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
485
 
486
+ dom.statusDiv = createStatusDiv();
487
+ }
488
+
489
+ /**
490
+ * Creates a hidden div with role aria-live to announce the
491
+ * current slide content. Hide the div off-screen to make it
492
+ * available only to Assistive Technologies.
493
+ */
494
+ function createStatusDiv() {
495
+
496
+ var statusDiv = document.getElementById( 'aria-status-div' );
497
+ if( !statusDiv ) {
498
+ statusDiv = document.createElement( 'div' );
499
+ statusDiv.style.position = 'absolute';
500
+ statusDiv.style.height = '1px';
501
+ statusDiv.style.width = '1px';
502
+ statusDiv.style.overflow ='hidden';
503
+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
504
+ statusDiv.setAttribute( 'id', 'aria-status-div' );
505
+ statusDiv.setAttribute( 'aria-live', 'polite' );
506
+ statusDiv.setAttribute( 'aria-atomic','true' );
507
+ dom.wrapper.appendChild( statusDiv );
508
+ }
509
+ return statusDiv;
510
+
511
+ }
512
+
513
+ /**
514
+ * Configures the presentation for printing to a static
515
+ * PDF.
516
+ */
517
+ function setupPDF() {
518
+
519
+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
520
+
521
+ // Dimensions of the PDF pages
522
+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
523
+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
524
+
525
+ // Dimensions of slides within the pages
526
+ var slideWidth = slideSize.width,
527
+ slideHeight = slideSize.height;
528
+
529
+ // Let the browser know what page size we want to print
530
+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
531
+
532
+ // Limit the size of certain elements to the dimensions of the slide
533
+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
534
+
535
+ document.body.classList.add( 'print-pdf' );
536
+ document.body.style.width = pageWidth + 'px';
537
+ document.body.style.height = pageHeight + 'px';
538
+
539
+ // Slide and slide background layout
540
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
541
+
542
+ // Vertical stacks are not centred since their section
543
+ // children will be
544
+ if( slide.classList.contains( 'stack' ) === false ) {
545
+ // Center the slide inside of the page, giving the slide some margin
546
+ var left = ( pageWidth - slideWidth ) / 2,
547
+ top = ( pageHeight - slideHeight ) / 2;
548
+
549
+ var contentHeight = getAbsoluteHeight( slide );
550
+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
551
+
552
+ // Center slides vertically
553
+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
554
+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
555
+ }
556
+
557
+ // Position the slide inside of the page
558
+ slide.style.left = left + 'px';
559
+ slide.style.top = top + 'px';
560
+ slide.style.width = slideWidth + 'px';
561
+
562
+ // TODO Backgrounds need to be multiplied when the slide
563
+ // stretches over multiple pages
564
+ var background = slide.querySelector( '.slide-background' );
565
+ if( background ) {
566
+ background.style.width = pageWidth + 'px';
567
+ background.style.height = ( pageHeight * numberOfPages ) + 'px';
568
+ background.style.top = -top + 'px';
569
+ background.style.left = -left + 'px';
570
+ }
571
+ }
572
+
573
+ } );
574
+
575
+ // Show all fragments
576
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
577
+ fragment.classList.add( 'visible' );
578
+ } );
579
+
580
+ }
581
+
582
+ /**
583
+ * This is an unfortunate necessity. Iframes can trigger the
584
+ * parent window to scroll, for example by focusing an input.
585
+ * This scrolling can not be prevented by hiding overflow in
586
+ * CSS so we have to resort to repeatedly checking if the
587
+ * browser has decided to offset our slides :(
588
+ */
589
+ function setupIframeScrollPrevention() {
590
+
591
+ if( dom.slides.querySelector( 'iframe' ) ) {
592
+ setInterval( function() {
593
+ if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
594
+ dom.wrapper.scrollTop = 0;
595
+ dom.wrapper.scrollLeft = 0;
596
+ }
597
+ }, 500 );
598
+ }
599
+
397
600
  }
398
601
 
399
602
  /**
@@ -403,15 +606,26 @@ var Reveal = (function(){
403
606
  */
404
607
  function createSingletonNode( container, tagname, classname, innerHTML ) {
405
608
 
406
- var node = container.querySelector( '.' + classname );
407
- if( !node ) {
408
- node = document.createElement( tagname );
409
- node.classList.add( classname );
410
- if( innerHTML !== null ) {
411
- node.innerHTML = innerHTML;
609
+ // Find all nodes matching the description
610
+ var nodes = container.querySelectorAll( '.' + classname );
611
+
612
+ // Check all matches to find one which is a direct child of
613
+ // the specified container
614
+ for( var i = 0; i < nodes.length; i++ ) {
615
+ var testNode = nodes[i];
616
+ if( testNode.parentNode === container ) {
617
+ return testNode;
412
618
  }
413
- container.appendChild( node );
414
619
  }
620
+
621
+ // If no node was found, create it now
622
+ var node = document.createElement( tagname );
623
+ node.classList.add( classname );
624
+ if( typeof innerHTML === 'string' ) {
625
+ node.innerHTML = innerHTML;
626
+ }
627
+ container.appendChild( node );
628
+
415
629
  return node;
416
630
 
417
631
  }
@@ -423,81 +637,36 @@ var Reveal = (function(){
423
637
  */
424
638
  function createBackgrounds() {
425
639
 
426
- if( isPrintingPDF() ) {
427
- document.body.classList.add( 'print-pdf' );
428
- }
640
+ var printMode = isPrintingPDF();
429
641
 
430
642
  // Clear prior backgrounds
431
643
  dom.background.innerHTML = '';
432
644
  dom.background.classList.add( 'no-transition' );
433
645
 
434
- // Helper method for creating a background element for the
435
- // given slide
436
- function _createBackground( slide, container ) {
437
-
438
- var data = {
439
- background: slide.getAttribute( 'data-background' ),
440
- backgroundSize: slide.getAttribute( 'data-background-size' ),
441
- backgroundImage: slide.getAttribute( 'data-background-image' ),
442
- backgroundColor: slide.getAttribute( 'data-background-color' ),
443
- backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
444
- backgroundPosition: slide.getAttribute( 'data-background-position' ),
445
- backgroundTransition: slide.getAttribute( 'data-background-transition' )
446
- };
447
-
448
- var element = document.createElement( 'div' );
449
- element.className = 'slide-background';
450
-
451
- if( data.background ) {
452
- // Auto-wrap image urls in url(...)
453
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
454
- element.style.backgroundImage = 'url('+ data.background +')';
455
- }
456
- else {
457
- element.style.background = data.background;
458
- }
459
- }
460
-
461
- if( data.background || data.backgroundColor || data.backgroundImage ) {
462
- element.setAttribute( 'data-background-hash', data.background + data.backgroundSize + data.backgroundImage + data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + data.backgroundTransition );
463
- }
464
-
465
- // Additional and optional background properties
466
- if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
467
- if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
468
- if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
469
- if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
470
- if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
471
- if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
472
-
473
- container.appendChild( element );
474
-
475
- return element;
476
-
477
- }
478
-
479
646
  // Iterate over all horizontal slides
480
- toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
647
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
481
648
 
482
649
  var backgroundStack;
483
650
 
484
- if( isPrintingPDF() ) {
485
- backgroundStack = _createBackground( slideh, slideh );
651
+ if( printMode ) {
652
+ backgroundStack = createBackground( slideh, slideh );
486
653
  }
487
654
  else {
488
- backgroundStack = _createBackground( slideh, dom.background );
655
+ backgroundStack = createBackground( slideh, dom.background );
489
656
  }
490
657
 
491
658
  // Iterate over all vertical slides
492
659
  toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
493
660
 
494
- if( isPrintingPDF() ) {
495
- _createBackground( slidev, slidev );
661
+ if( printMode ) {
662
+ createBackground( slidev, slidev );
496
663
  }
497
664
  else {
498
- _createBackground( slidev, backgroundStack );
665
+ createBackground( slidev, backgroundStack );
499
666
  }
500
667
 
668
+ backgroundStack.classList.add( 'stack' );
669
+
501
670
  } );
502
671
 
503
672
  } );
@@ -526,13 +695,131 @@ var Reveal = (function(){
526
695
 
527
696
  }
528
697
 
698
+ /**
699
+ * Creates a background for the given slide.
700
+ *
701
+ * @param {HTMLElement} slide
702
+ * @param {HTMLElement} container The element that the background
703
+ * should be appended to
704
+ */
705
+ function createBackground( slide, container ) {
706
+
707
+ var data = {
708
+ background: slide.getAttribute( 'data-background' ),
709
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
710
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
711
+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
712
+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
713
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
714
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
715
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
716
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
717
+ };
718
+
719
+ var element = document.createElement( 'div' );
720
+
721
+ // Carry over custom classes from the slide to the background
722
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
723
+
724
+ if( data.background ) {
725
+ // Auto-wrap image urls in url(...)
726
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
727
+ slide.setAttribute( 'data-background-image', data.background );
728
+ }
729
+ else {
730
+ element.style.background = data.background;
731
+ }
732
+ }
733
+
734
+ // Create a hash for this combination of background settings.
735
+ // This is used to determine when two slide backgrounds are
736
+ // the same.
737
+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
738
+ element.setAttribute( 'data-background-hash', data.background +
739
+ data.backgroundSize +
740
+ data.backgroundImage +
741
+ data.backgroundVideo +
742
+ data.backgroundIframe +
743
+ data.backgroundColor +
744
+ data.backgroundRepeat +
745
+ data.backgroundPosition +
746
+ data.backgroundTransition );
747
+ }
748
+
749
+ // Additional and optional background properties
750
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
751
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
752
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
753
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
754
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
755
+
756
+ container.appendChild( element );
757
+
758
+ // If backgrounds are being recreated, clear old classes
759
+ slide.classList.remove( 'has-dark-background' );
760
+ slide.classList.remove( 'has-light-background' );
761
+
762
+ // If this slide has a background color, add a class that
763
+ // signals if it is light or dark. If the slide has no background
764
+ // color, no class will be set
765
+ var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
766
+ if( computedBackgroundColor ) {
767
+ var rgb = colorToRgb( computedBackgroundColor );
768
+
769
+ // Ignore fully transparent backgrounds. Some browsers return
770
+ // rgba(0,0,0,0) when reading the computed background color of
771
+ // an element with no background
772
+ if( rgb && rgb.a !== 0 ) {
773
+ if( colorBrightness( computedBackgroundColor ) < 128 ) {
774
+ slide.classList.add( 'has-dark-background' );
775
+ }
776
+ else {
777
+ slide.classList.add( 'has-light-background' );
778
+ }
779
+ }
780
+ }
781
+
782
+ return element;
783
+
784
+ }
785
+
786
+ /**
787
+ * Registers a listener to postMessage events, this makes it
788
+ * possible to call all reveal.js API methods from another
789
+ * window. For example:
790
+ *
791
+ * revealWindow.postMessage( JSON.stringify({
792
+ * method: 'slide',
793
+ * args: [ 2 ]
794
+ * }), '*' );
795
+ */
796
+ function setupPostMessage() {
797
+
798
+ if( config.postMessage ) {
799
+ window.addEventListener( 'message', function ( event ) {
800
+ var data = event.data;
801
+
802
+ // Make sure we're dealing with JSON
803
+ if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
804
+ data = JSON.parse( data );
805
+
806
+ // Check if the requested method can be found
807
+ if( data.method && typeof Reveal[data.method] === 'function' ) {
808
+ Reveal[data.method].apply( Reveal, data.args );
809
+ }
810
+ }
811
+ }, false );
812
+ }
813
+
814
+ }
815
+
529
816
  /**
530
817
  * Applies the configuration settings from the config
531
818
  * object. May be called multiple times.
532
819
  */
533
820
  function configure( options ) {
534
821
 
535
- var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
822
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
536
823
 
537
824
  dom.wrapper.classList.remove( config.transition );
538
825
 
@@ -565,6 +852,11 @@ var Reveal = (function(){
565
852
  dom.wrapper.classList.remove( 'center' );
566
853
  }
567
854
 
855
+ // Exit the paused mode if it was configured off
856
+ if( config.pause === false ) {
857
+ resume();
858
+ }
859
+
568
860
  if( config.mouseWheel ) {
569
861
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
570
862
  document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
@@ -591,7 +883,13 @@ var Reveal = (function(){
591
883
  enablePreviewLinks( '[data-preview-link]' );
592
884
  }
593
885
 
594
- // Auto-slide playback controls
886
+ // Remove existing auto-slide controls
887
+ if( autoSlidePlayer ) {
888
+ autoSlidePlayer.destroy();
889
+ autoSlidePlayer = null;
890
+ }
891
+
892
+ // Generate auto-slide controls if needed
595
893
  if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
894
  autoSlidePlayer = new Playback( dom.wrapper, function() {
597
895
  return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
@@ -600,21 +898,13 @@ var Reveal = (function(){
600
898
  autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
899
  autoSlidePaused = false;
602
900
  }
603
- else if( autoSlidePlayer ) {
604
- autoSlidePlayer.destroy();
605
- autoSlidePlayer = null;
606
- }
607
901
 
608
- // Load the theme in the config, if it's not already loaded
609
- if( config.theme && dom.theme ) {
610
- var themeURL = dom.theme.getAttribute( 'href' );
611
- var themeFinder = /[^\/]*?(?=\.css)/;
612
- var themeName = themeURL.match(themeFinder)[0];
613
-
614
- if( config.theme !== themeName ) {
615
- themeURL = themeURL.replace(themeFinder, config.theme);
616
- dom.theme.setAttribute( 'href', themeURL );
617
- }
902
+ // When fragments are turned off they should be visible
903
+ if( config.fragments === false ) {
904
+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
905
+ element.classList.add( 'visible' );
906
+ element.classList.remove( 'current-fragment' );
907
+ } );
618
908
  }
619
909
 
620
910
  sync();
@@ -637,7 +927,14 @@ var Reveal = (function(){
637
927
  dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
638
928
 
639
929
  // Support pointer-style touch interaction as well
640
- if( window.navigator.msPointerEnabled ) {
930
+ if( window.navigator.pointerEnabled ) {
931
+ // IE 11 uses un-prefixed version of pointer events
932
+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
933
+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
934
+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
935
+ }
936
+ else if( window.navigator.msPointerEnabled ) {
937
+ // IE 10 uses prefixed version of pointer events
641
938
  dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
642
939
  dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
643
940
  dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
@@ -646,13 +943,14 @@ var Reveal = (function(){
646
943
 
647
944
  if( config.keyboard ) {
648
945
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
946
+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
649
947
  }
650
948
 
651
949
  if( config.progress && dom.progress ) {
652
950
  dom.progress.addEventListener( 'click', onProgressClicked, false );
653
951
  }
654
952
 
655
- if( config.focusBodyOnPageVisiblityChange ) {
953
+ if( config.focusBodyOnPageVisibilityChange ) {
656
954
  var visibilityChange;
657
955
 
658
956
  if( 'hidden' in document ) {
@@ -670,7 +968,17 @@ var Reveal = (function(){
670
968
  }
671
969
  }
672
970
 
673
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
971
+ // Listen to both touch and click events, in case the device
972
+ // supports both
973
+ var pointerEvents = [ 'touchstart', 'click' ];
974
+
975
+ // Only support touch for Android, fixes double navigations in
976
+ // stock browser
977
+ if( navigator.userAgent.match( /android/gi ) ) {
978
+ pointerEvents = [ 'touchstart' ];
979
+ }
980
+
981
+ pointerEvents.forEach( function( eventName ) {
674
982
  dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
983
  dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
984
  dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
@@ -689,6 +997,7 @@ var Reveal = (function(){
689
997
  eventsAreBound = false;
690
998
 
691
999
  document.removeEventListener( 'keydown', onDocumentKeyDown, false );
1000
+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
692
1001
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
693
1002
  window.removeEventListener( 'resize', onWindowResize, false );
694
1003
 
@@ -696,7 +1005,14 @@ var Reveal = (function(){
696
1005
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
697
1006
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
698
1007
 
699
- if( window.navigator.msPointerEnabled ) {
1008
+ // IE11
1009
+ if( window.navigator.pointerEnabled ) {
1010
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
1011
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
1012
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
1013
+ }
1014
+ // IE10
1015
+ else if( window.navigator.msPointerEnabled ) {
700
1016
  dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
701
1017
  dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
702
1018
  dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
@@ -738,6 +1054,22 @@ var Reveal = (function(){
738
1054
 
739
1055
  }
740
1056
 
1057
+ /**
1058
+ * Utility for deserializing a value.
1059
+ */
1060
+ function deserialize( value ) {
1061
+
1062
+ if( typeof value === 'string' ) {
1063
+ if( value === 'null' ) return null;
1064
+ else if( value === 'true' ) return true;
1065
+ else if( value === 'false' ) return false;
1066
+ else if( value.match( /^\d+$/ ) ) return parseFloat( value );
1067
+ }
1068
+
1069
+ return value;
1070
+
1071
+ }
1072
+
741
1073
  /**
742
1074
  * Measures the distance in pixels between point a
743
1075
  * and point b.
@@ -762,11 +1094,119 @@ var Reveal = (function(){
762
1094
  element.style.WebkitTransform = transform;
763
1095
  element.style.MozTransform = transform;
764
1096
  element.style.msTransform = transform;
765
- element.style.OTransform = transform;
766
1097
  element.style.transform = transform;
767
1098
 
768
1099
  }
769
1100
 
1101
+ /**
1102
+ * Applies CSS transforms to the slides container. The container
1103
+ * is transformed from two separate sources: layout and the overview
1104
+ * mode.
1105
+ */
1106
+ function transformSlides( transforms ) {
1107
+
1108
+ // Pick up new transforms from arguments
1109
+ if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
1110
+ if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
1111
+
1112
+ // Apply the transforms to the slides container
1113
+ if( slidesTransform.layout ) {
1114
+ transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
1115
+ }
1116
+ else {
1117
+ transformElement( dom.slides, slidesTransform.overview );
1118
+ }
1119
+
1120
+ }
1121
+
1122
+ /**
1123
+ * Injects the given CSS styles into the DOM.
1124
+ */
1125
+ function injectStyleSheet( value ) {
1126
+
1127
+ var tag = document.createElement( 'style' );
1128
+ tag.type = 'text/css';
1129
+ if( tag.styleSheet ) {
1130
+ tag.styleSheet.cssText = value;
1131
+ }
1132
+ else {
1133
+ tag.appendChild( document.createTextNode( value ) );
1134
+ }
1135
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
1136
+
1137
+ }
1138
+
1139
+ /**
1140
+ * Converts various color input formats to an {r:0,g:0,b:0} object.
1141
+ *
1142
+ * @param {String} color The string representation of a color,
1143
+ * the following formats are supported:
1144
+ * - #000
1145
+ * - #000000
1146
+ * - rgb(0,0,0)
1147
+ */
1148
+ function colorToRgb( color ) {
1149
+
1150
+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
1151
+ if( hex3 && hex3[1] ) {
1152
+ hex3 = hex3[1];
1153
+ return {
1154
+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
1155
+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
1156
+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
1157
+ };
1158
+ }
1159
+
1160
+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
1161
+ if( hex6 && hex6[1] ) {
1162
+ hex6 = hex6[1];
1163
+ return {
1164
+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
1165
+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
1166
+ b: parseInt( hex6.substr( 4, 2 ), 16 )
1167
+ };
1168
+ }
1169
+
1170
+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
1171
+ if( rgb ) {
1172
+ return {
1173
+ r: parseInt( rgb[1], 10 ),
1174
+ g: parseInt( rgb[2], 10 ),
1175
+ b: parseInt( rgb[3], 10 )
1176
+ };
1177
+ }
1178
+
1179
+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
1180
+ if( rgba ) {
1181
+ return {
1182
+ r: parseInt( rgba[1], 10 ),
1183
+ g: parseInt( rgba[2], 10 ),
1184
+ b: parseInt( rgba[3], 10 ),
1185
+ a: parseFloat( rgba[4] )
1186
+ };
1187
+ }
1188
+
1189
+ return null;
1190
+
1191
+ }
1192
+
1193
+ /**
1194
+ * Calculates brightness on a scale of 0-255.
1195
+ *
1196
+ * @param color See colorStringToRgb for supported formats.
1197
+ */
1198
+ function colorBrightness( color ) {
1199
+
1200
+ if( typeof color === 'string' ) color = colorToRgb( color );
1201
+
1202
+ if( color ) {
1203
+ return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
1204
+ }
1205
+
1206
+ return null;
1207
+
1208
+ }
1209
+
770
1210
  /**
771
1211
  * Retrieves the height of the given element by looking
772
1212
  * at the position and height of its immediate children.
@@ -782,7 +1222,7 @@ var Reveal = (function(){
782
1222
 
783
1223
  if( typeof child.offsetTop === 'number' && child.style ) {
784
1224
  // Count # of abs children
785
- if( child.style.position === 'absolute' ) {
1225
+ if( window.getComputedStyle( child ).position === 'absolute' ) {
786
1226
  absoluteChildren += 1;
787
1227
  }
788
1228
 
@@ -804,40 +1244,26 @@ var Reveal = (function(){
804
1244
 
805
1245
  /**
806
1246
  * Returns the remaining height within the parent of the
807
- * target element after subtracting the height of all
808
- * siblings.
1247
+ * target element.
809
1248
  *
810
- * remaining height = [parent height] - [ siblings height]
1249
+ * remaining height = [ configured parent height ] - [ current parent height ]
811
1250
  */
812
1251
  function getRemainingHeight( element, height ) {
813
1252
 
814
1253
  height = height || 0;
815
1254
 
816
1255
  if( element ) {
817
- var parent = element.parentNode;
818
- var siblings = parent.childNodes;
819
-
820
- // Subtract the height of each sibling
821
- toArray( siblings ).forEach( function( sibling ) {
822
-
823
- if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
1256
+ var newHeight, oldHeight = element.style.height;
824
1257
 
825
- var styles = window.getComputedStyle( sibling ),
826
- marginTop = parseInt( styles.marginTop, 10 ),
827
- marginBottom = parseInt( styles.marginBottom, 10 );
1258
+ // Change the .stretch element height to 0 in order find the height of all
1259
+ // the other elements
1260
+ element.style.height = '0px';
1261
+ newHeight = height - element.parentNode.offsetHeight;
828
1262
 
829
- height -= sibling.offsetHeight + marginTop + marginBottom;
830
-
831
- }
832
-
833
- } );
834
-
835
- var elementStyles = window.getComputedStyle( element );
836
-
837
- // Subtract the margins of the target element
838
- height -= parseInt( elementStyles.marginTop, 10 ) +
839
- parseInt( elementStyles.marginBottom, 10 );
1263
+ // Restore the old height, just in case
1264
+ element.style.height = oldHeight + 'px';
840
1265
 
1266
+ return newHeight;
841
1267
  }
842
1268
 
843
1269
  return height;
@@ -882,13 +1308,19 @@ var Reveal = (function(){
882
1308
  * Dispatches an event of the specified type from the
883
1309
  * reveal DOM element.
884
1310
  */
885
- function dispatchEvent( type, properties ) {
1311
+ function dispatchEvent( type, args ) {
886
1312
 
887
- var event = document.createEvent( "HTMLEvents", 1, 2 );
1313
+ var event = document.createEvent( 'HTMLEvents', 1, 2 );
888
1314
  event.initEvent( type, true, true );
889
- extend( event, properties );
1315
+ extend( event, args );
890
1316
  dom.wrapper.dispatchEvent( event );
891
1317
 
1318
+ // If we're in an iframe, post each reveal.js event to the
1319
+ // parent window. Used by the notes plugin
1320
+ if( config.postMessageEvents && window.parent !== window.self ) {
1321
+ window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
1322
+ }
1323
+
892
1324
  }
893
1325
 
894
1326
  /**
@@ -897,7 +1329,7 @@ var Reveal = (function(){
897
1329
  function enableRollingLinks() {
898
1330
 
899
1331
  if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
900
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
1332
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
901
1333
 
902
1334
  for( var i = 0, len = anchors.length; i < len; i++ ) {
903
1335
  var anchor = anchors[i];
@@ -921,7 +1353,7 @@ var Reveal = (function(){
921
1353
  */
922
1354
  function disableRollingLinks() {
923
1355
 
924
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
1356
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
925
1357
 
926
1358
  for( var i = 0, len = anchors.length; i < len; i++ ) {
927
1359
  var anchor = anchors[i];
@@ -968,53 +1400,98 @@ var Reveal = (function(){
968
1400
  /**
969
1401
  * Opens a preview window for the target URL.
970
1402
  */
971
- function openPreview( url ) {
1403
+ function showPreview( url ) {
972
1404
 
973
- closePreview();
1405
+ closeOverlay();
974
1406
 
975
- dom.preview = document.createElement( 'div' );
976
- dom.preview.classList.add( 'preview-link-overlay' );
977
- dom.wrapper.appendChild( dom.preview );
1407
+ dom.overlay = document.createElement( 'div' );
1408
+ dom.overlay.classList.add( 'overlay' );
1409
+ dom.overlay.classList.add( 'overlay-preview' );
1410
+ dom.wrapper.appendChild( dom.overlay );
978
1411
 
979
- dom.preview.innerHTML = [
1412
+ dom.overlay.innerHTML = [
980
1413
  '<header>',
981
- '<a class="close" href="#"><span class="icon"></span></a>',
982
- '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
1414
+ '<a class="close" href="#"><span class="icon"></span></a>',
1415
+ '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
983
1416
  '</header>',
984
1417
  '<div class="spinner"></div>',
985
1418
  '<div class="viewport">',
986
- '<iframe src="'+ url +'"></iframe>',
1419
+ '<iframe src="'+ url +'"></iframe>',
987
1420
  '</div>'
988
1421
  ].join('');
989
1422
 
990
- dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
991
- dom.preview.classList.add( 'loaded' );
1423
+ dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
1424
+ dom.overlay.classList.add( 'loaded' );
992
1425
  }, false );
993
1426
 
994
- dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
995
- closePreview();
1427
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1428
+ closeOverlay();
996
1429
  event.preventDefault();
997
1430
  }, false );
998
1431
 
999
- dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1000
- closePreview();
1432
+ dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1433
+ closeOverlay();
1001
1434
  }, false );
1002
1435
 
1003
1436
  setTimeout( function() {
1004
- dom.preview.classList.add( 'visible' );
1437
+ dom.overlay.classList.add( 'visible' );
1005
1438
  }, 1 );
1006
1439
 
1007
1440
  }
1008
1441
 
1009
1442
  /**
1010
- * Closes the iframe preview window.
1443
+ * Opens a overlay window with help material.
1011
1444
  */
1012
- function closePreview() {
1445
+ function showHelp() {
1446
+
1447
+ if( config.help ) {
1448
+
1449
+ closeOverlay();
1450
+
1451
+ dom.overlay = document.createElement( 'div' );
1452
+ dom.overlay.classList.add( 'overlay' );
1453
+ dom.overlay.classList.add( 'overlay-help' );
1454
+ dom.wrapper.appendChild( dom.overlay );
1455
+
1456
+ var html = '<p class="title">Keyboard Shortcuts</p><br/>';
1457
+
1458
+ html += '<table><th>KEY</th><th>ACTION</th>';
1459
+ for( var key in keyboardShortcuts ) {
1460
+ html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
1461
+ }
1462
+
1463
+ html += '</table>';
1464
+
1465
+ dom.overlay.innerHTML = [
1466
+ '<header>',
1467
+ '<a class="close" href="#"><span class="icon"></span></a>',
1468
+ '</header>',
1469
+ '<div class="viewport">',
1470
+ '<div class="viewport-inner">'+ html +'</div>',
1471
+ '</div>'
1472
+ ].join('');
1473
+
1474
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1475
+ closeOverlay();
1476
+ event.preventDefault();
1477
+ }, false );
1478
+
1479
+ setTimeout( function() {
1480
+ dom.overlay.classList.add( 'visible' );
1481
+ }, 1 );
1013
1482
 
1014
- if( dom.preview ) {
1015
- dom.preview.setAttribute( 'src', '' );
1016
- dom.preview.parentNode.removeChild( dom.preview );
1017
- dom.preview = null;
1483
+ }
1484
+
1485
+ }
1486
+
1487
+ /**
1488
+ * Closes any currently open overlay.
1489
+ */
1490
+ function closeOverlay() {
1491
+
1492
+ if( dom.overlay ) {
1493
+ dom.overlay.parentNode.removeChild( dom.overlay );
1494
+ dom.overlay = null;
1018
1495
  }
1019
1496
 
1020
1497
  }
@@ -1027,54 +1504,50 @@ var Reveal = (function(){
1027
1504
 
1028
1505
  if( dom.wrapper && !isPrintingPDF() ) {
1029
1506
 
1030
- // Available space to scale within
1031
- var availableWidth = dom.wrapper.offsetWidth,
1032
- availableHeight = dom.wrapper.offsetHeight;
1507
+ var size = getComputedSlideSize();
1033
1508
 
1034
- // Reduce available space by margin
1035
- availableWidth -= ( availableHeight * config.margin );
1036
- availableHeight -= ( availableHeight * config.margin );
1037
-
1038
- // Dimensions of the content
1039
- var slideWidth = config.width,
1040
- slideHeight = config.height,
1041
- slidePadding = 20; // TODO Dig this out of DOM
1509
+ var slidePadding = 20; // TODO Dig this out of DOM
1042
1510
 
1043
1511
  // Layout the contents of the slides
1044
1512
  layoutSlideContents( config.width, config.height, slidePadding );
1045
1513
 
1046
- // Slide width may be a percentage of available width
1047
- if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
1048
- slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
1049
- }
1050
-
1051
- // Slide height may be a percentage of available height
1052
- if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
1053
- slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
1054
- }
1055
-
1056
- dom.slides.style.width = slideWidth + 'px';
1057
- dom.slides.style.height = slideHeight + 'px';
1514
+ dom.slides.style.width = size.width + 'px';
1515
+ dom.slides.style.height = size.height + 'px';
1058
1516
 
1059
1517
  // Determine scale of content to fit within available space
1060
- scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1518
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1061
1519
 
1062
1520
  // Respect max/min scale settings
1063
1521
  scale = Math.max( scale, config.minScale );
1064
1522
  scale = Math.min( scale, config.maxScale );
1065
1523
 
1066
- // Prefer applying scale via zoom since Chrome blurs scaled content
1067
- // with nested transforms
1068
- if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
1069
- dom.slides.style.zoom = scale;
1524
+ // Don't apply any scaling styles if scale is 1
1525
+ if( scale === 1 ) {
1526
+ dom.slides.style.zoom = '';
1527
+ dom.slides.style.left = '';
1528
+ dom.slides.style.top = '';
1529
+ dom.slides.style.bottom = '';
1530
+ dom.slides.style.right = '';
1531
+ transformSlides( { layout: '' } );
1070
1532
  }
1071
- // Apply scale transform as a fallback
1072
1533
  else {
1073
- transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1534
+ // Prefer zooming in desktop Chrome so that content remains crisp
1535
+ if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
1536
+ dom.slides.style.zoom = scale;
1537
+ transformSlides( { layout: '' } );
1538
+ }
1539
+ // Apply scale transform as a fallback
1540
+ else {
1541
+ dom.slides.style.left = '50%';
1542
+ dom.slides.style.top = '50%';
1543
+ dom.slides.style.bottom = 'auto';
1544
+ dom.slides.style.right = 'auto';
1545
+ transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
1546
+ }
1074
1547
  }
1075
1548
 
1076
1549
  // Select all slides, vertical and horizontal
1077
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1550
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
1078
1551
 
1079
1552
  for( var i = 0, len = slides.length; i < len; i++ ) {
1080
1553
  var slide = slides[ i ];
@@ -1091,7 +1564,7 @@ var Reveal = (function(){
1091
1564
  slide.style.top = 0;
1092
1565
  }
1093
1566
  else {
1094
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1567
+ slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
1095
1568
  }
1096
1569
  }
1097
1570
  else {
@@ -1117,7 +1590,7 @@ var Reveal = (function(){
1117
1590
  toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
1591
 
1119
1592
  // Determine how much vertical space we can use
1120
- var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1593
+ var remainingHeight = getRemainingHeight( element, height );
1121
1594
 
1122
1595
  // Consider the aspect ratio of media elements
1123
1596
  if( /(img|video)/gi.test( element.nodeName ) ) {
@@ -1139,6 +1612,41 @@ var Reveal = (function(){
1139
1612
 
1140
1613
  }
1141
1614
 
1615
+ /**
1616
+ * Calculates the computed pixel size of our slides. These
1617
+ * values are based on the width and height configuration
1618
+ * options.
1619
+ */
1620
+ function getComputedSlideSize( presentationWidth, presentationHeight ) {
1621
+
1622
+ var size = {
1623
+ // Slide size
1624
+ width: config.width,
1625
+ height: config.height,
1626
+
1627
+ // Presentation size
1628
+ presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
1629
+ presentationHeight: presentationHeight || dom.wrapper.offsetHeight
1630
+ };
1631
+
1632
+ // Reduce available space by margin
1633
+ size.presentationWidth -= ( size.presentationWidth * config.margin );
1634
+ size.presentationHeight -= ( size.presentationHeight * config.margin );
1635
+
1636
+ // Slide width may be a percentage of available width
1637
+ if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
1638
+ size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
1639
+ }
1640
+
1641
+ // Slide height may be a percentage of available height
1642
+ if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
1643
+ size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
1644
+ }
1645
+
1646
+ return size;
1647
+
1648
+ }
1649
+
1142
1650
  /**
1143
1651
  * Stores the vertical index of a stack so that the same
1144
1652
  * vertical slide can be selected when navigating to and
@@ -1176,92 +1684,122 @@ var Reveal = (function(){
1176
1684
  }
1177
1685
 
1178
1686
  /**
1179
- * Displays the overview of slides (quick nav) by
1180
- * scaling down and arranging all slide elements.
1181
- *
1182
- * Experimental feature, might be dropped if perf
1183
- * can't be improved.
1687
+ * Displays the overview of slides (quick nav) by scaling
1688
+ * down and arranging all slide elements.
1184
1689
  */
1185
1690
  function activateOverview() {
1186
1691
 
1187
1692
  // Only proceed if enabled in config
1188
- if( config.overview ) {
1693
+ if( config.overview && !isOverview() ) {
1189
1694
 
1190
- // Don't auto-slide while in overview mode
1191
- cancelAutoSlide();
1192
-
1193
- var wasActive = dom.wrapper.classList.contains( 'overview' );
1194
-
1195
- // Vary the depth of the overview based on screen size
1196
- var depth = window.innerWidth < 400 ? 1000 : 2500;
1695
+ overview = true;
1197
1696
 
1198
1697
  dom.wrapper.classList.add( 'overview' );
1199
1698
  dom.wrapper.classList.remove( 'overview-deactivating' );
1200
1699
 
1201
- clearTimeout( activateOverviewTimeout );
1202
- clearTimeout( deactivateOverviewTimeout );
1700
+ if( features.overviewTransitions ) {
1701
+ setTimeout( function() {
1702
+ dom.wrapper.classList.add( 'overview-animated' );
1703
+ }, 1 );
1704
+ }
1203
1705
 
1204
- // Not the pretties solution, but need to let the overview
1205
- // class apply first so that slides are measured accurately
1206
- // before we can position them
1207
- activateOverviewTimeout = setTimeout( function() {
1706
+ // Don't auto-slide while in overview mode
1707
+ cancelAutoSlide();
1208
1708
 
1209
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1709
+ // Move the backgrounds element into the slide container to
1710
+ // that the same scaling is applied
1711
+ dom.slides.appendChild( dom.background );
1210
1712
 
1211
- for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1212
- var hslide = horizontalSlides[i],
1213
- hoffset = config.rtl ? -105 : 105;
1713
+ // Clicking on an overview slide navigates to it
1714
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1715
+ if( !slide.classList.contains( 'stack' ) ) {
1716
+ slide.addEventListener( 'click', onOverviewSlideClicked, true );
1717
+ }
1718
+ } );
1214
1719
 
1215
- hslide.setAttribute( 'data-index-h', i );
1720
+ updateSlidesVisibility();
1721
+ layoutOverview();
1722
+ updateOverview();
1216
1723
 
1217
- // Apply CSS transform
1218
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1724
+ layout();
1219
1725
 
1220
- if( hslide.classList.contains( 'stack' ) ) {
1726
+ // Notify observers of the overview showing
1727
+ dispatchEvent( 'overviewshown', {
1728
+ 'indexh': indexh,
1729
+ 'indexv': indexv,
1730
+ 'currentSlide': currentSlide
1731
+ } );
1221
1732
 
1222
- var verticalSlides = hslide.querySelectorAll( 'section' );
1733
+ }
1223
1734
 
1224
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1225
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1735
+ }
1226
1736
 
1227
- var vslide = verticalSlides[j];
1737
+ /**
1738
+ * Uses CSS transforms to position all slides in a grid for
1739
+ * display inside of the overview mode.
1740
+ */
1741
+ function layoutOverview() {
1228
1742
 
1229
- vslide.setAttribute( 'data-index-h', i );
1230
- vslide.setAttribute( 'data-index-v', j );
1743
+ var margin = 70;
1744
+ var slideWidth = config.width + margin,
1745
+ slideHeight = config.height + margin;
1231
1746
 
1232
- // Apply CSS transform
1233
- transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1747
+ // Reverse in RTL mode
1748
+ if( config.rtl ) {
1749
+ slideWidth = -slideWidth;
1750
+ }
1234
1751
 
1235
- // Navigate to this slide on click
1236
- vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1237
- }
1752
+ // Layout slides
1753
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
1754
+ hslide.setAttribute( 'data-index-h', h );
1755
+ transformElement( hslide, 'translate3d(' + ( h * slideWidth ) + 'px, 0, 0)' );
1238
1756
 
1239
- }
1240
- else {
1757
+ if( hslide.classList.contains( 'stack' ) ) {
1241
1758
 
1242
- // Navigate to this slide on click
1243
- hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1759
+ toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
1760
+ vslide.setAttribute( 'data-index-h', h );
1761
+ vslide.setAttribute( 'data-index-v', v );
1244
1762
 
1245
- }
1246
- }
1763
+ transformElement( vslide, 'translate3d(0, ' + ( v * slideHeight ) + 'px, 0)' );
1764
+ } );
1247
1765
 
1248
- updateSlidesVisibility();
1766
+ }
1767
+ } );
1249
1768
 
1250
- layout();
1769
+ // Layout slide backgrounds
1770
+ toArray( dom.background.childNodes ).forEach( function( hbackground, h ) {
1771
+ transformElement( hbackground, 'translate3d(' + ( h * slideWidth ) + 'px, 0, 0)' );
1251
1772
 
1252
- if( !wasActive ) {
1253
- // Notify observers of the overview showing
1254
- dispatchEvent( 'overviewshown', {
1255
- 'indexh': indexh,
1256
- 'indexv': indexv,
1257
- 'currentSlide': currentSlide
1258
- } );
1259
- }
1773
+ toArray( hbackground.querySelectorAll( '.slide-background' ) ).forEach( function( vbackground, v ) {
1774
+ transformElement( vbackground, 'translate3d(0, ' + ( v * slideHeight ) + 'px, 0)' );
1775
+ } );
1776
+ } );
1260
1777
 
1261
- }, 10 );
1778
+ }
1262
1779
 
1780
+ /**
1781
+ * Moves the overview viewport to the current slides.
1782
+ * Called each time the current slide changes.
1783
+ */
1784
+ function updateOverview() {
1785
+
1786
+ var margin = 70;
1787
+ var slideWidth = config.width + margin,
1788
+ slideHeight = config.height + margin;
1789
+
1790
+ // Reverse in RTL mode
1791
+ if( config.rtl ) {
1792
+ slideWidth = -slideWidth;
1263
1793
  }
1264
1794
 
1795
+ transformSlides( {
1796
+ overview: [
1797
+ 'translateX('+ ( -indexh * slideWidth ) +'px)',
1798
+ 'translateY('+ ( -indexv * slideHeight ) +'px)',
1799
+ 'translateZ('+ ( window.innerWidth < 400 ? -1000 : -2500 ) +'px)'
1800
+ ].join( ' ' )
1801
+ } );
1802
+
1265
1803
  }
1266
1804
 
1267
1805
  /**
@@ -1273,30 +1811,41 @@ var Reveal = (function(){
1273
1811
  // Only proceed if enabled in config
1274
1812
  if( config.overview ) {
1275
1813
 
1276
- clearTimeout( activateOverviewTimeout );
1277
- clearTimeout( deactivateOverviewTimeout );
1814
+ overview = false;
1278
1815
 
1279
1816
  dom.wrapper.classList.remove( 'overview' );
1817
+ dom.wrapper.classList.remove( 'overview-animated' );
1280
1818
 
1281
1819
  // Temporarily add a class so that transitions can do different things
1282
1820
  // depending on whether they are exiting/entering overview, or just
1283
1821
  // moving from slide to slide
1284
1822
  dom.wrapper.classList.add( 'overview-deactivating' );
1285
1823
 
1286
- deactivateOverviewTimeout = setTimeout( function () {
1824
+ setTimeout( function () {
1287
1825
  dom.wrapper.classList.remove( 'overview-deactivating' );
1288
1826
  }, 1 );
1289
1827
 
1290
- // Select all slides
1291
- toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1292
- // Resets all transforms to use the external styles
1828
+ // Move the background element back out
1829
+ dom.wrapper.appendChild( dom.background );
1830
+
1831
+ // Clean up changes made to slides
1832
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1293
1833
  transformElement( slide, '' );
1294
1834
 
1295
1835
  slide.removeEventListener( 'click', onOverviewSlideClicked, true );
1296
1836
  } );
1297
1837
 
1838
+ // Clean up changes made to backgrounds
1839
+ toArray( dom.background.querySelectorAll( '.slide-background' ) ).forEach( function( background ) {
1840
+ transformElement( background, '' );
1841
+ } );
1842
+
1843
+ transformSlides( { overview: '' } );
1844
+
1298
1845
  slide( indexh, indexv );
1299
1846
 
1847
+ layout();
1848
+
1300
1849
  cueAutoSlide();
1301
1850
 
1302
1851
  // Notify observers of the overview hiding
@@ -1335,7 +1884,7 @@ var Reveal = (function(){
1335
1884
  */
1336
1885
  function isOverview() {
1337
1886
 
1338
- return dom.wrapper.classList.contains( 'overview' );
1887
+ return overview;
1339
1888
 
1340
1889
  }
1341
1890
 
@@ -1367,10 +1916,10 @@ var Reveal = (function(){
1367
1916
 
1368
1917
  // Check which implementation is available
1369
1918
  var requestMethod = element.requestFullScreen ||
1370
- element.webkitRequestFullscreen ||
1371
- element.webkitRequestFullScreen ||
1372
- element.mozRequestFullScreen ||
1373
- element.msRequestFullScreen;
1919
+ element.webkitRequestFullscreen ||
1920
+ element.webkitRequestFullScreen ||
1921
+ element.mozRequestFullScreen ||
1922
+ element.msRequestFullscreen;
1374
1923
 
1375
1924
  if( requestMethod ) {
1376
1925
  requestMethod.apply( element );
@@ -1384,13 +1933,15 @@ var Reveal = (function(){
1384
1933
  */
1385
1934
  function pause() {
1386
1935
 
1387
- var wasPaused = dom.wrapper.classList.contains( 'paused' );
1936
+ if( config.pause ) {
1937
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1388
1938
 
1389
- cancelAutoSlide();
1390
- dom.wrapper.classList.add( 'paused' );
1939
+ cancelAutoSlide();
1940
+ dom.wrapper.classList.add( 'paused' );
1391
1941
 
1392
- if( wasPaused === false ) {
1393
- dispatchEvent( 'paused' );
1942
+ if( wasPaused === false ) {
1943
+ dispatchEvent( 'paused' );
1944
+ }
1394
1945
  }
1395
1946
 
1396
1947
  }
@@ -1414,13 +1965,13 @@ var Reveal = (function(){
1414
1965
  /**
1415
1966
  * Toggles the paused mode on and off.
1416
1967
  */
1417
- function togglePause() {
1968
+ function togglePause( override ) {
1418
1969
 
1419
- if( isPaused() ) {
1420
- resume();
1970
+ if( typeof override === 'boolean' ) {
1971
+ override ? pause() : resume();
1421
1972
  }
1422
1973
  else {
1423
- pause();
1974
+ isPaused() ? resume() : pause();
1424
1975
  }
1425
1976
 
1426
1977
  }
@@ -1434,6 +1985,34 @@ var Reveal = (function(){
1434
1985
 
1435
1986
  }
1436
1987
 
1988
+ /**
1989
+ * Toggles the auto slide mode on and off.
1990
+ *
1991
+ * @param {Boolean} override Optional flag which sets the desired state.
1992
+ * True means autoplay starts, false means it stops.
1993
+ */
1994
+
1995
+ function toggleAutoSlide( override ) {
1996
+
1997
+ if( typeof override === 'boolean' ) {
1998
+ override ? resumeAutoSlide() : pauseAutoSlide();
1999
+ }
2000
+
2001
+ else {
2002
+ autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
2003
+ }
2004
+
2005
+ }
2006
+
2007
+ /**
2008
+ * Checks if the auto slide mode is currently on.
2009
+ */
2010
+ function isAutoSliding() {
2011
+
2012
+ return !!( autoSlide && !autoSlidePaused );
2013
+
2014
+ }
2015
+
1437
2016
  /**
1438
2017
  * Steps from the current point in the presentation to the
1439
2018
  * slide which matches the specified horizontal and vertical
@@ -1451,11 +2030,11 @@ var Reveal = (function(){
1451
2030
  previousSlide = currentSlide;
1452
2031
 
1453
2032
  // Query all horizontal slides in the deck
1454
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
2033
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1455
2034
 
1456
2035
  // If no vertical index is specified and the upcoming slide is a
1457
2036
  // stack, resume at its previous vertical index
1458
- if( v === undefined ) {
2037
+ if( v === undefined && !isOverview() ) {
1459
2038
  v = getPreviousVerticalIndex( horizontalSlides[ h ] );
1460
2039
  }
1461
2040
 
@@ -1505,9 +2084,9 @@ var Reveal = (function(){
1505
2084
  document.documentElement.classList.remove( stateBefore.pop() );
1506
2085
  }
1507
2086
 
1508
- // If the overview is active, re-activate it to update positions
2087
+ // Update the overview if it's currently active
1509
2088
  if( isOverview() ) {
1510
- activateOverview();
2089
+ updateOverview();
1511
2090
  }
1512
2091
 
1513
2092
  // Find the current horizontal slide and any possible vertical slides
@@ -1544,13 +2123,14 @@ var Reveal = (function(){
1544
2123
  // stacks
1545
2124
  if( previousSlide ) {
1546
2125
  previousSlide.classList.remove( 'present' );
2126
+ previousSlide.setAttribute( 'aria-hidden', 'true' );
1547
2127
 
1548
2128
  // Reset all slides upon navigate to home
1549
2129
  // Issue: #285
1550
- if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
2130
+ if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1551
2131
  // Launch async task
1552
2132
  setTimeout( function () {
1553
- var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
2133
+ var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1554
2134
  for( i in slides ) {
1555
2135
  if( slides[i] ) {
1556
2136
  // Reset stack
@@ -1562,11 +2142,14 @@ var Reveal = (function(){
1562
2142
  }
1563
2143
 
1564
2144
  // Handle embedded content
1565
- if( slideChanged ) {
2145
+ if( slideChanged || !previousSlide ) {
1566
2146
  stopEmbeddedContent( previousSlide );
1567
2147
  startEmbeddedContent( currentSlide );
1568
2148
  }
1569
2149
 
2150
+ // Announce the current slide contents, for screen readers
2151
+ dom.statusDiv.textContent = currentSlide.textContent;
2152
+
1570
2153
  updateControls();
1571
2154
  updateProgress();
1572
2155
  updateBackground();
@@ -1603,12 +2186,23 @@ var Reveal = (function(){
1603
2186
  // Re-create the slide backgrounds
1604
2187
  createBackgrounds();
1605
2188
 
2189
+ // Write the current hash to the URL
2190
+ writeURL();
2191
+
1606
2192
  sortAllFragments();
1607
2193
 
1608
2194
  updateControls();
1609
2195
  updateProgress();
1610
2196
  updateBackground( true );
1611
2197
  updateSlideNumber();
2198
+ updateSlidesVisibility();
2199
+
2200
+ formatEmbeddedContent();
2201
+ startEmbeddedContent( currentSlide );
2202
+
2203
+ if( isOverview() ) {
2204
+ layoutOverview();
2205
+ }
1612
2206
 
1613
2207
  }
1614
2208
 
@@ -1618,7 +2212,7 @@ var Reveal = (function(){
1618
2212
  */
1619
2213
  function resetVerticalSlides() {
1620
2214
 
1621
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2215
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
2216
  horizontalSlides.forEach( function( horizontalSlide ) {
1623
2217
 
1624
2218
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1628,6 +2222,7 @@ var Reveal = (function(){
1628
2222
  verticalSlide.classList.remove( 'present' );
1629
2223
  verticalSlide.classList.remove( 'past' );
1630
2224
  verticalSlide.classList.add( 'future' );
2225
+ verticalSlide.setAttribute( 'aria-hidden', 'true' );
1631
2226
  }
1632
2227
 
1633
2228
  } );
@@ -1642,7 +2237,7 @@ var Reveal = (function(){
1642
2237
  */
1643
2238
  function sortAllFragments() {
1644
2239
 
1645
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2240
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
2241
  horizontalSlides.forEach( function( horizontalSlide ) {
1647
2242
 
1648
2243
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1675,9 +2270,11 @@ var Reveal = (function(){
1675
2270
 
1676
2271
  // Select all slides and convert the NodeList result to
1677
2272
  // an array
1678
- var slides = toArray( document.querySelectorAll( selector ) ),
2273
+ var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
1679
2274
  slidesLength = slides.length;
1680
2275
 
2276
+ var printMode = isPrintingPDF();
2277
+
1681
2278
  if( slidesLength ) {
1682
2279
 
1683
2280
  // Should the index loop?
@@ -1703,43 +2300,55 @@ var Reveal = (function(){
1703
2300
 
1704
2301
  // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1705
2302
  element.setAttribute( 'hidden', '' );
2303
+ element.setAttribute( 'aria-hidden', 'true' );
2304
+
2305
+ // If this element contains vertical slides
2306
+ if( element.querySelector( 'section' ) ) {
2307
+ element.classList.add( 'stack' );
2308
+ }
2309
+
2310
+ // If we're printing static slides, all slides are "present"
2311
+ if( printMode ) {
2312
+ element.classList.add( 'present' );
2313
+ continue;
2314
+ }
1706
2315
 
1707
2316
  if( i < index ) {
1708
2317
  // Any element previous to index is given the 'past' class
1709
2318
  element.classList.add( reverse ? 'future' : 'past' );
1710
2319
 
1711
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
2320
+ if( config.fragments ) {
2321
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
2322
 
1713
- // Show all fragments on prior slides
1714
- while( pastFragments.length ) {
1715
- var pastFragment = pastFragments.pop();
1716
- pastFragment.classList.add( 'visible' );
1717
- pastFragment.classList.remove( 'current-fragment' );
2323
+ // Show all fragments on prior slides
2324
+ while( pastFragments.length ) {
2325
+ var pastFragment = pastFragments.pop();
2326
+ pastFragment.classList.add( 'visible' );
2327
+ pastFragment.classList.remove( 'current-fragment' );
2328
+ }
1718
2329
  }
1719
2330
  }
1720
2331
  else if( i > index ) {
1721
2332
  // Any element subsequent to index is given the 'future' class
1722
2333
  element.classList.add( reverse ? 'past' : 'future' );
1723
2334
 
1724
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
2335
+ if( config.fragments ) {
2336
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
2337
 
1726
- // No fragments in future slides should be visible ahead of time
1727
- while( futureFragments.length ) {
1728
- var futureFragment = futureFragments.pop();
1729
- futureFragment.classList.remove( 'visible' );
1730
- futureFragment.classList.remove( 'current-fragment' );
2338
+ // No fragments in future slides should be visible ahead of time
2339
+ while( futureFragments.length ) {
2340
+ var futureFragment = futureFragments.pop();
2341
+ futureFragment.classList.remove( 'visible' );
2342
+ futureFragment.classList.remove( 'current-fragment' );
2343
+ }
1731
2344
  }
1732
2345
  }
1733
-
1734
- // If this element contains vertical slides
1735
- if( element.querySelector( 'section' ) ) {
1736
- element.classList.add( 'stack' );
1737
- }
1738
2346
  }
1739
2347
 
1740
2348
  // Mark the current slide as present
1741
2349
  slides[index].classList.add( 'present' );
1742
2350
  slides[index].removeAttribute( 'hidden' );
2351
+ slides[index].removeAttribute( 'aria-hidden' );
1743
2352
 
1744
2353
  // If this slide has a state associated with it, add it
1745
2354
  // onto the current state of the deck
@@ -1767,12 +2376,12 @@ var Reveal = (function(){
1767
2376
 
1768
2377
  // Select all slides and convert the NodeList result to
1769
2378
  // an array
1770
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
2379
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
2380
  horizontalSlidesLength = horizontalSlides.length,
1772
2381
  distanceX,
1773
2382
  distanceY;
1774
2383
 
1775
- if( horizontalSlidesLength ) {
2384
+ if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
1776
2385
 
1777
2386
  // The number of steps away from the present slide that will
1778
2387
  // be visible
@@ -1780,7 +2389,12 @@ var Reveal = (function(){
1780
2389
 
1781
2390
  // Limit view distance on weaker devices
1782
2391
  if( isMobileDevice ) {
1783
- viewDistance = isOverview() ? 6 : 1;
2392
+ viewDistance = isOverview() ? 6 : 2;
2393
+ }
2394
+
2395
+ // All slides need to be visible when exporting to PDF
2396
+ if( isPrintingPDF() ) {
2397
+ viewDistance = Number.MAX_VALUE;
1784
2398
  }
1785
2399
 
1786
2400
  for( var x = 0; x < horizontalSlidesLength; x++ ) {
@@ -1789,11 +2403,22 @@ var Reveal = (function(){
1789
2403
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
1790
2404
  verticalSlidesLength = verticalSlides.length;
1791
2405
 
1792
- // Loops so that it measures 1 between the first and last slides
1793
- distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2406
+ // Determine how far away this slide is from the present
2407
+ distanceX = Math.abs( ( indexh || 0 ) - x ) || 0;
2408
+
2409
+ // If the presentation is looped, distance should measure
2410
+ // 1 between the first and last slides
2411
+ if( config.loop ) {
2412
+ distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2413
+ }
1794
2414
 
1795
2415
  // Show the horizontal slide if it's within the view distance
1796
- horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
2416
+ if( distanceX < viewDistance ) {
2417
+ showSlide( horizontalSlide );
2418
+ }
2419
+ else {
2420
+ hideSlide( horizontalSlide );
2421
+ }
1797
2422
 
1798
2423
  if( verticalSlidesLength ) {
1799
2424
 
@@ -1802,9 +2427,14 @@ var Reveal = (function(){
1802
2427
  for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
2428
  var verticalSlide = verticalSlides[y];
1804
2429
 
1805
- distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
2430
+ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
1806
2431
 
1807
- verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
2432
+ if( distanceX + distanceY < viewDistance ) {
2433
+ showSlide( verticalSlide );
2434
+ }
2435
+ else {
2436
+ hideSlide( verticalSlide );
2437
+ }
1808
2438
  }
1809
2439
 
1810
2440
  }
@@ -1820,44 +2450,9 @@ var Reveal = (function(){
1820
2450
  function updateProgress() {
1821
2451
 
1822
2452
  // Update progress if enabled
1823
- if( config.progress && dom.progress ) {
1824
-
1825
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1826
-
1827
- // The number of past and total slides
1828
- var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
1829
- var pastCount = 0;
1830
-
1831
- // Step through all slides and count the past ones
1832
- mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
1833
-
1834
- var horizontalSlide = horizontalSlides[i];
1835
- var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1836
-
1837
- for( var j = 0; j < verticalSlides.length; j++ ) {
1838
-
1839
- // Stop as soon as we arrive at the present
1840
- if( verticalSlides[j].classList.contains( 'present' ) ) {
1841
- break mainLoop;
1842
- }
2453
+ if( config.progress && dom.progressbar ) {
1843
2454
 
1844
- pastCount++;
1845
-
1846
- }
1847
-
1848
- // Stop as soon as we arrive at the present
1849
- if( horizontalSlide.classList.contains( 'present' ) ) {
1850
- break;
1851
- }
1852
-
1853
- // Don't count the wrapping section for vertical slides
1854
- if( horizontalSlide.classList.contains( 'stack' ) === false ) {
1855
- pastCount++;
1856
- }
1857
-
1858
- }
1859
-
1860
- dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
2455
+ dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
1861
2456
 
1862
2457
  }
1863
2458
 
@@ -1865,19 +2460,31 @@ var Reveal = (function(){
1865
2460
 
1866
2461
  /**
1867
2462
  * Updates the slide number div to reflect the current slide.
2463
+ *
2464
+ * Slide number format can be defined as a string using the
2465
+ * following variables:
2466
+ * h: current slide's horizontal index
2467
+ * v: current slide's vertical index
2468
+ * c: current slide index (flattened)
2469
+ * t: total number of slides (flattened)
1868
2470
  */
1869
2471
  function updateSlideNumber() {
1870
2472
 
1871
2473
  // Update slide number if enabled
1872
2474
  if( config.slideNumber && dom.slideNumber) {
1873
2475
 
1874
- // Display the number of the page using 'indexh - indexv' format
1875
- var indexString = indexh;
1876
- if( indexv > 0 ) {
1877
- indexString += ' - ' + indexv;
2476
+ // Default to only showing the current slide number
2477
+ var format = 'c';
2478
+
2479
+ // Check if a custom slide number format is available
2480
+ if( typeof config.slideNumber === 'string' ) {
2481
+ format = config.slideNumber;
1878
2482
  }
1879
2483
 
1880
- dom.slideNumber.innerHTML = indexString;
2484
+ dom.slideNumber.innerHTML = format.replace( /h/g, indexh )
2485
+ .replace( /v/g, indexv )
2486
+ .replace( /c/g, getSlidePastCount() + 1 )
2487
+ .replace( /t/g, getTotalSlides() );
1881
2488
  }
1882
2489
 
1883
2490
  }
@@ -1892,13 +2499,13 @@ var Reveal = (function(){
1892
2499
 
1893
2500
  // Remove the 'enabled' class from all directions
1894
2501
  dom.controlsLeft.concat( dom.controlsRight )
1895
- .concat( dom.controlsUp )
1896
- .concat( dom.controlsDown )
1897
- .concat( dom.controlsPrev )
1898
- .concat( dom.controlsNext ).forEach( function( node ) {
1899
- node.classList.remove( 'enabled' );
1900
- node.classList.remove( 'fragmented' );
1901
- } );
2502
+ .concat( dom.controlsUp )
2503
+ .concat( dom.controlsDown )
2504
+ .concat( dom.controlsPrev )
2505
+ .concat( dom.controlsNext ).forEach( function( node ) {
2506
+ node.classList.remove( 'enabled' );
2507
+ node.classList.remove( 'fragmented' );
2508
+ } );
1902
2509
 
1903
2510
  // Add the 'enabled' class to the available routes
1904
2511
  if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
@@ -1951,43 +2558,76 @@ var Reveal = (function(){
1951
2558
  // states of their slides (past/present/future)
1952
2559
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1953
2560
 
2561
+ backgroundh.classList.remove( 'past' );
2562
+ backgroundh.classList.remove( 'present' );
2563
+ backgroundh.classList.remove( 'future' );
2564
+
1954
2565
  if( h < indexh ) {
1955
- backgroundh.className = 'slide-background ' + horizontalPast;
2566
+ backgroundh.classList.add( horizontalPast );
1956
2567
  }
1957
2568
  else if ( h > indexh ) {
1958
- backgroundh.className = 'slide-background ' + horizontalFuture;
2569
+ backgroundh.classList.add( horizontalFuture );
1959
2570
  }
1960
2571
  else {
1961
- backgroundh.className = 'slide-background present';
2572
+ backgroundh.classList.add( 'present' );
1962
2573
 
1963
2574
  // Store a reference to the current background element
1964
2575
  currentBackground = backgroundh;
1965
2576
  }
1966
2577
 
1967
2578
  if( includeAll || h === indexh ) {
1968
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
2579
+ toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
2580
+
2581
+ backgroundv.classList.remove( 'past' );
2582
+ backgroundv.classList.remove( 'present' );
2583
+ backgroundv.classList.remove( 'future' );
1969
2584
 
1970
2585
  if( v < indexv ) {
1971
- backgroundv.className = 'slide-background past';
2586
+ backgroundv.classList.add( 'past' );
1972
2587
  }
1973
2588
  else if ( v > indexv ) {
1974
- backgroundv.className = 'slide-background future';
2589
+ backgroundv.classList.add( 'future' );
1975
2590
  }
1976
2591
  else {
1977
- backgroundv.className = 'slide-background present';
2592
+ backgroundv.classList.add( 'present' );
1978
2593
 
1979
2594
  // Only if this is the present horizontal and vertical slide
1980
2595
  if( h === indexh ) currentBackground = backgroundv;
1981
2596
  }
1982
2597
 
1983
- } );
2598
+ } );
2599
+ }
2600
+
2601
+ } );
2602
+
2603
+ // Stop any currently playing video background
2604
+ if( previousBackground ) {
2605
+
2606
+ var previousVideo = previousBackground.querySelector( 'video' );
2607
+ if( previousVideo ) previousVideo.pause();
2608
+
2609
+ }
2610
+
2611
+ if( currentBackground ) {
2612
+
2613
+ // Start video playback
2614
+ var currentVideo = currentBackground.querySelector( 'video' );
2615
+ if( currentVideo ) {
2616
+ currentVideo.currentTime = 0;
2617
+ currentVideo.play();
1984
2618
  }
1985
2619
 
1986
- } );
2620
+ var backgroundImageURL = currentBackground.style.backgroundImage || '';
1987
2621
 
1988
- // Don't transition between identical backgrounds. This
1989
- // prevents unwanted flicker.
1990
- if( currentBackground ) {
2622
+ // Restart GIFs (doesn't work in Firefox)
2623
+ if( /\.gif/i.test( backgroundImageURL ) ) {
2624
+ currentBackground.style.backgroundImage = '';
2625
+ window.getComputedStyle( currentBackground ).opacity;
2626
+ currentBackground.style.backgroundImage = backgroundImageURL;
2627
+ }
2628
+
2629
+ // Don't transition between identical backgrounds. This
2630
+ // prevents unwanted flicker.
1991
2631
  var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
2632
  var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
2633
  if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
@@ -1995,6 +2635,20 @@ var Reveal = (function(){
1995
2635
  }
1996
2636
 
1997
2637
  previousBackground = currentBackground;
2638
+
2639
+ }
2640
+
2641
+ // If there's a background brightness flag for this slide,
2642
+ // bubble it to the .reveal container
2643
+ if( currentSlide ) {
2644
+ [ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
2645
+ if( currentSlide.classList.contains( classToBubble ) ) {
2646
+ dom.wrapper.classList.add( classToBubble );
2647
+ }
2648
+ else {
2649
+ dom.wrapper.classList.remove( classToBubble );
2650
+ }
2651
+ } );
1998
2652
  }
1999
2653
 
2000
2654
  // Allow the first background to apply without transition
@@ -2012,8 +2666,8 @@ var Reveal = (function(){
2012
2666
 
2013
2667
  if( config.parallaxBackgroundImage ) {
2014
2668
 
2015
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2669
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2670
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
2671
 
2018
2672
  var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
2673
  backgroundWidth, backgroundHeight;
@@ -2026,16 +2680,138 @@ var Reveal = (function(){
2026
2680
  backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
2681
  }
2028
2682
 
2029
- var slideWidth = dom.background.offsetWidth;
2030
- var horizontalSlideCount = horizontalSlides.length;
2031
- var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2683
+ var slideWidth = dom.background.offsetWidth,
2684
+ horizontalSlideCount = horizontalSlides.length,
2685
+ horizontalOffsetMultiplier,
2686
+ horizontalOffset;
2687
+
2688
+ if( typeof config.parallaxBackgroundHorizontal === 'number' ) {
2689
+ horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;
2690
+ }
2691
+ else {
2692
+ horizontalOffsetMultiplier = ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 );
2693
+ }
2694
+
2695
+ horizontalOffset = horizontalOffsetMultiplier * indexh * -1;
2696
+
2697
+ var slideHeight = dom.background.offsetHeight,
2698
+ verticalSlideCount = verticalSlides.length,
2699
+ verticalOffsetMultiplier,
2700
+ verticalOffset;
2701
+
2702
+ if( typeof config.parallaxBackgroundVertical === 'number' ) {
2703
+ verticalOffsetMultiplier = config.parallaxBackgroundVertical;
2704
+ }
2705
+ else {
2706
+ verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );
2707
+ }
2708
+
2709
+ verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv * 1 : 0;
2710
+
2711
+ dom.background.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';
2712
+
2713
+ }
2714
+
2715
+ }
2716
+
2717
+ /**
2718
+ * Called when the given slide is within the configured view
2719
+ * distance. Shows the slide element and loads any content
2720
+ * that is set to load lazily (data-src).
2721
+ */
2722
+ function showSlide( slide ) {
2723
+
2724
+ // Show the slide element
2725
+ slide.style.display = 'block';
2726
+
2727
+ // Media elements with data-src attributes
2728
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
2729
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
2730
+ element.removeAttribute( 'data-src' );
2731
+ } );
2732
+
2733
+ // Media elements with <source> children
2734
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
2735
+ var sources = 0;
2736
+
2737
+ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
2738
+ source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
2739
+ source.removeAttribute( 'data-src' );
2740
+ sources += 1;
2741
+ } );
2742
+
2743
+ // If we rewrote sources for this video/audio element, we need
2744
+ // to manually tell it to load from its new origin
2745
+ if( sources > 0 ) {
2746
+ media.load();
2747
+ }
2748
+ } );
2749
+
2750
+
2751
+ // Show the corresponding background element
2752
+ var indices = getIndices( slide );
2753
+ var background = getSlideBackground( indices.h, indices.v );
2754
+ if( background ) {
2755
+ background.style.display = 'block';
2756
+
2757
+ // If the background contains media, load it
2758
+ if( background.hasAttribute( 'data-loaded' ) === false ) {
2759
+ background.setAttribute( 'data-loaded', 'true' );
2760
+
2761
+ var backgroundImage = slide.getAttribute( 'data-background-image' ),
2762
+ backgroundVideo = slide.getAttribute( 'data-background-video' ),
2763
+ backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
2764
+ backgroundIframe = slide.getAttribute( 'data-background-iframe' );
2765
+
2766
+ // Images
2767
+ if( backgroundImage ) {
2768
+ background.style.backgroundImage = 'url('+ backgroundImage +')';
2769
+ }
2770
+ // Videos
2771
+ else if ( backgroundVideo && !isSpeakerNotes() ) {
2772
+ var video = document.createElement( 'video' );
2773
+
2774
+ if( backgroundVideoLoop ) {
2775
+ video.setAttribute( 'loop', '' );
2776
+ }
2777
+
2778
+ // Support comma separated lists of video sources
2779
+ backgroundVideo.split( ',' ).forEach( function( source ) {
2780
+ video.innerHTML += '<source src="'+ source +'">';
2781
+ } );
2782
+
2783
+ background.appendChild( video );
2784
+ }
2785
+ // Iframes
2786
+ else if( backgroundIframe ) {
2787
+ var iframe = document.createElement( 'iframe' );
2788
+ iframe.setAttribute( 'src', backgroundIframe );
2789
+ iframe.style.width = '100%';
2790
+ iframe.style.height = '100%';
2791
+ iframe.style.maxHeight = '100%';
2792
+ iframe.style.maxWidth = '100%';
2793
+
2794
+ background.appendChild( iframe );
2795
+ }
2796
+ }
2797
+ }
2798
+
2799
+ }
2032
2800
 
2033
- var slideHeight = dom.background.offsetHeight;
2034
- var verticalSlideCount = verticalSlides.length;
2035
- var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2801
+ /**
2802
+ * Called when the given slide is moved outside of the
2803
+ * configured view distance.
2804
+ */
2805
+ function hideSlide( slide ) {
2036
2806
 
2037
- dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2807
+ // Hide the slide element
2808
+ slide.style.display = 'none';
2038
2809
 
2810
+ // Hide the corresponding background element
2811
+ var indices = getIndices( slide );
2812
+ var background = getSlideBackground( indices.h, indices.v );
2813
+ if( background ) {
2814
+ background.style.display = 'none';
2039
2815
  }
2040
2816
 
2041
2817
  }
@@ -2047,8 +2823,8 @@ var Reveal = (function(){
2047
2823
  */
2048
2824
  function availableRoutes() {
2049
2825
 
2050
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2051
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2826
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2827
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2052
2828
 
2053
2829
  var routes = {
2054
2830
  left: indexh > 0 || config.loop,
@@ -2091,6 +2867,30 @@ var Reveal = (function(){
2091
2867
 
2092
2868
  }
2093
2869
 
2870
+ /**
2871
+ * Enforces origin-specific format rules for embedded media.
2872
+ */
2873
+ function formatEmbeddedContent() {
2874
+
2875
+ var _appendParamToIframeSource = function( sourceAttribute, sourceURL, param ) {
2876
+ toArray( dom.slides.querySelectorAll( 'iframe['+ sourceAttribute +'*="'+ sourceURL +'"]' ) ).forEach( function( el ) {
2877
+ var src = el.getAttribute( sourceAttribute );
2878
+ if( src && src.indexOf( param ) === -1 ) {
2879
+ el.setAttribute( sourceAttribute, src + ( !/\?/.test( src ) ? '?' : '&' ) + param );
2880
+ }
2881
+ });
2882
+ };
2883
+
2884
+ // YouTube frames must include "?enablejsapi=1"
2885
+ _appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );
2886
+ _appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );
2887
+
2888
+ // Vimeo frames must include "?api=1"
2889
+ _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
2890
+ _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
2891
+
2892
+ }
2893
+
2094
2894
  /**
2095
2895
  * Start playback of any embedded content inside of
2096
2896
  * the targeted slide.
@@ -2098,24 +2898,56 @@ var Reveal = (function(){
2098
2898
  function startEmbeddedContent( slide ) {
2099
2899
 
2100
2900
  if( slide && !isSpeakerNotes() ) {
2901
+ // Restart GIFs
2902
+ toArray( slide.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
2903
+ // Setting the same unchanged source like this was confirmed
2904
+ // to work in Chrome, FF & Safari
2905
+ el.setAttribute( 'src', el.getAttribute( 'src' ) );
2906
+ } );
2907
+
2101
2908
  // HTML5 media elements
2102
2909
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2103
- if( el.hasAttribute( 'data-autoplay' ) ) {
2910
+ if( el.hasAttribute( 'data-autoplay' ) && typeof el.play === 'function' ) {
2104
2911
  el.play();
2105
2912
  }
2106
2913
  } );
2107
2914
 
2108
- // iframe embeds
2109
- toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
- el.contentWindow.postMessage( 'slide:start', '*' );
2111
- });
2915
+ // Normal iframes
2916
+ toArray( slide.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
2917
+ startEmbeddedIframe( { target: el } );
2918
+ } );
2112
2919
 
2113
- // YouTube embeds
2114
- toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2115
- if( el.hasAttribute( 'data-autoplay' ) ) {
2116
- el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2920
+ // Lazy loading iframes
2921
+ toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
2922
+ if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
2923
+ el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes
2924
+ el.addEventListener( 'load', startEmbeddedIframe );
2925
+ el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
2117
2926
  }
2118
- });
2927
+ } );
2928
+ }
2929
+
2930
+ }
2931
+
2932
+ /**
2933
+ * "Starts" the content of an embedded iframe using the
2934
+ * postmessage API.
2935
+ */
2936
+ function startEmbeddedIframe( event ) {
2937
+
2938
+ var iframe = event.target;
2939
+
2940
+ // YouTube postMessage API
2941
+ if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
2942
+ iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2943
+ }
2944
+ // Vimeo postMessage API
2945
+ else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
2946
+ iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
2947
+ }
2948
+ // Generic postMessage API
2949
+ else {
2950
+ iframe.contentWindow.postMessage( 'slide:start', '*' );
2119
2951
  }
2120
2952
 
2121
2953
  }
@@ -2126,27 +2958,120 @@ var Reveal = (function(){
2126
2958
  */
2127
2959
  function stopEmbeddedContent( slide ) {
2128
2960
 
2129
- if( slide ) {
2961
+ if( slide && slide.parentNode ) {
2130
2962
  // HTML5 media elements
2131
2963
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2132
- if( !el.hasAttribute( 'data-ignore' ) ) {
2964
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
2133
2965
  el.pause();
2134
2966
  }
2135
2967
  } );
2136
2968
 
2137
- // iframe embeds
2969
+ // Generic postMessage API for non-lazy loaded iframes
2138
2970
  toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
2971
  el.contentWindow.postMessage( 'slide:stop', '*' );
2972
+ el.removeEventListener( 'load', startEmbeddedIframe );
2140
2973
  });
2141
2974
 
2142
- // YouTube embeds
2975
+ // YouTube postMessage API
2143
2976
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2144
2977
  if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2145
2978
  el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
2146
2979
  }
2147
2980
  });
2981
+
2982
+ // Vimeo postMessage API
2983
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2984
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2985
+ el.contentWindow.postMessage( '{"method":"pause"}', '*' );
2986
+ }
2987
+ });
2988
+
2989
+ // Lazy loading iframes
2990
+ toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
2991
+ // Only removing the src doesn't actually unload the frame
2992
+ // in all browsers (Firefox) so we set it to blank first
2993
+ el.setAttribute( 'src', 'about:blank' );
2994
+ el.removeAttribute( 'src' );
2995
+ } );
2996
+ }
2997
+
2998
+ }
2999
+
3000
+ /**
3001
+ * Returns the number of past slides. This can be used as a global
3002
+ * flattened index for slides.
3003
+ */
3004
+ function getSlidePastCount() {
3005
+
3006
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3007
+
3008
+ // The number of past slides
3009
+ var pastCount = 0;
3010
+
3011
+ // Step through all slides and count the past ones
3012
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
3013
+
3014
+ var horizontalSlide = horizontalSlides[i];
3015
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
3016
+
3017
+ for( var j = 0; j < verticalSlides.length; j++ ) {
3018
+
3019
+ // Stop as soon as we arrive at the present
3020
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
3021
+ break mainLoop;
3022
+ }
3023
+
3024
+ pastCount++;
3025
+
3026
+ }
3027
+
3028
+ // Stop as soon as we arrive at the present
3029
+ if( horizontalSlide.classList.contains( 'present' ) ) {
3030
+ break;
3031
+ }
3032
+
3033
+ // Don't count the wrapping section for vertical slides
3034
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
3035
+ pastCount++;
3036
+ }
3037
+
3038
+ }
3039
+
3040
+ return pastCount;
3041
+
3042
+ }
3043
+
3044
+ /**
3045
+ * Returns a value ranging from 0-1 that represents
3046
+ * how far into the presentation we have navigated.
3047
+ */
3048
+ function getProgress() {
3049
+
3050
+ // The number of past and total slides
3051
+ var totalCount = getTotalSlides();
3052
+ var pastCount = getSlidePastCount();
3053
+
3054
+ if( currentSlide ) {
3055
+
3056
+ var allFragments = currentSlide.querySelectorAll( '.fragment' );
3057
+
3058
+ // If there are fragments in the current slide those should be
3059
+ // accounted for in the progress.
3060
+ if( allFragments.length > 0 ) {
3061
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
3062
+
3063
+ // This value represents how big a portion of the slide progress
3064
+ // that is made up by its fragments (0-1)
3065
+ var fragmentWeight = 0.9;
3066
+
3067
+ // Add fragment progress to the past slide count
3068
+ pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
3069
+ }
3070
+
2148
3071
  }
2149
3072
 
3073
+ return pastCount / ( totalCount - 1 );
3074
+
2150
3075
  }
2151
3076
 
2152
3077
  /**
@@ -2173,8 +3098,13 @@ var Reveal = (function(){
2173
3098
  // If the first bit is invalid and there is a name we can
2174
3099
  // assume that this is a named link
2175
3100
  if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
2176
- // Find the slide with the specified name
2177
- var element = document.querySelector( '#' + name );
3101
+ var element;
3102
+
3103
+ // Ensure the named link is a valid HTML ID attribute
3104
+ if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
3105
+ // Find the slide with the specified ID
3106
+ element = document.getElementById( name );
3107
+ }
2178
3108
 
2179
3109
  if( element ) {
2180
3110
  // Find the position of the named slide and navigate to it
@@ -2216,12 +3146,19 @@ var Reveal = (function(){
2216
3146
  if( typeof delay === 'number' ) {
2217
3147
  writeURLTimeout = setTimeout( writeURL, delay );
2218
3148
  }
2219
- else {
3149
+ else if( currentSlide ) {
2220
3150
  var url = '/';
2221
3151
 
3152
+ // Attempt to create a named link based on the slide's ID
3153
+ var id = currentSlide.getAttribute( 'id' );
3154
+ if( id ) {
3155
+ id = id.toLowerCase();
3156
+ id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
3157
+ }
3158
+
2222
3159
  // If the current slide has an ID, use that as a named link
2223
- if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
2224
- url = '/' + currentSlide.getAttribute( 'id' );
3160
+ if( typeof id === 'string' && id.length ) {
3161
+ url = '/' + id;
2225
3162
  }
2226
3163
  // Otherwise use the /h/v index
2227
3164
  else {
@@ -2258,11 +3195,14 @@ var Reveal = (function(){
2258
3195
  var slideh = isVertical ? slide.parentNode : slide;
2259
3196
 
2260
3197
  // Select all horizontal slides
2261
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3198
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2262
3199
 
2263
3200
  // Now that we know which the horizontal slide is, get its index
2264
3201
  h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2265
3202
 
3203
+ // Assume we're not vertical
3204
+ v = undefined;
3205
+
2266
3206
  // If this is a vertical slide, grab the vertical index
2267
3207
  if( isVertical ) {
2268
3208
  v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
@@ -2272,8 +3212,13 @@ var Reveal = (function(){
2272
3212
  if( !slide && currentSlide ) {
2273
3213
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
3214
  if( hasFragments ) {
2275
- var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
- f = visibleFragments.length - 1;
3215
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3216
+ if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
3217
+ f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
3218
+ }
3219
+ else {
3220
+ f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
3221
+ }
2277
3222
  }
2278
3223
  }
2279
3224
 
@@ -2281,6 +3226,107 @@ var Reveal = (function(){
2281
3226
 
2282
3227
  }
2283
3228
 
3229
+ /**
3230
+ * Retrieves the total number of slides in this presentation.
3231
+ */
3232
+ function getTotalSlides() {
3233
+
3234
+ return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
3235
+
3236
+ }
3237
+
3238
+ /**
3239
+ * Returns the slide element matching the specified index.
3240
+ */
3241
+ function getSlide( x, y ) {
3242
+
3243
+ var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3244
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
3245
+
3246
+ if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
3247
+ return verticalSlides ? verticalSlides[ y ] : undefined;
3248
+ }
3249
+
3250
+ return horizontalSlide;
3251
+
3252
+ }
3253
+
3254
+ /**
3255
+ * Returns the background element for the given slide.
3256
+ * All slides, even the ones with no background properties
3257
+ * defined, have a background element so as long as the
3258
+ * index is valid an element will be returned.
3259
+ */
3260
+ function getSlideBackground( x, y ) {
3261
+
3262
+ // When printing to PDF the slide backgrounds are nested
3263
+ // inside of the slides
3264
+ if( isPrintingPDF() ) {
3265
+ var slide = getSlide( x, y );
3266
+ if( slide ) {
3267
+ var background = slide.querySelector( '.slide-background' );
3268
+ if( background && background.parentNode === slide ) {
3269
+ return background;
3270
+ }
3271
+ }
3272
+
3273
+ return undefined;
3274
+ }
3275
+
3276
+ var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
3277
+ var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
3278
+
3279
+ if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
3280
+ return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
3281
+ }
3282
+
3283
+ return horizontalBackground;
3284
+
3285
+ }
3286
+
3287
+ /**
3288
+ * Retrieves the current state of the presentation as
3289
+ * an object. This state can then be restored at any
3290
+ * time.
3291
+ */
3292
+ function getState() {
3293
+
3294
+ var indices = getIndices();
3295
+
3296
+ return {
3297
+ indexh: indices.h,
3298
+ indexv: indices.v,
3299
+ indexf: indices.f,
3300
+ paused: isPaused(),
3301
+ overview: isOverview()
3302
+ };
3303
+
3304
+ }
3305
+
3306
+ /**
3307
+ * Restores the presentation to the given state.
3308
+ *
3309
+ * @param {Object} state As generated by getState()
3310
+ */
3311
+ function setState( state ) {
3312
+
3313
+ if( typeof state === 'object' ) {
3314
+ slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
3315
+
3316
+ var pausedFlag = deserialize( state.paused ),
3317
+ overviewFlag = deserialize( state.overview );
3318
+
3319
+ if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
3320
+ togglePause( pausedFlag );
3321
+ }
3322
+
3323
+ if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
3324
+ toggleOverview( overviewFlag );
3325
+ }
3326
+ }
3327
+
3328
+ }
3329
+
2284
3330
  /**
2285
3331
  * Return a sorted fragments list, ordered by an increasing
2286
3332
  * "data-fragment-index" attribute.
@@ -2392,6 +3438,9 @@ var Reveal = (function(){
2392
3438
  element.classList.add( 'visible' );
2393
3439
  element.classList.remove( 'current-fragment' );
2394
3440
 
3441
+ // Announce the fragments one by one to the Screen Reader
3442
+ dom.statusDiv.textContent = element.textContent;
3443
+
2395
3444
  if( i === index ) {
2396
3445
  element.classList.add( 'current-fragment' );
2397
3446
  }
@@ -2415,6 +3464,7 @@ var Reveal = (function(){
2415
3464
  }
2416
3465
 
2417
3466
  updateControls();
3467
+ updateProgress();
2418
3468
 
2419
3469
  return !!( fragmentsShown.length || fragmentsHidden.length );
2420
3470
 
@@ -2459,14 +3509,21 @@ var Reveal = (function(){
2459
3509
 
2460
3510
  if( currentSlide ) {
2461
3511
 
3512
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3513
+
3514
+ var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
2462
3515
  var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
3516
  var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
3517
 
2465
3518
  // Pick value in the following priority order:
2466
- // 1. Current slide's data-autoslide
2467
- // 2. Parent slide's data-autoslide
2468
- // 3. Global autoSlide setting
2469
- if( slideAutoSlide ) {
3519
+ // 1. Current fragment's data-autoslide
3520
+ // 2. Current slide's data-autoslide
3521
+ // 3. Parent slide's data-autoslide
3522
+ // 4. Global autoSlide setting
3523
+ if( fragmentAutoSlide ) {
3524
+ autoSlide = parseInt( fragmentAutoSlide, 10 );
3525
+ }
3526
+ else if( slideAutoSlide ) {
2470
3527
  autoSlide = parseInt( slideAutoSlide, 10 );
2471
3528
  }
2472
3529
  else if( parentAutoSlide ) {
@@ -2478,14 +3535,17 @@ var Reveal = (function(){
2478
3535
 
2479
3536
  // If there are media elements with data-autoplay,
2480
3537
  // automatically set the autoSlide duration to the
2481
- // length of that media
2482
- toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2483
- if( el.hasAttribute( 'data-autoplay' ) ) {
2484
- if( autoSlide && el.duration * 1000 > autoSlide ) {
2485
- autoSlide = ( el.duration * 1000 ) + 1000;
3538
+ // length of that media. Not applicable if the slide
3539
+ // is divided up into fragments.
3540
+ if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
3541
+ toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
3542
+ if( el.hasAttribute( 'data-autoplay' ) ) {
3543
+ if( autoSlide && el.duration * 1000 > autoSlide ) {
3544
+ autoSlide = ( el.duration * 1000 ) + 1000;
3545
+ }
2486
3546
  }
2487
- }
2488
- } );
3547
+ } );
3548
+ }
2489
3549
 
2490
3550
  // Cue the next auto-slide if:
2491
3551
  // - There is an autoSlide value
@@ -2493,7 +3553,7 @@ var Reveal = (function(){
2493
3553
  // - The presentation isn't paused
2494
3554
  // - The overview isn't active
2495
3555
  // - The presentation isn't over
2496
- if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
3556
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
2497
3557
  autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
3558
  autoSlideStartTime = Date.now();
2499
3559
  }
@@ -2518,19 +3578,25 @@ var Reveal = (function(){
2518
3578
 
2519
3579
  function pauseAutoSlide() {
2520
3580
 
2521
- autoSlidePaused = true;
2522
- clearTimeout( autoSlideTimeout );
3581
+ if( autoSlide && !autoSlidePaused ) {
3582
+ autoSlidePaused = true;
3583
+ dispatchEvent( 'autoslidepaused' );
3584
+ clearTimeout( autoSlideTimeout );
2523
3585
 
2524
- if( autoSlidePlayer ) {
2525
- autoSlidePlayer.setPlaying( false );
3586
+ if( autoSlidePlayer ) {
3587
+ autoSlidePlayer.setPlaying( false );
3588
+ }
2526
3589
  }
2527
3590
 
2528
3591
  }
2529
3592
 
2530
3593
  function resumeAutoSlide() {
2531
3594
 
2532
- autoSlidePaused = false;
2533
- cueAutoSlide();
3595
+ if( autoSlide && autoSlidePaused ) {
3596
+ autoSlidePaused = false;
3597
+ dispatchEvent( 'autoslideresumed' );
3598
+ cueAutoSlide();
3599
+ }
2534
3600
 
2535
3601
  }
2536
3602
 
@@ -2597,7 +3663,14 @@ var Reveal = (function(){
2597
3663
  }
2598
3664
  else {
2599
3665
  // Fetch the previous horizontal slide, if there is one
2600
- var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
3666
+ var previousSlide;
3667
+
3668
+ if( config.rtl ) {
3669
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
3670
+ }
3671
+ else {
3672
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
3673
+ }
2601
3674
 
2602
3675
  if( previousSlide ) {
2603
3676
  var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
@@ -2610,13 +3683,21 @@ var Reveal = (function(){
2610
3683
  }
2611
3684
 
2612
3685
  /**
2613
- * Same as #navigatePrev() but navigates forwards.
3686
+ * The reverse of #navigatePrev().
2614
3687
  */
2615
3688
  function navigateNext() {
2616
3689
 
2617
3690
  // Prioritize revealing fragments
2618
3691
  if( nextFragment() === false ) {
2619
- availableRoutes().down ? navigateDown() : navigateRight();
3692
+ if( availableRoutes().down ) {
3693
+ navigateDown();
3694
+ }
3695
+ else if( config.rtl ) {
3696
+ navigateLeft();
3697
+ }
3698
+ else {
3699
+ navigateRight();
3700
+ }
2620
3701
  }
2621
3702
 
2622
3703
  // If auto-sliding is enabled we need to cue up
@@ -2642,21 +3723,47 @@ var Reveal = (function(){
2642
3723
 
2643
3724
  }
2644
3725
 
3726
+ /**
3727
+ * Handler for the document level 'keypress' event.
3728
+ */
3729
+ function onDocumentKeyPress( event ) {
3730
+
3731
+ // Check if the pressed key is question mark
3732
+ if( event.shiftKey && event.charCode === 63 ) {
3733
+ if( dom.overlay ) {
3734
+ closeOverlay();
3735
+ }
3736
+ else {
3737
+ showHelp( true );
3738
+ }
3739
+ }
3740
+
3741
+ }
3742
+
2645
3743
  /**
2646
3744
  * Handler for the document level 'keydown' event.
2647
3745
  */
2648
3746
  function onDocumentKeyDown( event ) {
2649
3747
 
3748
+ // If there's a condition specified and it returns false,
3749
+ // ignore this event
3750
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
3751
+ return true;
3752
+ }
3753
+
3754
+ // Remember if auto-sliding was paused so we can toggle it
3755
+ var autoSlideWasPaused = autoSlidePaused;
3756
+
2650
3757
  onUserInput( event );
2651
3758
 
2652
3759
  // Check if there's a focused element that could be using
2653
3760
  // the keyboard
2654
- var activeElement = document.activeElement;
2655
- var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
3761
+ var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
3762
+ var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
2656
3763
 
2657
3764
  // Disregard the event if there's a focused element or a
2658
3765
  // keyboard modifier key is present
2659
- if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
3766
+ if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2660
3767
 
2661
3768
  // While paused only allow "unpausing" keyboard events (b and .)
2662
3769
  if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
@@ -2719,10 +3826,12 @@ var Reveal = (function(){
2719
3826
  case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2720
3827
  // return
2721
3828
  case 13: isOverview() ? deactivateOverview() : triggered = false; break;
2722
- // b, period, Logitech presenter tools "black screen" button
2723
- case 66: case 190: case 191: togglePause(); break;
3829
+ // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
3830
+ case 58: case 59: case 66: case 190: case 191: togglePause(); break;
2724
3831
  // f
2725
3832
  case 70: enterFullscreen(); break;
3833
+ // a
3834
+ case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
2726
3835
  default:
2727
3836
  triggered = false;
2728
3837
  }
@@ -2732,18 +3841,18 @@ var Reveal = (function(){
2732
3841
  // If the input resulted in a triggered action we should prevent
2733
3842
  // the browsers default behavior
2734
3843
  if( triggered ) {
2735
- event.preventDefault();
3844
+ event.preventDefault && event.preventDefault();
2736
3845
  }
2737
3846
  // ESC or O key
2738
3847
  else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
- if( dom.preview ) {
2740
- closePreview();
3848
+ if( dom.overlay ) {
3849
+ closeOverlay();
2741
3850
  }
2742
3851
  else {
2743
3852
  toggleOverview();
2744
3853
  }
2745
3854
 
2746
- event.preventDefault();
3855
+ event.preventDefault && event.preventDefault();
2747
3856
  }
2748
3857
 
2749
3858
  // If auto-sliding is enabled we need to cue up
@@ -2877,7 +3986,7 @@ var Reveal = (function(){
2877
3986
  */
2878
3987
  function onPointerDown( event ) {
2879
3988
 
2880
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3989
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2881
3990
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2882
3991
  onTouchStart( event );
2883
3992
  }
@@ -2889,7 +3998,7 @@ var Reveal = (function(){
2889
3998
  */
2890
3999
  function onPointerMove( event ) {
2891
4000
 
2892
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
4001
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2893
4002
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2894
4003
  onTouchMove( event );
2895
4004
  }
@@ -2901,7 +4010,7 @@ var Reveal = (function(){
2901
4010
  */
2902
4011
  function onPointerUp( event ) {
2903
4012
 
2904
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
4013
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2905
4014
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2906
4015
  onTouchEnd( event );
2907
4016
  }
@@ -2942,9 +4051,13 @@ var Reveal = (function(){
2942
4051
 
2943
4052
  event.preventDefault();
2944
4053
 
2945
- var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
4054
+ var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2946
4055
  var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2947
4056
 
4057
+ if( config.rtl ) {
4058
+ slideIndex = slidesTotal - slideIndex;
4059
+ }
4060
+
2948
4061
  slide( slideIndex );
2949
4062
 
2950
4063
  }
@@ -2983,13 +4096,16 @@ var Reveal = (function(){
2983
4096
  function onPageVisibilityChange( event ) {
2984
4097
 
2985
4098
  var isHidden = document.webkitHidden ||
2986
- document.msHidden ||
2987
- document.hidden;
4099
+ document.msHidden ||
4100
+ document.hidden;
2988
4101
 
2989
4102
  // If, after clicking a link or similar and we're coming back,
2990
4103
  // focus the document.body to ensure we can use keyboard shortcuts
2991
4104
  if( isHidden === false && document.activeElement !== document.body ) {
2992
- document.activeElement.blur();
4105
+ // Not all elements support .blur() - SVGs among them.
4106
+ if( typeof document.activeElement.blur === 'function' ) {
4107
+ document.activeElement.blur();
4108
+ }
2993
4109
  document.body.focus();
2994
4110
  }
2995
4111
 
@@ -3033,10 +4149,12 @@ var Reveal = (function(){
3033
4149
  */
3034
4150
  function onPreviewLinkClicked( event ) {
3035
4151
 
3036
- var url = event.target.getAttribute( 'href' );
3037
- if( url ) {
3038
- openPreview( url );
3039
- event.preventDefault();
4152
+ if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
4153
+ var url = event.currentTarget.getAttribute( 'href' );
4154
+ if( url ) {
4155
+ showPreview( url );
4156
+ event.preventDefault();
4157
+ }
3040
4158
  }
3041
4159
 
3042
4160
  }
@@ -3232,7 +4350,7 @@ var Reveal = (function(){
3232
4350
  // --------------------------------------------------------------------//
3233
4351
 
3234
4352
 
3235
- return {
4353
+ Reveal = {
3236
4354
  initialize: initialize,
3237
4355
  configure: configure,
3238
4356
  sync: sync,
@@ -3275,28 +4393,35 @@ var Reveal = (function(){
3275
4393
  // Toggles the "black screen" mode on/off
3276
4394
  togglePause: togglePause,
3277
4395
 
4396
+ // Toggles the auto slide mode on/off
4397
+ toggleAutoSlide: toggleAutoSlide,
4398
+
3278
4399
  // State checks
3279
4400
  isOverview: isOverview,
3280
4401
  isPaused: isPaused,
4402
+ isAutoSliding: isAutoSliding,
3281
4403
 
3282
4404
  // Adds or removes all internal event listeners (such as keyboard)
3283
4405
  addEventListeners: addEventListeners,
3284
4406
  removeEventListeners: removeEventListeners,
3285
4407
 
4408
+ // Facility for persisting and restoring the presentation state
4409
+ getState: getState,
4410
+ setState: setState,
4411
+
4412
+ // Presentation progress on range of 0-1
4413
+ getProgress: getProgress,
4414
+
3286
4415
  // Returns the indices of the current, or specified, slide
3287
4416
  getIndices: getIndices,
3288
4417
 
3289
- // Returns the slide at the specified index, y is optional
3290
- getSlide: function( x, y ) {
3291
- var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3292
- var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
4418
+ getTotalSlides: getTotalSlides,
3293
4419
 
3294
- if( typeof y !== 'undefined' ) {
3295
- return verticalSlides ? verticalSlides[ y ] : undefined;
3296
- }
4420
+ // Returns the slide element at the specified index
4421
+ getSlide: getSlide,
3297
4422
 
3298
- return horizontalSlide;
3299
- },
4423
+ // Returns the slide background element at the specified index
4424
+ getSlideBackground: getSlideBackground,
3300
4425
 
3301
4426
  // Returns the previous slide element, may be null
3302
4427
  getPreviousSlide: function() {
@@ -3330,12 +4455,7 @@ var Reveal = (function(){
3330
4455
  for( var i in query ) {
3331
4456
  var value = query[ i ];
3332
4457
 
3333
- query[ i ] = unescape( value );
3334
-
3335
- if( value === 'null' ) query[ i ] = null;
3336
- else if( value === 'true' ) query[ i ] = true;
3337
- else if( value === 'false' ) query[ i ] = false;
3338
- else if( value.match( /^\d+$/ ) ) query[ i ] = parseFloat( value );
4458
+ query[ i ] = deserialize( unescape( value ) );
3339
4459
  }
3340
4460
 
3341
4461
  return query;
@@ -3343,7 +4463,7 @@ var Reveal = (function(){
3343
4463
 
3344
4464
  // Returns true if we're currently on the first slide
3345
4465
  isFirstSlide: function() {
3346
- return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
4466
+ return ( indexh === 0 && indexv === 0 );
3347
4467
  },
3348
4468
 
3349
4469
  // Returns true if we're currently on the last slide
@@ -3376,7 +4496,14 @@ var Reveal = (function(){
3376
4496
  if( 'addEventListener' in window ) {
3377
4497
  ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
3378
4498
  }
4499
+ },
4500
+
4501
+ // Programatically triggers a keyboard event
4502
+ triggerKey: function( keyCode ) {
4503
+ onDocumentKeyDown( { keyCode: keyCode } );
3379
4504
  }
3380
4505
  };
3381
4506
 
3382
- })();
4507
+ return Reveal;
4508
+
4509
+ }));