slide_hero 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/README.md +1 -1
  4. data/lib/slide_hero/version.rb +1 -1
  5. data/lib/slide_hero/views/layout.html.erb +2 -2
  6. data/vendor/reveal.js/.gitignore +3 -1
  7. data/vendor/reveal.js/.travis.yml +1 -1
  8. data/vendor/reveal.js/CONTRIBUTING.md +19 -0
  9. data/vendor/reveal.js/Gruntfile.js +58 -21
  10. data/vendor/reveal.js/LICENSE +1 -1
  11. data/vendor/reveal.js/README.md +154 -74
  12. data/vendor/reveal.js/css/print/paper.css +193 -167
  13. data/vendor/reveal.js/css/print/pdf.css +20 -53
  14. data/vendor/reveal.js/css/reveal.css +912 -1651
  15. data/vendor/reveal.js/css/reveal.scss +1316 -0
  16. data/vendor/reveal.js/css/theme/README.md +1 -1
  17. data/vendor/reveal.js/css/theme/beige.css +177 -60
  18. data/vendor/reveal.js/css/theme/black.css +261 -0
  19. data/vendor/reveal.js/css/theme/blood.css +191 -75
  20. data/vendor/reveal.js/css/theme/league.css +267 -0
  21. data/vendor/reveal.js/css/theme/moon.css +168 -51
  22. data/vendor/reveal.js/css/theme/night.css +165 -42
  23. data/vendor/reveal.js/css/theme/serif.css +181 -58
  24. data/vendor/reveal.js/css/theme/simple.css +173 -50
  25. data/vendor/reveal.js/css/theme/sky.css +170 -47
  26. data/vendor/reveal.js/css/theme/solarized.css +168 -51
  27. data/vendor/reveal.js/css/theme/source/beige.scss +1 -12
  28. data/vendor/reveal.js/css/theme/source/black.scss +49 -0
  29. data/vendor/reveal.js/css/theme/source/blood.scss +4 -4
  30. data/vendor/reveal.js/css/theme/source/{default.scss → league.scss} +5 -13
  31. data/vendor/reveal.js/css/theme/source/moon.scss +1 -12
  32. data/vendor/reveal.js/css/theme/source/serif.scss +1 -1
  33. data/vendor/reveal.js/css/theme/source/sky.scss +1 -1
  34. data/vendor/reveal.js/css/theme/source/solarized.scss +1 -12
  35. data/vendor/reveal.js/css/theme/source/white.scss +49 -0
  36. data/vendor/reveal.js/css/theme/template/settings.scss +13 -4
  37. data/vendor/reveal.js/css/theme/template/theme.scss +182 -13
  38. data/vendor/reveal.js/css/theme/white.css +261 -0
  39. data/vendor/reveal.js/index.html +198 -178
  40. data/vendor/reveal.js/js/reveal.js +1286 -392
  41. data/vendor/reveal.js/lib/css/zenburn.css +74 -71
  42. data/vendor/reveal.js/lib/font/{league_gothic_license → league-gothic/LICENSE} +0 -0
  43. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.css +10 -0
  44. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.eot +0 -0
  45. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.ttf +0 -0
  46. data/vendor/reveal.js/lib/font/league-gothic/league-gothic.woff +0 -0
  47. data/vendor/reveal.js/lib/font/source-sans-pro/LICENSE +45 -0
  48. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.eot +0 -0
  49. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.ttf +0 -0
  50. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.woff +0 -0
  51. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.eot +0 -0
  52. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.ttf +0 -0
  53. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.woff +0 -0
  54. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.eot +0 -0
  55. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.ttf +0 -0
  56. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.woff +0 -0
  57. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot +0 -0
  58. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf +0 -0
  59. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff +0 -0
  60. data/vendor/reveal.js/lib/font/source-sans-pro/source-sans-pro.css +39 -0
  61. data/vendor/reveal.js/package.json +9 -7
  62. data/vendor/reveal.js/plugin/highlight/highlight.js +2 -4
  63. data/vendor/reveal.js/plugin/leap/leap.js +4 -2
  64. data/vendor/reveal.js/plugin/markdown/example.html +2 -2
  65. data/vendor/reveal.js/plugin/markdown/markdown.js +8 -7
  66. data/vendor/reveal.js/plugin/notes/notes.html +321 -182
  67. data/vendor/reveal.js/plugin/notes/notes.js +89 -45
  68. data/vendor/reveal.js/plugin/notes-server/client.js +49 -46
  69. data/vendor/reveal.js/plugin/notes-server/index.js +28 -21
  70. data/vendor/reveal.js/plugin/notes-server/notes.html +351 -97
  71. data/vendor/reveal.js/plugin/print-pdf/print-pdf.js +24 -20
  72. data/vendor/reveal.js/plugin/zoom-js/zoom.js +77 -57
  73. data/vendor/reveal.js/test/examples/barebones.html +2 -2
  74. data/vendor/reveal.js/test/examples/embedded-media.html +2 -2
  75. data/vendor/reveal.js/test/examples/math.html +2 -2
  76. data/vendor/reveal.js/test/examples/slide-backgrounds.html +29 -7
  77. data/vendor/reveal.js/test/test-markdown-element-attributes.html +6 -6
  78. data/vendor/reveal.js/test/test-markdown-slide-attributes.html +7 -7
  79. data/vendor/reveal.js/test/test-markdown.html +4 -4
  80. data/vendor/reveal.js/test/test-pdf.html +83 -0
  81. data/vendor/reveal.js/test/test-pdf.js +15 -0
  82. data/vendor/reveal.js/test/test.html +5 -4
  83. data/vendor/reveal.js/test/test.js +143 -9
  84. metadata +31 -13
  85. data/vendor/reveal.js/css/reveal.min.css +0 -7
  86. data/vendor/reveal.js/css/theme/default.css +0 -148
  87. data/vendor/reveal.js/js/reveal.min.js +0 -9
  88. data/vendor/reveal.js/lib/font/league_gothic-webfont.eot +0 -0
  89. data/vendor/reveal.js/lib/font/league_gothic-webfont.svg +0 -230
  90. data/vendor/reveal.js/lib/font/league_gothic-webfont.ttf +0 -0
  91. data/vendor/reveal.js/lib/font/league_gothic-webfont.woff +0 -0
  92. data/vendor/reveal.js/plugin/postmessage/example.html +0 -39
  93. data/vendor/reveal.js/plugin/postmessage/postmessage.js +0 -42
@@ -3,16 +3,32 @@
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2015 Hakim El Hattab, http://hakim.se
7
7
  */
8
- var Reveal = (function(){
8
+ (function( root, factory ) {
9
+ if( typeof define === 'function' && define.amd ) {
10
+ // AMD. Register as an anonymous module.
11
+ define( function() {
12
+ root.Reveal = factory();
13
+ return root.Reveal;
14
+ } );
15
+ } else if( typeof exports === 'object' ) {
16
+ // Node. Does not work with strict CommonJS.
17
+ module.exports = factory();
18
+ } else {
19
+ // Browser globals.
20
+ root.Reveal = factory();
21
+ }
22
+ }( this, function() {
9
23
 
10
24
  'use strict';
11
25
 
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',
26
+ var Reveal;
27
+
28
+ var SLIDES_SELECTOR = '.slides section',
29
+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
30
+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
31
+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
16
32
 
17
33
  // Configurations defaults, can be overridden at initialization time
18
34
  config = {
@@ -27,7 +43,7 @@ var Reveal = (function(){
27
43
 
28
44
  // Bounds for smallest/largest possible scale to apply to content
29
45
  minScale: 0.2,
30
- maxScale: 1.0,
46
+ maxScale: 1.5,
31
47
 
32
48
  // Display controls in the bottom right corner
33
49
  controls: true,
@@ -44,6 +60,9 @@ var Reveal = (function(){
44
60
  // Enable keyboard shortcuts for navigation
45
61
  keyboard: true,
46
62
 
63
+ // Optional function that blocks keyboard events when retuning false
64
+ keyboardCondition: null,
65
+
47
66
  // Enable the slide overview mode
48
67
  overview: true,
49
68
 
@@ -66,6 +85,13 @@ var Reveal = (function(){
66
85
  // i.e. contained within a limited portion of the screen
67
86
  embedded: false,
68
87
 
88
+ // Flags if we should show a help overlay when the questionmark
89
+ // key is pressed
90
+ help: true,
91
+
92
+ // Flags if it should be possible to pause the presentation (blackout)
93
+ pause: true,
94
+
69
95
  // Number of milliseconds between automatically proceeding to the
70
96
  // next slide, disabled when set to 0, this value can be overwritten
71
97
  // by using a data-autoslide attribute on your slides
@@ -86,20 +112,23 @@ var Reveal = (function(){
86
112
  // Opens links in an iframe preview overlay
87
113
  previewLinks: false,
88
114
 
89
- // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
- focusBodyOnPageVisiblityChange: true,
115
+ // Exposes the reveal.js API through window.postMessage
116
+ postMessage: true,
91
117
 
92
- // Theme (see /css/theme)
93
- theme: null,
118
+ // Dispatches all reveal.js events to the parent window through postMessage
119
+ postMessageEvents: false,
120
+
121
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
122
+ focusBodyOnPageVisibilityChange: true,
94
123
 
95
124
  // Transition style
96
- transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
125
+ transition: 'slide', // none/fade/slide/convex/concave/zoom
97
126
 
98
127
  // Transition speed
99
128
  transitionSpeed: 'default', // default/fast/slow
100
129
 
101
130
  // Transition style for full page slide backgrounds
102
- backgroundTransition: 'default', // default/linear/none
131
+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
103
132
 
104
133
  // Parallax background image
105
134
  parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
@@ -151,12 +180,6 @@ var Reveal = (function(){
151
180
  // Delays updates to the URL due to a Chrome thumbnailer bug
152
181
  writeURLTimeout = 0,
153
182
 
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
183
  // Flags if the interaction event listeners are bound
161
184
  eventsAreBound = false,
162
185
 
@@ -177,6 +200,21 @@ var Reveal = (function(){
177
200
  startCount: 0,
178
201
  captured: false,
179
202
  threshold: 40
203
+ },
204
+
205
+ // Holds information about the keyboard shortcuts
206
+ keyboardShortcuts = {
207
+ 'N , SPACE': 'Next slide',
208
+ 'P': 'Previous slide',
209
+ '← , H': 'Navigate left',
210
+ '→ , L': 'Navigate right',
211
+ '↑ , K': 'Navigate up',
212
+ '↓ , J': 'Navigate down',
213
+ 'Home': 'First slide',
214
+ 'End': 'Last slide',
215
+ 'B , .': 'Pause',
216
+ 'F': 'Fullscreen',
217
+ 'ESC, O': 'Slide overview'
180
218
  };
181
219
 
182
220
  /**
@@ -189,11 +227,26 @@ var Reveal = (function(){
189
227
  if( !features.transforms2d && !features.transforms3d ) {
190
228
  document.body.setAttribute( 'class', 'no-transforms' );
191
229
 
230
+ // Since JS won't be running any further, we need to load all
231
+ // images that were intended to lazy load now
232
+ var images = document.getElementsByTagName( 'img' );
233
+ for( var i = 0, len = images.length; i < len; i++ ) {
234
+ var image = images[i];
235
+ if( image.getAttribute( 'data-src' ) ) {
236
+ image.setAttribute( 'src', image.getAttribute( 'data-src' ) );
237
+ image.removeAttribute( 'data-src' );
238
+ }
239
+ }
240
+
192
241
  // If the browser doesn't support core features we won't be
193
242
  // using JavaScript to control the presentation
194
243
  return;
195
244
  }
196
245
 
246
+ // Cache references to key DOM elements
247
+ dom.wrapper = document.querySelector( '.reveal' );
248
+ dom.slides = document.querySelector( '.reveal .slides' );
249
+
197
250
  // Force a layout when the whole page, incl fonts, has loaded
198
251
  window.addEventListener( 'load', layout, false );
199
252
 
@@ -238,10 +291,11 @@ var Reveal = (function(){
238
291
 
239
292
  features.canvas = !!document.createElement( 'canvas' ).getContext;
240
293
 
241
- isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
294
+ features.touch = !!( 'ontouchstart' in window );
242
295
 
243
- }
296
+ isMobileDevice = navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi );
244
297
 
298
+ }
245
299
 
246
300
  /**
247
301
  * Loads the dependencies of reveal.js. Dependencies are
@@ -316,6 +370,9 @@ var Reveal = (function(){
316
370
  // Make sure we've got all the DOM elements we need
317
371
  setupDOM();
318
372
 
373
+ // Listen to messages posted to this window
374
+ setupPostMessage();
375
+
319
376
  // Resets all vertical slides so that only the first is visible
320
377
  resetVerticalSlides();
321
378
 
@@ -343,6 +400,20 @@ var Reveal = (function(){
343
400
  } );
344
401
  }, 1 );
345
402
 
403
+ // Special setup and config is required when printing to PDF
404
+ if( isPrintingPDF() ) {
405
+ removeEventListeners();
406
+
407
+ // The document needs to have loaded for the PDF layout
408
+ // measurements to be accurate
409
+ if( document.readyState === 'complete' ) {
410
+ setupPDF();
411
+ }
412
+ else {
413
+ window.addEventListener( 'load', setupPDF );
414
+ }
415
+ }
416
+
346
417
  }
347
418
 
348
419
  /**
@@ -352,11 +423,6 @@ var Reveal = (function(){
352
423
  */
353
424
  function setupDOM() {
354
425
 
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
426
  // Prevent transitions while we're loading
361
427
  dom.slides.classList.add( 'no-transition' );
362
428
 
@@ -377,14 +443,14 @@ var Reveal = (function(){
377
443
  // Slide number
378
444
  dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
445
 
380
- // State background element [DEPRECATED]
381
- createSingletonNode( dom.wrapper, 'div', 'state-background', null );
382
-
383
446
  // Overlay graphic which is displayed during the paused mode
384
447
  createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
385
448
 
386
449
  // Cache references to elements
387
450
  dom.controls = document.querySelector( '.reveal .controls' );
451
+ dom.theme = document.querySelector( '#theme' );
452
+
453
+ dom.wrapper.setAttribute( 'role', 'application' );
388
454
 
389
455
  // There can be multiple instances of controls throughout the page
390
456
  dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
@@ -394,6 +460,100 @@ var Reveal = (function(){
394
460
  dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
461
  dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
462
 
463
+ dom.statusDiv = createStatusDiv();
464
+ }
465
+
466
+ /**
467
+ * Creates a hidden div with role aria-live to announce the
468
+ * current slide content. Hide the div off-screen to make it
469
+ * available only to Assistive Technologies.
470
+ */
471
+ function createStatusDiv() {
472
+
473
+ var statusDiv = document.getElementById( 'aria-status-div' );
474
+ if( !statusDiv ) {
475
+ statusDiv = document.createElement( 'div' );
476
+ statusDiv.style.position = 'absolute';
477
+ statusDiv.style.height = '1px';
478
+ statusDiv.style.width = '1px';
479
+ statusDiv.style.overflow ='hidden';
480
+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
481
+ statusDiv.setAttribute( 'id', 'aria-status-div' );
482
+ statusDiv.setAttribute( 'aria-live', 'polite' );
483
+ statusDiv.setAttribute( 'aria-atomic','true' );
484
+ dom.wrapper.appendChild( statusDiv );
485
+ }
486
+ return statusDiv;
487
+
488
+ }
489
+
490
+ /**
491
+ * Configures the presentation for printing to a static
492
+ * PDF.
493
+ */
494
+ function setupPDF() {
495
+
496
+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
497
+
498
+ // Dimensions of the PDF pages
499
+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
500
+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
501
+
502
+ // Dimensions of slides within the pages
503
+ var slideWidth = slideSize.width,
504
+ slideHeight = slideSize.height;
505
+
506
+ // Let the browser know what page size we want to print
507
+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
508
+
509
+ // Limit the size of certain elements to the dimensions of the slide
510
+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
511
+
512
+ document.body.classList.add( 'print-pdf' );
513
+ document.body.style.width = pageWidth + 'px';
514
+ document.body.style.height = pageHeight + 'px';
515
+
516
+ // Slide and slide background layout
517
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
518
+
519
+ // Vertical stacks are not centred since their section
520
+ // children will be
521
+ if( slide.classList.contains( 'stack' ) === false ) {
522
+ // Center the slide inside of the page, giving the slide some margin
523
+ var left = ( pageWidth - slideWidth ) / 2,
524
+ top = ( pageHeight - slideHeight ) / 2;
525
+
526
+ var contentHeight = getAbsoluteHeight( slide );
527
+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
528
+
529
+ // Center slides vertically
530
+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
531
+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
532
+ }
533
+
534
+ // Position the slide inside of the page
535
+ slide.style.left = left + 'px';
536
+ slide.style.top = top + 'px';
537
+ slide.style.width = slideWidth + 'px';
538
+
539
+ // TODO Backgrounds need to be multiplied when the slide
540
+ // stretches over multiple pages
541
+ var background = slide.querySelector( '.slide-background' );
542
+ if( background ) {
543
+ background.style.width = pageWidth + 'px';
544
+ background.style.height = ( pageHeight * numberOfPages ) + 'px';
545
+ background.style.top = -top + 'px';
546
+ background.style.left = -left + 'px';
547
+ }
548
+ }
549
+
550
+ } );
551
+
552
+ // Show all fragments
553
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
554
+ fragment.classList.add( 'visible' );
555
+ } );
556
+
397
557
  }
398
558
 
399
559
  /**
@@ -403,15 +563,26 @@ var Reveal = (function(){
403
563
  */
404
564
  function createSingletonNode( container, tagname, classname, innerHTML ) {
405
565
 
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;
566
+ // Find all nodes matching the description
567
+ var nodes = container.querySelectorAll( '.' + classname );
568
+
569
+ // Check all matches to find one which is a direct child of
570
+ // the specified container
571
+ for( var i = 0; i < nodes.length; i++ ) {
572
+ var testNode = nodes[i];
573
+ if( testNode.parentNode === container ) {
574
+ return testNode;
412
575
  }
413
- container.appendChild( node );
414
576
  }
577
+
578
+ // If no node was found, create it now
579
+ var node = document.createElement( tagname );
580
+ node.classList.add( classname );
581
+ if( typeof innerHTML === 'string' ) {
582
+ node.innerHTML = innerHTML;
583
+ }
584
+ container.appendChild( node );
585
+
415
586
  return node;
416
587
 
417
588
  }
@@ -423,81 +594,36 @@ var Reveal = (function(){
423
594
  */
424
595
  function createBackgrounds() {
425
596
 
426
- if( isPrintingPDF() ) {
427
- document.body.classList.add( 'print-pdf' );
428
- }
597
+ var printMode = isPrintingPDF();
429
598
 
430
599
  // Clear prior backgrounds
431
600
  dom.background.innerHTML = '';
432
601
  dom.background.classList.add( 'no-transition' );
433
602
 
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
603
  // Iterate over all horizontal slides
480
- toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
604
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
481
605
 
482
606
  var backgroundStack;
483
607
 
484
- if( isPrintingPDF() ) {
485
- backgroundStack = _createBackground( slideh, slideh );
608
+ if( printMode ) {
609
+ backgroundStack = createBackground( slideh, slideh );
486
610
  }
487
611
  else {
488
- backgroundStack = _createBackground( slideh, dom.background );
612
+ backgroundStack = createBackground( slideh, dom.background );
489
613
  }
490
614
 
491
615
  // Iterate over all vertical slides
492
616
  toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
493
617
 
494
- if( isPrintingPDF() ) {
495
- _createBackground( slidev, slidev );
618
+ if( printMode ) {
619
+ createBackground( slidev, slidev );
496
620
  }
497
621
  else {
498
- _createBackground( slidev, backgroundStack );
622
+ createBackground( slidev, backgroundStack );
499
623
  }
500
624
 
625
+ backgroundStack.classList.add( 'stack' );
626
+
501
627
  } );
502
628
 
503
629
  } );
@@ -526,13 +652,131 @@ var Reveal = (function(){
526
652
 
527
653
  }
528
654
 
655
+ /**
656
+ * Creates a background for the given slide.
657
+ *
658
+ * @param {HTMLElement} slide
659
+ * @param {HTMLElement} container The element that the background
660
+ * should be appended to
661
+ */
662
+ function createBackground( slide, container ) {
663
+
664
+ var data = {
665
+ background: slide.getAttribute( 'data-background' ),
666
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
667
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
668
+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
669
+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
670
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
671
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
672
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
673
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
674
+ };
675
+
676
+ var element = document.createElement( 'div' );
677
+
678
+ // Carry over custom classes from the slide to the background
679
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
680
+
681
+ if( data.background ) {
682
+ // Auto-wrap image urls in url(...)
683
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
684
+ slide.setAttribute( 'data-background-image', data.background );
685
+ }
686
+ else {
687
+ element.style.background = data.background;
688
+ }
689
+ }
690
+
691
+ // Create a hash for this combination of background settings.
692
+ // This is used to determine when two slide backgrounds are
693
+ // the same.
694
+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
695
+ element.setAttribute( 'data-background-hash', data.background +
696
+ data.backgroundSize +
697
+ data.backgroundImage +
698
+ data.backgroundVideo +
699
+ data.backgroundIframe +
700
+ data.backgroundColor +
701
+ data.backgroundRepeat +
702
+ data.backgroundPosition +
703
+ data.backgroundTransition );
704
+ }
705
+
706
+ // Additional and optional background properties
707
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
708
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
709
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
710
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
711
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
712
+
713
+ container.appendChild( element );
714
+
715
+ // If backgrounds are being recreated, clear old classes
716
+ slide.classList.remove( 'has-dark-background' );
717
+ slide.classList.remove( 'has-light-background' );
718
+
719
+ // If this slide has a background color, add a class that
720
+ // signals if it is light or dark. If the slide has no background
721
+ // color, no class will be set
722
+ var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
723
+ if( computedBackgroundColor ) {
724
+ var rgb = colorToRgb( computedBackgroundColor );
725
+
726
+ // Ignore fully transparent backgrounds. Some browsers return
727
+ // rgba(0,0,0,0) when reading the computed background color of
728
+ // an element with no background
729
+ if( rgb && rgb.a !== 0 ) {
730
+ if( colorBrightness( computedBackgroundColor ) < 128 ) {
731
+ slide.classList.add( 'has-dark-background' );
732
+ }
733
+ else {
734
+ slide.classList.add( 'has-light-background' );
735
+ }
736
+ }
737
+ }
738
+
739
+ return element;
740
+
741
+ }
742
+
743
+ /**
744
+ * Registers a listener to postMessage events, this makes it
745
+ * possible to call all reveal.js API methods from another
746
+ * window. For example:
747
+ *
748
+ * revealWindow.postMessage( JSON.stringify({
749
+ * method: 'slide',
750
+ * args: [ 2 ]
751
+ * }), '*' );
752
+ */
753
+ function setupPostMessage() {
754
+
755
+ if( config.postMessage ) {
756
+ window.addEventListener( 'message', function ( event ) {
757
+ var data = event.data;
758
+
759
+ // Make sure we're dealing with JSON
760
+ if( data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
761
+ data = JSON.parse( data );
762
+
763
+ // Check if the requested method can be found
764
+ if( data.method && typeof Reveal[data.method] === 'function' ) {
765
+ Reveal[data.method].apply( Reveal, data.args );
766
+ }
767
+ }
768
+ }, false );
769
+ }
770
+
771
+ }
772
+
529
773
  /**
530
774
  * Applies the configuration settings from the config
531
775
  * object. May be called multiple times.
532
776
  */
533
777
  function configure( options ) {
534
778
 
535
- var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
779
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
536
780
 
537
781
  dom.wrapper.classList.remove( config.transition );
538
782
 
@@ -565,6 +809,11 @@ var Reveal = (function(){
565
809
  dom.wrapper.classList.remove( 'center' );
566
810
  }
567
811
 
812
+ // Exit the paused mode if it was configured off
813
+ if( config.pause === false ) {
814
+ resume();
815
+ }
816
+
568
817
  if( config.mouseWheel ) {
569
818
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
570
819
  document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
@@ -591,7 +840,13 @@ var Reveal = (function(){
591
840
  enablePreviewLinks( '[data-preview-link]' );
592
841
  }
593
842
 
594
- // Auto-slide playback controls
843
+ // Remove existing auto-slide controls
844
+ if( autoSlidePlayer ) {
845
+ autoSlidePlayer.destroy();
846
+ autoSlidePlayer = null;
847
+ }
848
+
849
+ // Generate auto-slide controls if needed
595
850
  if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
851
  autoSlidePlayer = new Playback( dom.wrapper, function() {
597
852
  return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
@@ -600,21 +855,13 @@ var Reveal = (function(){
600
855
  autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
856
  autoSlidePaused = false;
602
857
  }
603
- else if( autoSlidePlayer ) {
604
- autoSlidePlayer.destroy();
605
- autoSlidePlayer = null;
606
- }
607
858
 
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
- }
859
+ // When fragments are turned off they should be visible
860
+ if( config.fragments === false ) {
861
+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
862
+ element.classList.add( 'visible' );
863
+ element.classList.remove( 'current-fragment' );
864
+ } );
618
865
  }
619
866
 
620
867
  sync();
@@ -637,7 +884,14 @@ var Reveal = (function(){
637
884
  dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
638
885
 
639
886
  // Support pointer-style touch interaction as well
640
- if( window.navigator.msPointerEnabled ) {
887
+ if( window.navigator.pointerEnabled ) {
888
+ // IE 11 uses un-prefixed version of pointer events
889
+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
890
+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
891
+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
892
+ }
893
+ else if( window.navigator.msPointerEnabled ) {
894
+ // IE 10 uses prefixed version of pointer events
641
895
  dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
642
896
  dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
643
897
  dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
@@ -646,13 +900,14 @@ var Reveal = (function(){
646
900
 
647
901
  if( config.keyboard ) {
648
902
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
903
+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
649
904
  }
650
905
 
651
906
  if( config.progress && dom.progress ) {
652
907
  dom.progress.addEventListener( 'click', onProgressClicked, false );
653
908
  }
654
909
 
655
- if( config.focusBodyOnPageVisiblityChange ) {
910
+ if( config.focusBodyOnPageVisibilityChange ) {
656
911
  var visibilityChange;
657
912
 
658
913
  if( 'hidden' in document ) {
@@ -670,7 +925,17 @@ var Reveal = (function(){
670
925
  }
671
926
  }
672
927
 
673
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
928
+ // Listen to both touch and click events, in case the device
929
+ // supports both
930
+ var pointerEvents = [ 'touchstart', 'click' ];
931
+
932
+ // Only support touch for Android, fixes double navigations in
933
+ // stock browser
934
+ if( navigator.userAgent.match( /android/gi ) ) {
935
+ pointerEvents = [ 'touchstart' ];
936
+ }
937
+
938
+ pointerEvents.forEach( function( eventName ) {
674
939
  dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
940
  dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
941
  dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
@@ -689,6 +954,7 @@ var Reveal = (function(){
689
954
  eventsAreBound = false;
690
955
 
691
956
  document.removeEventListener( 'keydown', onDocumentKeyDown, false );
957
+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
692
958
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
693
959
  window.removeEventListener( 'resize', onWindowResize, false );
694
960
 
@@ -696,7 +962,14 @@ var Reveal = (function(){
696
962
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
697
963
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
698
964
 
699
- if( window.navigator.msPointerEnabled ) {
965
+ // IE11
966
+ if( window.navigator.pointerEnabled ) {
967
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
968
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
969
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
970
+ }
971
+ // IE10
972
+ else if( window.navigator.msPointerEnabled ) {
700
973
  dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
701
974
  dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
702
975
  dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
@@ -738,6 +1011,22 @@ var Reveal = (function(){
738
1011
 
739
1012
  }
740
1013
 
1014
+ /**
1015
+ * Utility for deserializing a value.
1016
+ */
1017
+ function deserialize( value ) {
1018
+
1019
+ if( typeof value === 'string' ) {
1020
+ if( value === 'null' ) return null;
1021
+ else if( value === 'true' ) return true;
1022
+ else if( value === 'false' ) return false;
1023
+ else if( value.match( /^\d+$/ ) ) return parseFloat( value );
1024
+ }
1025
+
1026
+ return value;
1027
+
1028
+ }
1029
+
741
1030
  /**
742
1031
  * Measures the distance in pixels between point a
743
1032
  * and point b.
@@ -767,6 +1056,94 @@ var Reveal = (function(){
767
1056
 
768
1057
  }
769
1058
 
1059
+ /**
1060
+ * Injects the given CSS styles into the DOM.
1061
+ */
1062
+ function injectStyleSheet( value ) {
1063
+
1064
+ var tag = document.createElement( 'style' );
1065
+ tag.type = 'text/css';
1066
+ if( tag.styleSheet ) {
1067
+ tag.styleSheet.cssText = value;
1068
+ }
1069
+ else {
1070
+ tag.appendChild( document.createTextNode( value ) );
1071
+ }
1072
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
1073
+
1074
+ }
1075
+
1076
+ /**
1077
+ * Measures the distance in pixels between point a and point b.
1078
+ *
1079
+ * @param {String} color The string representation of a color,
1080
+ * the following formats are supported:
1081
+ * - #000
1082
+ * - #000000
1083
+ * - rgb(0,0,0)
1084
+ */
1085
+ function colorToRgb( color ) {
1086
+
1087
+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
1088
+ if( hex3 && hex3[1] ) {
1089
+ hex3 = hex3[1];
1090
+ return {
1091
+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
1092
+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
1093
+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
1094
+ };
1095
+ }
1096
+
1097
+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
1098
+ if( hex6 && hex6[1] ) {
1099
+ hex6 = hex6[1];
1100
+ return {
1101
+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
1102
+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
1103
+ b: parseInt( hex6.substr( 4, 2 ), 16 )
1104
+ };
1105
+ }
1106
+
1107
+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
1108
+ if( rgb ) {
1109
+ return {
1110
+ r: parseInt( rgb[1], 10 ),
1111
+ g: parseInt( rgb[2], 10 ),
1112
+ b: parseInt( rgb[3], 10 )
1113
+ };
1114
+ }
1115
+
1116
+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
1117
+ if( rgba ) {
1118
+ return {
1119
+ r: parseInt( rgba[1], 10 ),
1120
+ g: parseInt( rgba[2], 10 ),
1121
+ b: parseInt( rgba[3], 10 ),
1122
+ a: parseFloat( rgba[4] )
1123
+ };
1124
+ }
1125
+
1126
+ return null;
1127
+
1128
+ }
1129
+
1130
+ /**
1131
+ * Calculates brightness on a scale of 0-255.
1132
+ *
1133
+ * @param color See colorStringToRgb for supported formats.
1134
+ */
1135
+ function colorBrightness( color ) {
1136
+
1137
+ if( typeof color === 'string' ) color = colorToRgb( color );
1138
+
1139
+ if( color ) {
1140
+ return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
1141
+ }
1142
+
1143
+ return null;
1144
+
1145
+ }
1146
+
770
1147
  /**
771
1148
  * Retrieves the height of the given element by looking
772
1149
  * at the position and height of its immediate children.
@@ -782,7 +1159,7 @@ var Reveal = (function(){
782
1159
 
783
1160
  if( typeof child.offsetTop === 'number' && child.style ) {
784
1161
  // Count # of abs children
785
- if( child.style.position === 'absolute' ) {
1162
+ if( window.getComputedStyle( child ).position === 'absolute' ) {
786
1163
  absoluteChildren += 1;
787
1164
  }
788
1165
 
@@ -804,40 +1181,26 @@ var Reveal = (function(){
804
1181
 
805
1182
  /**
806
1183
  * Returns the remaining height within the parent of the
807
- * target element after subtracting the height of all
808
- * siblings.
1184
+ * target element.
809
1185
  *
810
- * remaining height = [parent height] - [ siblings height]
1186
+ * remaining height = [ configured parent height ] - [ current parent height ]
811
1187
  */
812
1188
  function getRemainingHeight( element, height ) {
813
1189
 
814
1190
  height = height || 0;
815
1191
 
816
1192
  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 ) {
824
-
825
- var styles = window.getComputedStyle( sibling ),
826
- marginTop = parseInt( styles.marginTop, 10 ),
827
- marginBottom = parseInt( styles.marginBottom, 10 );
828
-
829
- height -= sibling.offsetHeight + marginTop + marginBottom;
830
-
831
- }
832
-
833
- } );
1193
+ var newHeight, oldHeight = element.style.height;
834
1194
 
835
- var elementStyles = window.getComputedStyle( element );
1195
+ // Change the .stretch element height to 0 in order find the height of all
1196
+ // the other elements
1197
+ element.style.height = '0px';
1198
+ newHeight = height - element.parentNode.offsetHeight;
836
1199
 
837
- // Subtract the margins of the target element
838
- height -= parseInt( elementStyles.marginTop, 10 ) +
839
- parseInt( elementStyles.marginBottom, 10 );
1200
+ // Restore the old height, just in case
1201
+ element.style.height = oldHeight + 'px';
840
1202
 
1203
+ return newHeight;
841
1204
  }
842
1205
 
843
1206
  return height;
@@ -882,13 +1245,19 @@ var Reveal = (function(){
882
1245
  * Dispatches an event of the specified type from the
883
1246
  * reveal DOM element.
884
1247
  */
885
- function dispatchEvent( type, properties ) {
1248
+ function dispatchEvent( type, args ) {
886
1249
 
887
- var event = document.createEvent( "HTMLEvents", 1, 2 );
1250
+ var event = document.createEvent( 'HTMLEvents', 1, 2 );
888
1251
  event.initEvent( type, true, true );
889
- extend( event, properties );
1252
+ extend( event, args );
890
1253
  dom.wrapper.dispatchEvent( event );
891
1254
 
1255
+ // If we're in an iframe, post each reveal.js event to the
1256
+ // parent window. Used by the notes plugin
1257
+ if( config.postMessageEvents && window.parent !== window.self ) {
1258
+ window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
1259
+ }
1260
+
892
1261
  }
893
1262
 
894
1263
  /**
@@ -897,7 +1266,7 @@ var Reveal = (function(){
897
1266
  function enableRollingLinks() {
898
1267
 
899
1268
  if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
900
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
1269
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
901
1270
 
902
1271
  for( var i = 0, len = anchors.length; i < len; i++ ) {
903
1272
  var anchor = anchors[i];
@@ -921,7 +1290,7 @@ var Reveal = (function(){
921
1290
  */
922
1291
  function disableRollingLinks() {
923
1292
 
924
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
1293
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
925
1294
 
926
1295
  for( var i = 0, len = anchors.length; i < len; i++ ) {
927
1296
  var anchor = anchors[i];
@@ -968,15 +1337,16 @@ var Reveal = (function(){
968
1337
  /**
969
1338
  * Opens a preview window for the target URL.
970
1339
  */
971
- function openPreview( url ) {
1340
+ function showPreview( url ) {
972
1341
 
973
- closePreview();
1342
+ closeOverlay();
974
1343
 
975
- dom.preview = document.createElement( 'div' );
976
- dom.preview.classList.add( 'preview-link-overlay' );
977
- dom.wrapper.appendChild( dom.preview );
1344
+ dom.overlay = document.createElement( 'div' );
1345
+ dom.overlay.classList.add( 'overlay' );
1346
+ dom.overlay.classList.add( 'overlay-preview' );
1347
+ dom.wrapper.appendChild( dom.overlay );
978
1348
 
979
- dom.preview.innerHTML = [
1349
+ dom.overlay.innerHTML = [
980
1350
  '<header>',
981
1351
  '<a class="close" href="#"><span class="icon"></span></a>',
982
1352
  '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
@@ -987,34 +1357,78 @@ var Reveal = (function(){
987
1357
  '</div>'
988
1358
  ].join('');
989
1359
 
990
- dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
991
- dom.preview.classList.add( 'loaded' );
1360
+ dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
1361
+ dom.overlay.classList.add( 'loaded' );
992
1362
  }, false );
993
1363
 
994
- dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
995
- closePreview();
1364
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1365
+ closeOverlay();
996
1366
  event.preventDefault();
997
1367
  }, false );
998
1368
 
999
- dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1000
- closePreview();
1369
+ dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1370
+ closeOverlay();
1001
1371
  }, false );
1002
1372
 
1003
1373
  setTimeout( function() {
1004
- dom.preview.classList.add( 'visible' );
1374
+ dom.overlay.classList.add( 'visible' );
1005
1375
  }, 1 );
1006
1376
 
1007
1377
  }
1008
1378
 
1009
1379
  /**
1010
- * Closes the iframe preview window.
1380
+ * Opens a overlay window with help material.
1011
1381
  */
1012
- function closePreview() {
1382
+ function showHelp() {
1383
+
1384
+ if( config.help ) {
1385
+
1386
+ closeOverlay();
1387
+
1388
+ dom.overlay = document.createElement( 'div' );
1389
+ dom.overlay.classList.add( 'overlay' );
1390
+ dom.overlay.classList.add( 'overlay-help' );
1391
+ dom.wrapper.appendChild( dom.overlay );
1392
+
1393
+ var html = '<p class="title">Keyboard Shortcuts</p><br/>';
1394
+
1395
+ html += '<table><th>KEY</th><th>ACTION</th>';
1396
+ for( var key in keyboardShortcuts ) {
1397
+ html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
1398
+ }
1399
+
1400
+ html += '</table>';
1401
+
1402
+ dom.overlay.innerHTML = [
1403
+ '<header>',
1404
+ '<a class="close" href="#"><span class="icon"></span></a>',
1405
+ '</header>',
1406
+ '<div class="viewport">',
1407
+ '<div class="viewport-inner">'+ html +'</div>',
1408
+ '</div>'
1409
+ ].join('');
1410
+
1411
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1412
+ closeOverlay();
1413
+ event.preventDefault();
1414
+ }, false );
1415
+
1416
+ setTimeout( function() {
1417
+ dom.overlay.classList.add( 'visible' );
1418
+ }, 1 );
1013
1419
 
1014
- if( dom.preview ) {
1015
- dom.preview.setAttribute( 'src', '' );
1016
- dom.preview.parentNode.removeChild( dom.preview );
1017
- dom.preview = null;
1420
+ }
1421
+
1422
+ }
1423
+
1424
+ /**
1425
+ * Closes any currently open overlay.
1426
+ */
1427
+ function closeOverlay() {
1428
+
1429
+ if( dom.overlay ) {
1430
+ dom.overlay.parentNode.removeChild( dom.overlay );
1431
+ dom.overlay = null;
1018
1432
  }
1019
1433
 
1020
1434
  }
@@ -1027,54 +1441,49 @@ var Reveal = (function(){
1027
1441
 
1028
1442
  if( dom.wrapper && !isPrintingPDF() ) {
1029
1443
 
1030
- // Available space to scale within
1031
- var availableWidth = dom.wrapper.offsetWidth,
1032
- availableHeight = dom.wrapper.offsetHeight;
1033
-
1034
- // Reduce available space by margin
1035
- availableWidth -= ( availableHeight * config.margin );
1036
- availableHeight -= ( availableHeight * config.margin );
1444
+ var size = getComputedSlideSize();
1037
1445
 
1038
- // Dimensions of the content
1039
- var slideWidth = config.width,
1040
- slideHeight = config.height,
1041
- slidePadding = 20; // TODO Dig this out of DOM
1446
+ var slidePadding = 20; // TODO Dig this out of DOM
1042
1447
 
1043
1448
  // Layout the contents of the slides
1044
1449
  layoutSlideContents( config.width, config.height, slidePadding );
1045
1450
 
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';
1451
+ dom.slides.style.width = size.width + 'px';
1452
+ dom.slides.style.height = size.height + 'px';
1058
1453
 
1059
1454
  // Determine scale of content to fit within available space
1060
- scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1455
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1061
1456
 
1062
1457
  // Respect max/min scale settings
1063
1458
  scale = Math.max( scale, config.minScale );
1064
1459
  scale = Math.min( scale, config.maxScale );
1065
1460
 
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;
1461
+ // Don't apply any scaling styles if scale is 1
1462
+ if( scale === 1 ) {
1463
+ dom.slides.style.zoom = '';
1464
+ dom.slides.style.left = '';
1465
+ dom.slides.style.top = '';
1466
+ dom.slides.style.bottom = '';
1467
+ dom.slides.style.right = '';
1468
+ transformElement( dom.slides, '' );
1070
1469
  }
1071
- // Apply scale transform as a fallback
1072
1470
  else {
1073
- transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1471
+ // Prefer zooming in desktop Chrome so that content remains crisp
1472
+ if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
1473
+ dom.slides.style.zoom = scale;
1474
+ }
1475
+ // Apply scale transform as a fallback
1476
+ else {
1477
+ dom.slides.style.left = '50%';
1478
+ dom.slides.style.top = '50%';
1479
+ dom.slides.style.bottom = 'auto';
1480
+ dom.slides.style.right = 'auto';
1481
+ transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +')' );
1482
+ }
1074
1483
  }
1075
1484
 
1076
1485
  // Select all slides, vertical and horizontal
1077
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1486
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
1078
1487
 
1079
1488
  for( var i = 0, len = slides.length; i < len; i++ ) {
1080
1489
  var slide = slides[ i ];
@@ -1091,7 +1500,7 @@ var Reveal = (function(){
1091
1500
  slide.style.top = 0;
1092
1501
  }
1093
1502
  else {
1094
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1503
+ slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
1095
1504
  }
1096
1505
  }
1097
1506
  else {
@@ -1117,7 +1526,7 @@ var Reveal = (function(){
1117
1526
  toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
1527
 
1119
1528
  // Determine how much vertical space we can use
1120
- var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1529
+ var remainingHeight = getRemainingHeight( element, height );
1121
1530
 
1122
1531
  // Consider the aspect ratio of media elements
1123
1532
  if( /(img|video)/gi.test( element.nodeName ) ) {
@@ -1139,6 +1548,41 @@ var Reveal = (function(){
1139
1548
 
1140
1549
  }
1141
1550
 
1551
+ /**
1552
+ * Calculates the computed pixel size of our slides. These
1553
+ * values are based on the width and height configuration
1554
+ * options.
1555
+ */
1556
+ function getComputedSlideSize( presentationWidth, presentationHeight ) {
1557
+
1558
+ var size = {
1559
+ // Slide size
1560
+ width: config.width,
1561
+ height: config.height,
1562
+
1563
+ // Presentation size
1564
+ presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
1565
+ presentationHeight: presentationHeight || dom.wrapper.offsetHeight
1566
+ };
1567
+
1568
+ // Reduce available space by margin
1569
+ size.presentationWidth -= ( size.presentationHeight * config.margin );
1570
+ size.presentationHeight -= ( size.presentationHeight * config.margin );
1571
+
1572
+ // Slide width may be a percentage of available width
1573
+ if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
1574
+ size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
1575
+ }
1576
+
1577
+ // Slide height may be a percentage of available height
1578
+ if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
1579
+ size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
1580
+ }
1581
+
1582
+ return size;
1583
+
1584
+ }
1585
+
1142
1586
  /**
1143
1587
  * Stores the vertical index of a stack so that the same
1144
1588
  * vertical slide can be selected when navigating to and
@@ -1198,67 +1642,57 @@ var Reveal = (function(){
1198
1642
  dom.wrapper.classList.add( 'overview' );
1199
1643
  dom.wrapper.classList.remove( 'overview-deactivating' );
1200
1644
 
1201
- clearTimeout( activateOverviewTimeout );
1202
- clearTimeout( deactivateOverviewTimeout );
1203
-
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() {
1208
-
1209
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1645
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1210
1646
 
1211
- for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1212
- var hslide = horizontalSlides[i],
1213
- hoffset = config.rtl ? -105 : 105;
1647
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1648
+ var hslide = horizontalSlides[i],
1649
+ hoffset = config.rtl ? -105 : 105;
1214
1650
 
1215
- hslide.setAttribute( 'data-index-h', i );
1651
+ hslide.setAttribute( 'data-index-h', i );
1216
1652
 
1217
- // Apply CSS transform
1218
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1653
+ // Apply CSS transform
1654
+ transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1219
1655
 
1220
- if( hslide.classList.contains( 'stack' ) ) {
1656
+ if( hslide.classList.contains( 'stack' ) ) {
1221
1657
 
1222
- var verticalSlides = hslide.querySelectorAll( 'section' );
1658
+ var verticalSlides = hslide.querySelectorAll( 'section' );
1223
1659
 
1224
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1225
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1660
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1661
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1226
1662
 
1227
- var vslide = verticalSlides[j];
1663
+ var vslide = verticalSlides[j];
1228
1664
 
1229
- vslide.setAttribute( 'data-index-h', i );
1230
- vslide.setAttribute( 'data-index-v', j );
1665
+ vslide.setAttribute( 'data-index-h', i );
1666
+ vslide.setAttribute( 'data-index-v', j );
1231
1667
 
1232
- // Apply CSS transform
1233
- transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1234
-
1235
- // Navigate to this slide on click
1236
- vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1237
- }
1238
-
1239
- }
1240
- else {
1668
+ // Apply CSS transform
1669
+ transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1241
1670
 
1242
1671
  // Navigate to this slide on click
1243
- hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1244
-
1672
+ vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1245
1673
  }
1246
- }
1247
1674
 
1248
- updateSlidesVisibility();
1675
+ }
1676
+ else {
1249
1677
 
1250
- layout();
1678
+ // Navigate to this slide on click
1679
+ hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1251
1680
 
1252
- if( !wasActive ) {
1253
- // Notify observers of the overview showing
1254
- dispatchEvent( 'overviewshown', {
1255
- 'indexh': indexh,
1256
- 'indexv': indexv,
1257
- 'currentSlide': currentSlide
1258
- } );
1259
1681
  }
1682
+ }
1683
+
1684
+ updateSlidesVisibility();
1260
1685
 
1261
- }, 10 );
1686
+ layout();
1687
+
1688
+ if( !wasActive ) {
1689
+ // Notify observers of the overview showing
1690
+ dispatchEvent( 'overviewshown', {
1691
+ 'indexh': indexh,
1692
+ 'indexv': indexv,
1693
+ 'currentSlide': currentSlide
1694
+ } );
1695
+ }
1262
1696
 
1263
1697
  }
1264
1698
 
@@ -1273,9 +1707,6 @@ var Reveal = (function(){
1273
1707
  // Only proceed if enabled in config
1274
1708
  if( config.overview ) {
1275
1709
 
1276
- clearTimeout( activateOverviewTimeout );
1277
- clearTimeout( deactivateOverviewTimeout );
1278
-
1279
1710
  dom.wrapper.classList.remove( 'overview' );
1280
1711
 
1281
1712
  // Temporarily add a class so that transitions can do different things
@@ -1283,12 +1714,12 @@ var Reveal = (function(){
1283
1714
  // moving from slide to slide
1284
1715
  dom.wrapper.classList.add( 'overview-deactivating' );
1285
1716
 
1286
- deactivateOverviewTimeout = setTimeout( function () {
1717
+ setTimeout( function () {
1287
1718
  dom.wrapper.classList.remove( 'overview-deactivating' );
1288
1719
  }, 1 );
1289
1720
 
1290
1721
  // Select all slides
1291
- toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1722
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1292
1723
  // Resets all transforms to use the external styles
1293
1724
  transformElement( slide, '' );
1294
1725
 
@@ -1370,7 +1801,7 @@ var Reveal = (function(){
1370
1801
  element.webkitRequestFullscreen ||
1371
1802
  element.webkitRequestFullScreen ||
1372
1803
  element.mozRequestFullScreen ||
1373
- element.msRequestFullScreen;
1804
+ element.msRequestFullscreen;
1374
1805
 
1375
1806
  if( requestMethod ) {
1376
1807
  requestMethod.apply( element );
@@ -1384,13 +1815,15 @@ var Reveal = (function(){
1384
1815
  */
1385
1816
  function pause() {
1386
1817
 
1387
- var wasPaused = dom.wrapper.classList.contains( 'paused' );
1818
+ if( config.pause ) {
1819
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1388
1820
 
1389
- cancelAutoSlide();
1390
- dom.wrapper.classList.add( 'paused' );
1821
+ cancelAutoSlide();
1822
+ dom.wrapper.classList.add( 'paused' );
1391
1823
 
1392
- if( wasPaused === false ) {
1393
- dispatchEvent( 'paused' );
1824
+ if( wasPaused === false ) {
1825
+ dispatchEvent( 'paused' );
1826
+ }
1394
1827
  }
1395
1828
 
1396
1829
  }
@@ -1414,13 +1847,13 @@ var Reveal = (function(){
1414
1847
  /**
1415
1848
  * Toggles the paused mode on and off.
1416
1849
  */
1417
- function togglePause() {
1850
+ function togglePause( override ) {
1418
1851
 
1419
- if( isPaused() ) {
1420
- resume();
1852
+ if( typeof override === 'boolean' ) {
1853
+ override ? pause() : resume();
1421
1854
  }
1422
1855
  else {
1423
- pause();
1856
+ isPaused() ? resume() : pause();
1424
1857
  }
1425
1858
 
1426
1859
  }
@@ -1434,6 +1867,34 @@ var Reveal = (function(){
1434
1867
 
1435
1868
  }
1436
1869
 
1870
+ /**
1871
+ * Toggles the auto slide mode on and off.
1872
+ *
1873
+ * @param {Boolean} override Optional flag which sets the desired state.
1874
+ * True means autoplay starts, false means it stops.
1875
+ */
1876
+
1877
+ function toggleAutoSlide( override ) {
1878
+
1879
+ if( typeof override === 'boolean' ) {
1880
+ override ? resumeAutoSlide() : pauseAutoSlide();
1881
+ }
1882
+
1883
+ else {
1884
+ autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
1885
+ }
1886
+
1887
+ }
1888
+
1889
+ /**
1890
+ * Checks if the auto slide mode is currently on.
1891
+ */
1892
+ function isAutoSliding() {
1893
+
1894
+ return !!( autoSlide && !autoSlidePaused );
1895
+
1896
+ }
1897
+
1437
1898
  /**
1438
1899
  * Steps from the current point in the presentation to the
1439
1900
  * slide which matches the specified horizontal and vertical
@@ -1451,7 +1912,7 @@ var Reveal = (function(){
1451
1912
  previousSlide = currentSlide;
1452
1913
 
1453
1914
  // Query all horizontal slides in the deck
1454
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1915
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1455
1916
 
1456
1917
  // If no vertical index is specified and the upcoming slide is a
1457
1918
  // stack, resume at its previous vertical index
@@ -1544,13 +2005,14 @@ var Reveal = (function(){
1544
2005
  // stacks
1545
2006
  if( previousSlide ) {
1546
2007
  previousSlide.classList.remove( 'present' );
2008
+ previousSlide.setAttribute( 'aria-hidden', 'true' );
1547
2009
 
1548
2010
  // Reset all slides upon navigate to home
1549
2011
  // Issue: #285
1550
- if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
2012
+ if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1551
2013
  // Launch async task
1552
2014
  setTimeout( function () {
1553
- var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
2015
+ var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1554
2016
  for( i in slides ) {
1555
2017
  if( slides[i] ) {
1556
2018
  // Reset stack
@@ -1562,11 +2024,14 @@ var Reveal = (function(){
1562
2024
  }
1563
2025
 
1564
2026
  // Handle embedded content
1565
- if( slideChanged ) {
2027
+ if( slideChanged || !previousSlide ) {
1566
2028
  stopEmbeddedContent( previousSlide );
1567
2029
  startEmbeddedContent( currentSlide );
1568
2030
  }
1569
2031
 
2032
+ // Announce the current slide contents, for screen readers
2033
+ dom.statusDiv.textContent = currentSlide.textContent;
2034
+
1570
2035
  updateControls();
1571
2036
  updateProgress();
1572
2037
  updateBackground();
@@ -1603,12 +2068,18 @@ var Reveal = (function(){
1603
2068
  // Re-create the slide backgrounds
1604
2069
  createBackgrounds();
1605
2070
 
2071
+ // Write the current hash to the URL
2072
+ writeURL();
2073
+
1606
2074
  sortAllFragments();
1607
2075
 
1608
2076
  updateControls();
1609
2077
  updateProgress();
1610
2078
  updateBackground( true );
1611
2079
  updateSlideNumber();
2080
+ updateSlidesVisibility();
2081
+
2082
+ formatEmbeddedContent();
1612
2083
 
1613
2084
  }
1614
2085
 
@@ -1618,7 +2089,7 @@ var Reveal = (function(){
1618
2089
  */
1619
2090
  function resetVerticalSlides() {
1620
2091
 
1621
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2092
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
2093
  horizontalSlides.forEach( function( horizontalSlide ) {
1623
2094
 
1624
2095
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1628,6 +2099,7 @@ var Reveal = (function(){
1628
2099
  verticalSlide.classList.remove( 'present' );
1629
2100
  verticalSlide.classList.remove( 'past' );
1630
2101
  verticalSlide.classList.add( 'future' );
2102
+ verticalSlide.setAttribute( 'aria-hidden', 'true' );
1631
2103
  }
1632
2104
 
1633
2105
  } );
@@ -1642,7 +2114,7 @@ var Reveal = (function(){
1642
2114
  */
1643
2115
  function sortAllFragments() {
1644
2116
 
1645
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2117
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
2118
  horizontalSlides.forEach( function( horizontalSlide ) {
1647
2119
 
1648
2120
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1675,9 +2147,11 @@ var Reveal = (function(){
1675
2147
 
1676
2148
  // Select all slides and convert the NodeList result to
1677
2149
  // an array
1678
- var slides = toArray( document.querySelectorAll( selector ) ),
2150
+ var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
1679
2151
  slidesLength = slides.length;
1680
2152
 
2153
+ var printMode = isPrintingPDF();
2154
+
1681
2155
  if( slidesLength ) {
1682
2156
 
1683
2157
  // Should the index loop?
@@ -1703,43 +2177,55 @@ var Reveal = (function(){
1703
2177
 
1704
2178
  // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1705
2179
  element.setAttribute( 'hidden', '' );
2180
+ element.setAttribute( 'aria-hidden', 'true' );
2181
+
2182
+ // If this element contains vertical slides
2183
+ if( element.querySelector( 'section' ) ) {
2184
+ element.classList.add( 'stack' );
2185
+ }
2186
+
2187
+ // If we're printing static slides, all slides are "present"
2188
+ if( printMode ) {
2189
+ element.classList.add( 'present' );
2190
+ continue;
2191
+ }
1706
2192
 
1707
2193
  if( i < index ) {
1708
2194
  // Any element previous to index is given the 'past' class
1709
2195
  element.classList.add( reverse ? 'future' : 'past' );
1710
2196
 
1711
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
2197
+ if( config.fragments ) {
2198
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
2199
 
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' );
2200
+ // Show all fragments on prior slides
2201
+ while( pastFragments.length ) {
2202
+ var pastFragment = pastFragments.pop();
2203
+ pastFragment.classList.add( 'visible' );
2204
+ pastFragment.classList.remove( 'current-fragment' );
2205
+ }
1718
2206
  }
1719
2207
  }
1720
2208
  else if( i > index ) {
1721
2209
  // Any element subsequent to index is given the 'future' class
1722
2210
  element.classList.add( reverse ? 'past' : 'future' );
1723
2211
 
1724
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
2212
+ if( config.fragments ) {
2213
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
2214
 
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' );
2215
+ // No fragments in future slides should be visible ahead of time
2216
+ while( futureFragments.length ) {
2217
+ var futureFragment = futureFragments.pop();
2218
+ futureFragment.classList.remove( 'visible' );
2219
+ futureFragment.classList.remove( 'current-fragment' );
2220
+ }
1731
2221
  }
1732
2222
  }
1733
-
1734
- // If this element contains vertical slides
1735
- if( element.querySelector( 'section' ) ) {
1736
- element.classList.add( 'stack' );
1737
- }
1738
2223
  }
1739
2224
 
1740
2225
  // Mark the current slide as present
1741
2226
  slides[index].classList.add( 'present' );
1742
2227
  slides[index].removeAttribute( 'hidden' );
2228
+ slides[index].removeAttribute( 'aria-hidden' );
1743
2229
 
1744
2230
  // If this slide has a state associated with it, add it
1745
2231
  // onto the current state of the deck
@@ -1767,12 +2253,12 @@ var Reveal = (function(){
1767
2253
 
1768
2254
  // Select all slides and convert the NodeList result to
1769
2255
  // an array
1770
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
2256
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
2257
  horizontalSlidesLength = horizontalSlides.length,
1772
2258
  distanceX,
1773
2259
  distanceY;
1774
2260
 
1775
- if( horizontalSlidesLength ) {
2261
+ if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
1776
2262
 
1777
2263
  // The number of steps away from the present slide that will
1778
2264
  // be visible
@@ -1780,7 +2266,12 @@ var Reveal = (function(){
1780
2266
 
1781
2267
  // Limit view distance on weaker devices
1782
2268
  if( isMobileDevice ) {
1783
- viewDistance = isOverview() ? 6 : 1;
2269
+ viewDistance = isOverview() ? 6 : 2;
2270
+ }
2271
+
2272
+ // Limit view distance on weaker devices
2273
+ if( isPrintingPDF() ) {
2274
+ viewDistance = Number.MAX_VALUE;
1784
2275
  }
1785
2276
 
1786
2277
  for( var x = 0; x < horizontalSlidesLength; x++ ) {
@@ -1790,10 +2281,15 @@ var Reveal = (function(){
1790
2281
  verticalSlidesLength = verticalSlides.length;
1791
2282
 
1792
2283
  // Loops so that it measures 1 between the first and last slides
1793
- distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2284
+ distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
1794
2285
 
1795
2286
  // Show the horizontal slide if it's within the view distance
1796
- horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
2287
+ if( distanceX < viewDistance ) {
2288
+ showSlide( horizontalSlide );
2289
+ }
2290
+ else {
2291
+ hideSlide( horizontalSlide );
2292
+ }
1797
2293
 
1798
2294
  if( verticalSlidesLength ) {
1799
2295
 
@@ -1802,9 +2298,14 @@ var Reveal = (function(){
1802
2298
  for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
2299
  var verticalSlide = verticalSlides[y];
1804
2300
 
1805
- distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
2301
+ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
1806
2302
 
1807
- verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
2303
+ if( distanceX + distanceY < viewDistance ) {
2304
+ showSlide( verticalSlide );
2305
+ }
2306
+ else {
2307
+ hideSlide( verticalSlide );
2308
+ }
1808
2309
  }
1809
2310
 
1810
2311
  }
@@ -1820,44 +2321,9 @@ var Reveal = (function(){
1820
2321
  function updateProgress() {
1821
2322
 
1822
2323
  // 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' ) );
2324
+ if( config.progress && dom.progressbar ) {
1836
2325
 
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
- }
1843
-
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';
2326
+ dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
1861
2327
 
1862
2328
  }
1863
2329
 
@@ -1951,30 +2417,38 @@ var Reveal = (function(){
1951
2417
  // states of their slides (past/present/future)
1952
2418
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1953
2419
 
2420
+ backgroundh.classList.remove( 'past' );
2421
+ backgroundh.classList.remove( 'present' );
2422
+ backgroundh.classList.remove( 'future' );
2423
+
1954
2424
  if( h < indexh ) {
1955
- backgroundh.className = 'slide-background ' + horizontalPast;
2425
+ backgroundh.classList.add( horizontalPast );
1956
2426
  }
1957
2427
  else if ( h > indexh ) {
1958
- backgroundh.className = 'slide-background ' + horizontalFuture;
2428
+ backgroundh.classList.add( horizontalFuture );
1959
2429
  }
1960
2430
  else {
1961
- backgroundh.className = 'slide-background present';
2431
+ backgroundh.classList.add( 'present' );
1962
2432
 
1963
2433
  // Store a reference to the current background element
1964
2434
  currentBackground = backgroundh;
1965
2435
  }
1966
2436
 
1967
2437
  if( includeAll || h === indexh ) {
1968
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
2438
+ toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
2439
+
2440
+ backgroundv.classList.remove( 'past' );
2441
+ backgroundv.classList.remove( 'present' );
2442
+ backgroundv.classList.remove( 'future' );
1969
2443
 
1970
2444
  if( v < indexv ) {
1971
- backgroundv.className = 'slide-background past';
2445
+ backgroundv.classList.add( 'past' );
1972
2446
  }
1973
2447
  else if ( v > indexv ) {
1974
- backgroundv.className = 'slide-background future';
2448
+ backgroundv.classList.add( 'future' );
1975
2449
  }
1976
2450
  else {
1977
- backgroundv.className = 'slide-background present';
2451
+ backgroundv.classList.add( 'present' );
1978
2452
 
1979
2453
  // Only if this is the present horizontal and vertical slide
1980
2454
  if( h === indexh ) currentBackground = backgroundv;
@@ -1985,9 +2459,25 @@ var Reveal = (function(){
1985
2459
 
1986
2460
  } );
1987
2461
 
1988
- // Don't transition between identical backgrounds. This
1989
- // prevents unwanted flicker.
2462
+ // Stop any currently playing video background
2463
+ if( previousBackground ) {
2464
+
2465
+ var previousVideo = previousBackground.querySelector( 'video' );
2466
+ if( previousVideo ) previousVideo.pause();
2467
+
2468
+ }
2469
+
1990
2470
  if( currentBackground ) {
2471
+
2472
+ // Start video playback
2473
+ var currentVideo = currentBackground.querySelector( 'video' );
2474
+ if( currentVideo ) {
2475
+ currentVideo.currentTime = 0;
2476
+ currentVideo.play();
2477
+ }
2478
+
2479
+ // Don't transition between identical backgrounds. This
2480
+ // prevents unwanted flicker.
1991
2481
  var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
2482
  var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
2483
  if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
@@ -1995,6 +2485,20 @@ var Reveal = (function(){
1995
2485
  }
1996
2486
 
1997
2487
  previousBackground = currentBackground;
2488
+
2489
+ }
2490
+
2491
+ // If there's a background brightness flag for this slide,
2492
+ // bubble it to the .reveal container
2493
+ if( currentSlide ) {
2494
+ [ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
2495
+ if( currentSlide.classList.contains( classToBubble ) ) {
2496
+ dom.wrapper.classList.add( classToBubble );
2497
+ }
2498
+ else {
2499
+ dom.wrapper.classList.remove( classToBubble );
2500
+ }
2501
+ } );
1998
2502
  }
1999
2503
 
2000
2504
  // Allow the first background to apply without transition
@@ -2012,8 +2516,8 @@ var Reveal = (function(){
2012
2516
 
2013
2517
  if( config.parallaxBackgroundImage ) {
2014
2518
 
2015
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2519
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2520
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
2521
 
2018
2522
  var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
2523
  backgroundWidth, backgroundHeight;
@@ -2032,7 +2536,7 @@ var Reveal = (function(){
2032
2536
 
2033
2537
  var slideHeight = dom.background.offsetHeight;
2034
2538
  var verticalSlideCount = verticalSlides.length;
2035
- var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2539
+ var verticalOffset = verticalSlideCount > 1 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2036
2540
 
2037
2541
  dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2038
2542
 
@@ -2040,6 +2544,103 @@ var Reveal = (function(){
2040
2544
 
2041
2545
  }
2042
2546
 
2547
+ /**
2548
+ * Called when the given slide is within the configured view
2549
+ * distance. Shows the slide element and loads any content
2550
+ * that is set to load lazily (data-src).
2551
+ */
2552
+ function showSlide( slide ) {
2553
+
2554
+ // Show the slide element
2555
+ slide.style.display = 'block';
2556
+
2557
+ // Media elements with data-src attributes
2558
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src], iframe[data-src]' ) ).forEach( function( element ) {
2559
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
2560
+ element.removeAttribute( 'data-src' );
2561
+ } );
2562
+
2563
+ // Media elements with <source> children
2564
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
2565
+ var sources = 0;
2566
+
2567
+ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
2568
+ source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
2569
+ source.removeAttribute( 'data-src' );
2570
+ sources += 1;
2571
+ } );
2572
+
2573
+ // If we rewrote sources for this video/audio element, we need
2574
+ // to manually tell it to load from its new origin
2575
+ if( sources > 0 ) {
2576
+ media.load();
2577
+ }
2578
+ } );
2579
+
2580
+
2581
+ // Show the corresponding background element
2582
+ var indices = getIndices( slide );
2583
+ var background = getSlideBackground( indices.h, indices.v );
2584
+ if( background ) {
2585
+ background.style.display = 'block';
2586
+
2587
+ // If the background contains media, load it
2588
+ if( background.hasAttribute( 'data-loaded' ) === false ) {
2589
+ background.setAttribute( 'data-loaded', 'true' );
2590
+
2591
+ var backgroundImage = slide.getAttribute( 'data-background-image' ),
2592
+ backgroundVideo = slide.getAttribute( 'data-background-video' ),
2593
+ backgroundIframe = slide.getAttribute( 'data-background-iframe' );
2594
+
2595
+ // Images
2596
+ if( backgroundImage ) {
2597
+ background.style.backgroundImage = 'url('+ backgroundImage +')';
2598
+ }
2599
+ // Videos
2600
+ else if ( backgroundVideo && !isSpeakerNotes() ) {
2601
+ var video = document.createElement( 'video' );
2602
+
2603
+ // Support comma separated lists of video sources
2604
+ backgroundVideo.split( ',' ).forEach( function( source ) {
2605
+ video.innerHTML += '<source src="'+ source +'">';
2606
+ } );
2607
+
2608
+ background.appendChild( video );
2609
+ }
2610
+ // Iframes
2611
+ else if ( backgroundIframe ) {
2612
+ var iframe = document.createElement( 'iframe' );
2613
+ iframe.setAttribute( 'src', backgroundIframe );
2614
+ iframe.style.width = '100%';
2615
+ iframe.style.height = '100%';
2616
+ iframe.style.maxHeight = '100%';
2617
+ iframe.style.maxWidth = '100%';
2618
+
2619
+ background.appendChild( iframe );
2620
+ }
2621
+ }
2622
+ }
2623
+
2624
+ }
2625
+
2626
+ /**
2627
+ * Called when the given slide is moved outside of the
2628
+ * configured view distance.
2629
+ */
2630
+ function hideSlide( slide ) {
2631
+
2632
+ // Hide the slide element
2633
+ slide.style.display = 'none';
2634
+
2635
+ // Hide the corresponding background element
2636
+ var indices = getIndices( slide );
2637
+ var background = getSlideBackground( indices.h, indices.v );
2638
+ if( background ) {
2639
+ background.style.display = 'none';
2640
+ }
2641
+
2642
+ }
2643
+
2043
2644
  /**
2044
2645
  * Determine what available routes there are for navigation.
2045
2646
  *
@@ -2047,8 +2648,8 @@ var Reveal = (function(){
2047
2648
  */
2048
2649
  function availableRoutes() {
2049
2650
 
2050
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2051
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2651
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2652
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2052
2653
 
2053
2654
  var routes = {
2054
2655
  left: indexh > 0 || config.loop,
@@ -2091,6 +2692,29 @@ var Reveal = (function(){
2091
2692
 
2092
2693
  }
2093
2694
 
2695
+ /**
2696
+ * Enforces origin-specific format rules for embedded media.
2697
+ */
2698
+ function formatEmbeddedContent() {
2699
+
2700
+ // YouTube frames must include "?enablejsapi=1"
2701
+ toArray( dom.slides.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2702
+ var src = el.getAttribute( 'src' );
2703
+ if( !/enablejsapi\=1/gi.test( src ) ) {
2704
+ el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'enablejsapi=1' );
2705
+ }
2706
+ });
2707
+
2708
+ // Vimeo frames must include "?api=1"
2709
+ toArray( dom.slides.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2710
+ var src = el.getAttribute( 'src' );
2711
+ if( !/api\=1/gi.test( src ) ) {
2712
+ el.setAttribute( 'src', src + ( !/\?/.test( src ) ? '?' : '&' ) + 'api=1' );
2713
+ }
2714
+ });
2715
+
2716
+ }
2717
+
2094
2718
  /**
2095
2719
  * Start playback of any embedded content inside of
2096
2720
  * the targeted slide.
@@ -2116,6 +2740,13 @@ var Reveal = (function(){
2116
2740
  el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2117
2741
  }
2118
2742
  });
2743
+
2744
+ // Vimeo embeds
2745
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2746
+ if( el.hasAttribute( 'data-autoplay' ) ) {
2747
+ el.contentWindow.postMessage( '{"method":"play"}', '*' );
2748
+ }
2749
+ });
2119
2750
  }
2120
2751
 
2121
2752
  }
@@ -2126,7 +2757,7 @@ var Reveal = (function(){
2126
2757
  */
2127
2758
  function stopEmbeddedContent( slide ) {
2128
2759
 
2129
- if( slide ) {
2760
+ if( slide && slide.parentNode ) {
2130
2761
  // HTML5 media elements
2131
2762
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2132
2763
  if( !el.hasAttribute( 'data-ignore' ) ) {
@@ -2145,8 +2776,79 @@ var Reveal = (function(){
2145
2776
  el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
2146
2777
  }
2147
2778
  });
2779
+
2780
+ // Vimeo embeds
2781
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2782
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2783
+ el.contentWindow.postMessage( '{"method":"pause"}', '*' );
2784
+ }
2785
+ });
2786
+ }
2787
+
2788
+ }
2789
+
2790
+ /**
2791
+ * Returns a value ranging from 0-1 that represents
2792
+ * how far into the presentation we have navigated.
2793
+ */
2794
+ function getProgress() {
2795
+
2796
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2797
+
2798
+ // The number of past and total slides
2799
+ var totalCount = getTotalSlides();
2800
+ var pastCount = 0;
2801
+
2802
+ // Step through all slides and count the past ones
2803
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
2804
+
2805
+ var horizontalSlide = horizontalSlides[i];
2806
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
2807
+
2808
+ for( var j = 0; j < verticalSlides.length; j++ ) {
2809
+
2810
+ // Stop as soon as we arrive at the present
2811
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
2812
+ break mainLoop;
2813
+ }
2814
+
2815
+ pastCount++;
2816
+
2817
+ }
2818
+
2819
+ // Stop as soon as we arrive at the present
2820
+ if( horizontalSlide.classList.contains( 'present' ) ) {
2821
+ break;
2822
+ }
2823
+
2824
+ // Don't count the wrapping section for vertical slides
2825
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
2826
+ pastCount++;
2827
+ }
2828
+
2829
+ }
2830
+
2831
+ if( currentSlide ) {
2832
+
2833
+ var allFragments = currentSlide.querySelectorAll( '.fragment' );
2834
+
2835
+ // If there are fragments in the current slide those should be
2836
+ // accounted for in the progress.
2837
+ if( allFragments.length > 0 ) {
2838
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2839
+
2840
+ // This value represents how big a portion of the slide progress
2841
+ // that is made up by its fragments (0-1)
2842
+ var fragmentWeight = 0.9;
2843
+
2844
+ // Add fragment progress to the past slide count
2845
+ pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
2846
+ }
2847
+
2148
2848
  }
2149
2849
 
2850
+ return pastCount / ( totalCount - 1 );
2851
+
2150
2852
  }
2151
2853
 
2152
2854
  /**
@@ -2173,8 +2875,13 @@ var Reveal = (function(){
2173
2875
  // If the first bit is invalid and there is a name we can
2174
2876
  // assume that this is a named link
2175
2877
  if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
2176
- // Find the slide with the specified name
2177
- var element = document.querySelector( '#' + name );
2878
+ var element;
2879
+
2880
+ // Ensure the named link is a valid HTML ID attribute
2881
+ if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
2882
+ // Find the slide with the specified ID
2883
+ element = document.querySelector( '#' + name );
2884
+ }
2178
2885
 
2179
2886
  if( element ) {
2180
2887
  // Find the position of the named slide and navigate to it
@@ -2216,12 +2923,19 @@ var Reveal = (function(){
2216
2923
  if( typeof delay === 'number' ) {
2217
2924
  writeURLTimeout = setTimeout( writeURL, delay );
2218
2925
  }
2219
- else {
2926
+ else if( currentSlide ) {
2220
2927
  var url = '/';
2221
2928
 
2929
+ // Attempt to create a named link based on the slide's ID
2930
+ var id = currentSlide.getAttribute( 'id' );
2931
+ if( id ) {
2932
+ id = id.toLowerCase();
2933
+ id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
2934
+ }
2935
+
2222
2936
  // 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' );
2937
+ if( typeof id === 'string' && id.length ) {
2938
+ url = '/' + id;
2225
2939
  }
2226
2940
  // Otherwise use the /h/v index
2227
2941
  else {
@@ -2258,11 +2972,14 @@ var Reveal = (function(){
2258
2972
  var slideh = isVertical ? slide.parentNode : slide;
2259
2973
 
2260
2974
  // Select all horizontal slides
2261
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2975
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2262
2976
 
2263
2977
  // Now that we know which the horizontal slide is, get its index
2264
2978
  h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2265
2979
 
2980
+ // Assume we're not vertical
2981
+ v = undefined;
2982
+
2266
2983
  // If this is a vertical slide, grab the vertical index
2267
2984
  if( isVertical ) {
2268
2985
  v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
@@ -2272,8 +2989,13 @@ var Reveal = (function(){
2272
2989
  if( !slide && currentSlide ) {
2273
2990
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
2991
  if( hasFragments ) {
2275
- var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
- f = visibleFragments.length - 1;
2992
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
2993
+ if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
2994
+ f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
2995
+ }
2996
+ else {
2997
+ f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
2998
+ }
2277
2999
  }
2278
3000
  }
2279
3001
 
@@ -2281,6 +3003,107 @@ var Reveal = (function(){
2281
3003
 
2282
3004
  }
2283
3005
 
3006
+ /**
3007
+ * Retrieves the total number of slides in this presentation.
3008
+ */
3009
+ function getTotalSlides() {
3010
+
3011
+ return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
3012
+
3013
+ }
3014
+
3015
+ /**
3016
+ * Returns the slide element matching the specified index.
3017
+ */
3018
+ function getSlide( x, y ) {
3019
+
3020
+ var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3021
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
3022
+
3023
+ if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
3024
+ return verticalSlides ? verticalSlides[ y ] : undefined;
3025
+ }
3026
+
3027
+ return horizontalSlide;
3028
+
3029
+ }
3030
+
3031
+ /**
3032
+ * Returns the background element for the given slide.
3033
+ * All slides, even the ones with no background properties
3034
+ * defined, have a background element so as long as the
3035
+ * index is valid an element will be returned.
3036
+ */
3037
+ function getSlideBackground( x, y ) {
3038
+
3039
+ // When printing to PDF the slide backgrounds are nested
3040
+ // inside of the slides
3041
+ if( isPrintingPDF() ) {
3042
+ var slide = getSlide( x, y );
3043
+ if( slide ) {
3044
+ var background = slide.querySelector( '.slide-background' );
3045
+ if( background && background.parentNode === slide ) {
3046
+ return background;
3047
+ }
3048
+ }
3049
+
3050
+ return undefined;
3051
+ }
3052
+
3053
+ var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
3054
+ var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
3055
+
3056
+ if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
3057
+ return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
3058
+ }
3059
+
3060
+ return horizontalBackground;
3061
+
3062
+ }
3063
+
3064
+ /**
3065
+ * Retrieves the current state of the presentation as
3066
+ * an object. This state can then be restored at any
3067
+ * time.
3068
+ */
3069
+ function getState() {
3070
+
3071
+ var indices = getIndices();
3072
+
3073
+ return {
3074
+ indexh: indices.h,
3075
+ indexv: indices.v,
3076
+ indexf: indices.f,
3077
+ paused: isPaused(),
3078
+ overview: isOverview()
3079
+ };
3080
+
3081
+ }
3082
+
3083
+ /**
3084
+ * Restores the presentation to the given state.
3085
+ *
3086
+ * @param {Object} state As generated by getState()
3087
+ */
3088
+ function setState( state ) {
3089
+
3090
+ if( typeof state === 'object' ) {
3091
+ slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
3092
+
3093
+ var pausedFlag = deserialize( state.paused ),
3094
+ overviewFlag = deserialize( state.overview );
3095
+
3096
+ if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
3097
+ togglePause( pausedFlag );
3098
+ }
3099
+
3100
+ if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
3101
+ toggleOverview( overviewFlag );
3102
+ }
3103
+ }
3104
+
3105
+ }
3106
+
2284
3107
  /**
2285
3108
  * Return a sorted fragments list, ordered by an increasing
2286
3109
  * "data-fragment-index" attribute.
@@ -2392,6 +3215,9 @@ var Reveal = (function(){
2392
3215
  element.classList.add( 'visible' );
2393
3216
  element.classList.remove( 'current-fragment' );
2394
3217
 
3218
+ // Announce the fragments one by one to the Screen Reader
3219
+ dom.statusDiv.textContent = element.textContent;
3220
+
2395
3221
  if( i === index ) {
2396
3222
  element.classList.add( 'current-fragment' );
2397
3223
  }
@@ -2415,6 +3241,7 @@ var Reveal = (function(){
2415
3241
  }
2416
3242
 
2417
3243
  updateControls();
3244
+ updateProgress();
2418
3245
 
2419
3246
  return !!( fragmentsShown.length || fragmentsHidden.length );
2420
3247
 
@@ -2459,14 +3286,21 @@ var Reveal = (function(){
2459
3286
 
2460
3287
  if( currentSlide ) {
2461
3288
 
3289
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3290
+
3291
+ var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
2462
3292
  var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
3293
  var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
3294
 
2465
3295
  // 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 ) {
3296
+ // 1. Current fragment's data-autoslide
3297
+ // 2. Current slide's data-autoslide
3298
+ // 3. Parent slide's data-autoslide
3299
+ // 4. Global autoSlide setting
3300
+ if( fragmentAutoSlide ) {
3301
+ autoSlide = parseInt( fragmentAutoSlide, 10 );
3302
+ }
3303
+ else if( slideAutoSlide ) {
2470
3304
  autoSlide = parseInt( slideAutoSlide, 10 );
2471
3305
  }
2472
3306
  else if( parentAutoSlide ) {
@@ -2493,7 +3327,7 @@ var Reveal = (function(){
2493
3327
  // - The presentation isn't paused
2494
3328
  // - The overview isn't active
2495
3329
  // - The presentation isn't over
2496
- if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
3330
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
2497
3331
  autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
3332
  autoSlideStartTime = Date.now();
2499
3333
  }
@@ -2518,19 +3352,25 @@ var Reveal = (function(){
2518
3352
 
2519
3353
  function pauseAutoSlide() {
2520
3354
 
2521
- autoSlidePaused = true;
2522
- clearTimeout( autoSlideTimeout );
3355
+ if( autoSlide && !autoSlidePaused ) {
3356
+ autoSlidePaused = true;
3357
+ dispatchEvent( 'autoslidepaused' );
3358
+ clearTimeout( autoSlideTimeout );
2523
3359
 
2524
- if( autoSlidePlayer ) {
2525
- autoSlidePlayer.setPlaying( false );
3360
+ if( autoSlidePlayer ) {
3361
+ autoSlidePlayer.setPlaying( false );
3362
+ }
2526
3363
  }
2527
3364
 
2528
3365
  }
2529
3366
 
2530
3367
  function resumeAutoSlide() {
2531
3368
 
2532
- autoSlidePaused = false;
2533
- cueAutoSlide();
3369
+ if( autoSlide && autoSlidePaused ) {
3370
+ autoSlidePaused = false;
3371
+ dispatchEvent( 'autoslideresumed' );
3372
+ cueAutoSlide();
3373
+ }
2534
3374
 
2535
3375
  }
2536
3376
 
@@ -2597,7 +3437,14 @@ var Reveal = (function(){
2597
3437
  }
2598
3438
  else {
2599
3439
  // Fetch the previous horizontal slide, if there is one
2600
- var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
3440
+ var previousSlide;
3441
+
3442
+ if( config.rtl ) {
3443
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
3444
+ }
3445
+ else {
3446
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
3447
+ }
2601
3448
 
2602
3449
  if( previousSlide ) {
2603
3450
  var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
@@ -2610,13 +3457,21 @@ var Reveal = (function(){
2610
3457
  }
2611
3458
 
2612
3459
  /**
2613
- * Same as #navigatePrev() but navigates forwards.
3460
+ * The reverse of #navigatePrev().
2614
3461
  */
2615
3462
  function navigateNext() {
2616
3463
 
2617
3464
  // Prioritize revealing fragments
2618
3465
  if( nextFragment() === false ) {
2619
- availableRoutes().down ? navigateDown() : navigateRight();
3466
+ if( availableRoutes().down ) {
3467
+ navigateDown();
3468
+ }
3469
+ else if( config.rtl ) {
3470
+ navigateLeft();
3471
+ }
3472
+ else {
3473
+ navigateRight();
3474
+ }
2620
3475
  }
2621
3476
 
2622
3477
  // If auto-sliding is enabled we need to cue up
@@ -2642,21 +3497,47 @@ var Reveal = (function(){
2642
3497
 
2643
3498
  }
2644
3499
 
3500
+ /**
3501
+ * Handler for the document level 'keypress' event.
3502
+ */
3503
+ function onDocumentKeyPress( event ) {
3504
+
3505
+ // Check if the pressed key is question mark
3506
+ if( event.shiftKey && event.charCode === 63 ) {
3507
+ if( dom.overlay ) {
3508
+ closeOverlay();
3509
+ }
3510
+ else {
3511
+ showHelp( true );
3512
+ }
3513
+ }
3514
+
3515
+ }
3516
+
2645
3517
  /**
2646
3518
  * Handler for the document level 'keydown' event.
2647
3519
  */
2648
3520
  function onDocumentKeyDown( event ) {
2649
3521
 
3522
+ // If there's a condition specified and it returns false,
3523
+ // ignore this event
3524
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
3525
+ return true;
3526
+ }
3527
+
3528
+ // Remember if auto-sliding was paused so we can toggle it
3529
+ var autoSlideWasPaused = autoSlidePaused;
3530
+
2650
3531
  onUserInput( event );
2651
3532
 
2652
3533
  // Check if there's a focused element that could be using
2653
3534
  // the keyboard
2654
- var activeElement = document.activeElement;
2655
- var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
3535
+ var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
3536
+ var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
2656
3537
 
2657
3538
  // Disregard the event if there's a focused element or a
2658
3539
  // keyboard modifier key is present
2659
- if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
3540
+ if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2660
3541
 
2661
3542
  // While paused only allow "unpausing" keyboard events (b and .)
2662
3543
  if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
@@ -2719,10 +3600,12 @@ var Reveal = (function(){
2719
3600
  case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2720
3601
  // return
2721
3602
  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;
3603
+ // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
3604
+ case 58: case 59: case 66: case 190: case 191: togglePause(); break;
2724
3605
  // f
2725
3606
  case 70: enterFullscreen(); break;
3607
+ // a
3608
+ case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
2726
3609
  default:
2727
3610
  triggered = false;
2728
3611
  }
@@ -2732,18 +3615,18 @@ var Reveal = (function(){
2732
3615
  // If the input resulted in a triggered action we should prevent
2733
3616
  // the browsers default behavior
2734
3617
  if( triggered ) {
2735
- event.preventDefault();
3618
+ event.preventDefault && event.preventDefault();
2736
3619
  }
2737
3620
  // ESC or O key
2738
3621
  else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
- if( dom.preview ) {
2740
- closePreview();
3622
+ if( dom.overlay ) {
3623
+ closeOverlay();
2741
3624
  }
2742
3625
  else {
2743
3626
  toggleOverview();
2744
3627
  }
2745
3628
 
2746
- event.preventDefault();
3629
+ event.preventDefault && event.preventDefault();
2747
3630
  }
2748
3631
 
2749
3632
  // If auto-sliding is enabled we need to cue up
@@ -2877,7 +3760,7 @@ var Reveal = (function(){
2877
3760
  */
2878
3761
  function onPointerDown( event ) {
2879
3762
 
2880
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3763
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2881
3764
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2882
3765
  onTouchStart( event );
2883
3766
  }
@@ -2889,7 +3772,7 @@ var Reveal = (function(){
2889
3772
  */
2890
3773
  function onPointerMove( event ) {
2891
3774
 
2892
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3775
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2893
3776
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2894
3777
  onTouchMove( event );
2895
3778
  }
@@ -2901,7 +3784,7 @@ var Reveal = (function(){
2901
3784
  */
2902
3785
  function onPointerUp( event ) {
2903
3786
 
2904
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3787
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2905
3788
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2906
3789
  onTouchEnd( event );
2907
3790
  }
@@ -2942,7 +3825,7 @@ var Reveal = (function(){
2942
3825
 
2943
3826
  event.preventDefault();
2944
3827
 
2945
- var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
3828
+ var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2946
3829
  var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2947
3830
 
2948
3831
  slide( slideIndex );
@@ -3033,10 +3916,12 @@ var Reveal = (function(){
3033
3916
  */
3034
3917
  function onPreviewLinkClicked( event ) {
3035
3918
 
3036
- var url = event.target.getAttribute( 'href' );
3037
- if( url ) {
3038
- openPreview( url );
3039
- event.preventDefault();
3919
+ if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
3920
+ var url = event.currentTarget.getAttribute( 'href' );
3921
+ if( url ) {
3922
+ showPreview( url );
3923
+ event.preventDefault();
3924
+ }
3040
3925
  }
3041
3926
 
3042
3927
  }
@@ -3232,7 +4117,7 @@ var Reveal = (function(){
3232
4117
  // --------------------------------------------------------------------//
3233
4118
 
3234
4119
 
3235
- return {
4120
+ Reveal = {
3236
4121
  initialize: initialize,
3237
4122
  configure: configure,
3238
4123
  sync: sync,
@@ -3275,28 +4160,35 @@ var Reveal = (function(){
3275
4160
  // Toggles the "black screen" mode on/off
3276
4161
  togglePause: togglePause,
3277
4162
 
4163
+ // Toggles the auto slide mode on/off
4164
+ toggleAutoSlide: toggleAutoSlide,
4165
+
3278
4166
  // State checks
3279
4167
  isOverview: isOverview,
3280
4168
  isPaused: isPaused,
4169
+ isAutoSliding: isAutoSliding,
3281
4170
 
3282
4171
  // Adds or removes all internal event listeners (such as keyboard)
3283
4172
  addEventListeners: addEventListeners,
3284
4173
  removeEventListeners: removeEventListeners,
3285
4174
 
4175
+ // Facility for persisting and restoring the presentation state
4176
+ getState: getState,
4177
+ setState: setState,
4178
+
4179
+ // Presentation progress on range of 0-1
4180
+ getProgress: getProgress,
4181
+
3286
4182
  // Returns the indices of the current, or specified, slide
3287
4183
  getIndices: getIndices,
3288
4184
 
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' );
4185
+ getTotalSlides: getTotalSlides,
3293
4186
 
3294
- if( typeof y !== 'undefined' ) {
3295
- return verticalSlides ? verticalSlides[ y ] : undefined;
3296
- }
4187
+ // Returns the slide element at the specified index
4188
+ getSlide: getSlide,
3297
4189
 
3298
- return horizontalSlide;
3299
- },
4190
+ // Returns the slide background element at the specified index
4191
+ getSlideBackground: getSlideBackground,
3300
4192
 
3301
4193
  // Returns the previous slide element, may be null
3302
4194
  getPreviousSlide: function() {
@@ -3330,12 +4222,7 @@ var Reveal = (function(){
3330
4222
  for( var i in query ) {
3331
4223
  var value = query[ i ];
3332
4224
 
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 );
4225
+ query[ i ] = deserialize( unescape( value ) );
3339
4226
  }
3340
4227
 
3341
4228
  return query;
@@ -3343,7 +4230,7 @@ var Reveal = (function(){
3343
4230
 
3344
4231
  // Returns true if we're currently on the first slide
3345
4232
  isFirstSlide: function() {
3346
- return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
4233
+ return ( indexh === 0 && indexv === 0 );
3347
4234
  },
3348
4235
 
3349
4236
  // Returns true if we're currently on the last slide
@@ -3376,7 +4263,14 @@ var Reveal = (function(){
3376
4263
  if( 'addEventListener' in window ) {
3377
4264
  ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
3378
4265
  }
4266
+ },
4267
+
4268
+ // Programatically triggers a keyboard event
4269
+ triggerKey: function( keyCode ) {
4270
+ onDocumentKeyDown( { keyCode: keyCode } );
3379
4271
  }
3380
4272
  };
3381
4273
 
3382
- })();
4274
+ return Reveal;
4275
+
4276
+ }));