slide_hero 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/lib/slide_hero/code.rb +1 -1
  3. data/lib/slide_hero/grouped_slides.rb +1 -1
  4. data/lib/slide_hero/list.rb +6 -3
  5. data/lib/slide_hero/list_point.rb +1 -1
  6. data/lib/slide_hero/presentation.rb +1 -2
  7. data/lib/slide_hero/slide.rb +1 -2
  8. data/lib/slide_hero/version.rb +1 -1
  9. data/lib/slide_hero/views/ordered_list.html.erb +1 -1
  10. data/lib/slide_hero/views/unordered_list.html.erb +1 -1
  11. data/lib/slide_hero.rb +9 -0
  12. data/slide_hero.gemspec +7 -7
  13. data/test/slide_hero/list_spec.rb +31 -9
  14. data/vendor/reveal.js/.gitignore +0 -0
  15. data/vendor/reveal.js/.travis.yml +0 -0
  16. data/vendor/reveal.js/Gruntfile.js +12 -7
  17. data/vendor/reveal.js/LICENSE +0 -0
  18. data/vendor/reveal.js/README.md +168 -33
  19. data/vendor/reveal.js/css/print/paper.css +0 -0
  20. data/vendor/reveal.js/css/print/pdf.css +1 -1
  21. data/vendor/reveal.js/css/reveal.css +248 -17
  22. data/vendor/reveal.js/css/reveal.min.css +1 -1
  23. data/vendor/reveal.js/css/theme/README.md +3 -1
  24. data/vendor/reveal.js/css/theme/beige.css +7 -1
  25. data/vendor/reveal.js/css/theme/blood.css +175 -0
  26. data/vendor/reveal.js/css/theme/default.css +7 -1
  27. data/vendor/reveal.js/css/theme/moon.css +7 -1
  28. data/vendor/reveal.js/css/theme/night.css +7 -1
  29. data/vendor/reveal.js/css/theme/serif.css +7 -1
  30. data/vendor/reveal.js/css/theme/simple.css +7 -1
  31. data/vendor/reveal.js/css/theme/sky.css +7 -1
  32. data/vendor/reveal.js/css/theme/solarized.css +7 -1
  33. data/vendor/reveal.js/css/theme/source/beige.scss +0 -0
  34. data/vendor/reveal.js/css/theme/source/blood.scss +91 -0
  35. data/vendor/reveal.js/css/theme/source/default.scss +0 -0
  36. data/vendor/reveal.js/css/theme/source/moon.scss +0 -0
  37. data/vendor/reveal.js/css/theme/source/night.scss +0 -0
  38. data/vendor/reveal.js/css/theme/source/serif.scss +0 -0
  39. data/vendor/reveal.js/css/theme/source/simple.scss +0 -0
  40. data/vendor/reveal.js/css/theme/source/sky.scss +0 -0
  41. data/vendor/reveal.js/css/theme/source/solarized.scss +0 -0
  42. data/vendor/reveal.js/css/theme/template/mixins.scss +0 -0
  43. data/vendor/reveal.js/css/theme/template/settings.scss +0 -0
  44. data/vendor/reveal.js/css/theme/template/theme.scss +8 -1
  45. data/vendor/reveal.js/index.html +388 -0
  46. data/vendor/reveal.js/js/reveal.js +887 -293
  47. data/vendor/reveal.js/js/reveal.min.js +3 -2
  48. data/vendor/reveal.js/lib/css/zenburn.css +16 -17
  49. data/vendor/reveal.js/lib/font/league_gothic-webfont.svg +0 -0
  50. data/vendor/reveal.js/lib/font/league_gothic-webfont.ttf +0 -0
  51. data/vendor/reveal.js/lib/font/league_gothic-webfont.woff +0 -0
  52. data/vendor/reveal.js/lib/font/league_gothic_license +0 -0
  53. data/vendor/reveal.js/lib/js/classList.js +0 -0
  54. data/vendor/reveal.js/lib/js/head.min.js +0 -0
  55. data/vendor/reveal.js/lib/js/html5shiv.js +0 -0
  56. data/vendor/reveal.js/package.json +10 -9
  57. data/vendor/reveal.js/plugin/highlight/highlight.js +3 -2
  58. data/vendor/reveal.js/plugin/leap/leap.js +0 -0
  59. data/vendor/reveal.js/plugin/markdown/example.html +34 -3
  60. data/vendor/reveal.js/plugin/markdown/example.md +0 -0
  61. data/vendor/reveal.js/plugin/markdown/markdown.js +373 -201
  62. data/vendor/reveal.js/plugin/markdown/marked.js +0 -0
  63. data/vendor/reveal.js/plugin/multiplex/client.js +0 -0
  64. data/vendor/reveal.js/plugin/multiplex/index.js +0 -0
  65. data/vendor/reveal.js/plugin/multiplex/master.js +2 -1
  66. data/vendor/reveal.js/plugin/notes/notes.html +10 -2
  67. data/vendor/reveal.js/plugin/notes/notes.js +0 -0
  68. data/vendor/reveal.js/plugin/notes-server/client.js +0 -0
  69. data/vendor/reveal.js/plugin/notes-server/index.js +0 -0
  70. data/vendor/reveal.js/plugin/notes-server/notes.html +0 -0
  71. data/vendor/reveal.js/plugin/postmessage/example.html +0 -0
  72. data/vendor/reveal.js/plugin/postmessage/postmessage.js +0 -0
  73. data/vendor/reveal.js/plugin/print-pdf/print-pdf.js +0 -0
  74. data/vendor/reveal.js/plugin/remotes/remotes.js +4 -4
  75. data/vendor/reveal.js/plugin/search/search.js +0 -0
  76. data/vendor/reveal.js/plugin/zoom-js/zoom.js +3 -1
  77. data/vendor/reveal.js/{examples → test/examples}/assets/image1.png +0 -0
  78. data/vendor/reveal.js/{examples → test/examples}/assets/image2.png +0 -0
  79. data/vendor/reveal.js/{examples → test/examples}/barebones.html +2 -3
  80. data/vendor/reveal.js/{examples → test/examples}/embedded-media.html +4 -4
  81. data/vendor/reveal.js/{examples → test/examples}/math.html +6 -6
  82. data/vendor/reveal.js/{examples → test/examples}/slide-backgrounds.html +26 -5
  83. data/vendor/reveal.js/test/qunit-1.12.0.css +244 -0
  84. data/vendor/reveal.js/test/qunit-1.12.0.js +2212 -0
  85. data/vendor/reveal.js/test/test-markdown-element-attributes.html +134 -0
  86. data/vendor/reveal.js/test/test-markdown-element-attributes.js +46 -0
  87. data/vendor/reveal.js/test/test-markdown-slide-attributes.html +128 -0
  88. data/vendor/reveal.js/test/test-markdown-slide-attributes.js +47 -0
  89. data/vendor/reveal.js/test/test-markdown.html +52 -0
  90. data/vendor/reveal.js/test/test-markdown.js +15 -0
  91. data/vendor/reveal.js/test/test.html +81 -0
  92. data/vendor/reveal.js/test/test.js +438 -0
  93. metadata +58 -45
@@ -12,7 +12,7 @@ var Reveal = (function(){
12
12
  var SLIDES_SELECTOR = '.reveal .slides section',
13
13
  HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
14
  VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-child',
15
+ HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
16
16
 
17
17
  // Configurations defaults, can be overridden at initialization time
18
18
  config = {
@@ -35,6 +35,9 @@ var Reveal = (function(){
35
35
  // Display a presentation progress bar
36
36
  progress: true,
37
37
 
38
+ // Display the page number of the current slide
39
+ slideNumber: false,
40
+
38
41
  // Push each slide change to the browser history
39
42
  history: false,
40
43
 
@@ -44,7 +47,7 @@ var Reveal = (function(){
44
47
  // Enable the slide overview mode
45
48
  overview: true,
46
49
 
47
- // Vertical centring of slides
50
+ // Vertical centering of slides
48
51
  center: true,
49
52
 
50
53
  // Enables touch navigation on devices with touch input
@@ -68,15 +71,24 @@ var Reveal = (function(){
68
71
  // by using a data-autoslide attribute on your slides
69
72
  autoSlide: 0,
70
73
 
74
+ // Stop auto-sliding after user input
75
+ autoSlideStoppable: true,
76
+
71
77
  // Enable slide navigation via mouse wheel
72
78
  mouseWheel: false,
73
79
 
74
80
  // Apply a 3D roll to links on hover
75
81
  rollingLinks: false,
76
82
 
83
+ // Hides the address bar on mobile devices
84
+ hideAddressBar: true,
85
+
77
86
  // Opens links in an iframe preview overlay
78
87
  previewLinks: false,
79
88
 
89
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
+ focusBodyOnPageVisiblityChange: true,
91
+
80
92
  // Theme (see /css/theme)
81
93
  theme: null,
82
94
 
@@ -89,19 +101,23 @@ var Reveal = (function(){
89
101
  // Transition style for full page slide backgrounds
90
102
  backgroundTransition: 'default', // default/linear/none
91
103
 
104
+ // Parallax background image
105
+ parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
106
+
107
+ // Parallax background size
108
+ parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
109
+
92
110
  // Number of slides away from the current that are visible
93
111
  viewDistance: 3,
94
112
 
95
113
  // Script dependencies to load
96
114
  dependencies: []
115
+
97
116
  },
98
117
 
99
118
  // Flags if reveal.js is loaded (has dispatched the 'ready' event)
100
119
  loaded = false,
101
120
 
102
- // The current auto-slide duration
103
- autoSlide = 0,
104
-
105
121
  // The horizontal and vertical index of the currently active slide
106
122
  indexh,
107
123
  indexv,
@@ -110,6 +126,8 @@ var Reveal = (function(){
110
126
  previousSlide,
111
127
  currentSlide,
112
128
 
129
+ previousBackground,
130
+
113
131
  // Slides may hold a data-state attribute which we pick up and apply
114
132
  // as a class to the body. This list contains the combined state of
115
133
  // all current slides.
@@ -121,11 +139,8 @@ var Reveal = (function(){
121
139
  // Cached references to DOM elements
122
140
  dom = {},
123
141
 
124
- // Client support for CSS 3D transforms, see #checkCapabilities()
125
- supports3DTransforms,
126
-
127
- // Client support for CSS 2D transforms, see #checkCapabilities()
128
- supports2DTransforms,
142
+ // Features supported by the browser, see #checkCapabilities()
143
+ features = {},
129
144
 
130
145
  // Client is a mobile device, see #checkCapabilities()
131
146
  isMobileDevice,
@@ -133,9 +148,6 @@ var Reveal = (function(){
133
148
  // Throttles mouse wheel navigation
134
149
  lastMouseWheelStep = 0,
135
150
 
136
- // An interval used to automatically move on to the next slide
137
- autoSlideTimeout = 0,
138
-
139
151
  // Delays updates to the URL due to a Chrome thumbnailer bug
140
152
  writeURLTimeout = 0,
141
153
 
@@ -148,6 +160,15 @@ var Reveal = (function(){
148
160
  // Flags if the interaction event listeners are bound
149
161
  eventsAreBound = false,
150
162
 
163
+ // The current auto-slide duration
164
+ autoSlide = 0,
165
+
166
+ // Auto slide properties
167
+ autoSlidePlayer,
168
+ autoSlideTimeout = 0,
169
+ autoSlideStartTime = -1,
170
+ autoSlidePaused = false,
171
+
151
172
  // Holds information about the currently ongoing touch input
152
173
  touch = {
153
174
  startX: 0,
@@ -165,7 +186,7 @@ var Reveal = (function(){
165
186
 
166
187
  checkCapabilities();
167
188
 
168
- if( !supports2DTransforms && !supports3DTransforms ) {
189
+ if( !features.transforms2d && !features.transforms3d ) {
169
190
  document.body.setAttribute( 'class', 'no-transforms' );
170
191
 
171
192
  // If the browser doesn't support core features we won't be
@@ -176,8 +197,15 @@ var Reveal = (function(){
176
197
  // Force a layout when the whole page, incl fonts, has loaded
177
198
  window.addEventListener( 'load', layout, false );
178
199
 
200
+ var query = Reveal.getQueryHash();
201
+
202
+ // Do not accept new dependencies via query config to avoid
203
+ // the potential of malicious script injection
204
+ if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
205
+
179
206
  // Copy options over to our config object
180
207
  extend( config, options );
208
+ extend( config, query );
181
209
 
182
210
  // Hide the address bar in mobile browsers
183
211
  hideAddressBar();
@@ -193,33 +221,63 @@ var Reveal = (function(){
193
221
  */
194
222
  function checkCapabilities() {
195
223
 
196
- supports3DTransforms = 'WebkitPerspective' in document.body.style ||
224
+ features.transforms3d = 'WebkitPerspective' in document.body.style ||
197
225
  'MozPerspective' in document.body.style ||
198
226
  'msPerspective' in document.body.style ||
199
227
  'OPerspective' in document.body.style ||
200
228
  'perspective' in document.body.style;
201
229
 
202
- supports2DTransforms = 'WebkitTransform' in document.body.style ||
230
+ features.transforms2d = 'WebkitTransform' in document.body.style ||
203
231
  'MozTransform' in document.body.style ||
204
232
  'msTransform' in document.body.style ||
205
233
  'OTransform' in document.body.style ||
206
234
  'transform' in document.body.style;
207
235
 
236
+ features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
237
+ features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
238
+
239
+ features.canvas = !!document.createElement( 'canvas' ).getContext;
240
+
208
241
  isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
209
242
 
210
243
  }
211
244
 
212
- /**
213
- * Loads the dependencies of reveal.js. Dependencies are
214
- * defined via the configuration option 'dependencies'
215
- * and will be loaded prior to starting/binding reveal.js.
216
- * Some dependencies may have an 'async' flag, if so they
217
- * will load after reveal.js has been started up.
218
- */
245
+
246
+ /**
247
+ * Loads the dependencies of reveal.js. Dependencies are
248
+ * defined via the configuration option 'dependencies'
249
+ * and will be loaded prior to starting/binding reveal.js.
250
+ * Some dependencies may have an 'async' flag, if so they
251
+ * will load after reveal.js has been started up.
252
+ */
219
253
  function load() {
220
254
 
221
255
  var scripts = [],
222
- scriptsAsync = [];
256
+ scriptsAsync = [],
257
+ scriptsToPreload = 0;
258
+
259
+ // Called once synchronous scripts finish loading
260
+ function proceed() {
261
+ if( scriptsAsync.length ) {
262
+ // Load asynchronous scripts
263
+ head.js.apply( null, scriptsAsync );
264
+ }
265
+
266
+ start();
267
+ }
268
+
269
+ function loadScript( s ) {
270
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
271
+ // Extension may contain callback functions
272
+ if( typeof s.callback === 'function' ) {
273
+ s.callback.apply( this );
274
+ }
275
+
276
+ if( --scriptsToPreload === 0 ) {
277
+ proceed();
278
+ }
279
+ });
280
+ }
223
281
 
224
282
  for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
225
283
  var s = config.dependencies[i];
@@ -233,25 +291,12 @@ var Reveal = (function(){
233
291
  scripts.push( s.src );
234
292
  }
235
293
 
236
- // Extension may contain callback functions
237
- if( typeof s.callback === 'function' ) {
238
- head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback );
239
- }
294
+ loadScript( s );
240
295
  }
241
296
  }
242
297
 
243
- // Called once synchronous scripts finish loading
244
- function proceed() {
245
- if( scriptsAsync.length ) {
246
- // Load asynchronous scripts
247
- head.js.apply( null, scriptsAsync );
248
- }
249
-
250
- start();
251
- }
252
-
253
298
  if( scripts.length ) {
254
- head.ready( proceed );
299
+ scriptsToPreload = scripts.length;
255
300
 
256
301
  // Load synchronous scripts
257
302
  head.js.apply( null, scripts );
@@ -271,8 +316,8 @@ var Reveal = (function(){
271
316
  // Make sure we've got all the DOM elements we need
272
317
  setupDOM();
273
318
 
274
- // Decorate the slide DOM elements with state classes (past/future)
275
- setupSlides();
319
+ // Resets all vertical slides so that only the first is visible
320
+ resetVerticalSlides();
276
321
 
277
322
  // Updates the presentation to match the current configuration values
278
323
  configure();
@@ -280,6 +325,9 @@ var Reveal = (function(){
280
325
  // Read the initial hash
281
326
  readURL();
282
327
 
328
+ // Update all backgrounds
329
+ updateBackground( true );
330
+
283
331
  // Notify listeners that the presentation is ready but use a 1ms
284
332
  // timeout to ensure it's not fired synchronously after #initialize()
285
333
  setTimeout( function() {
@@ -297,26 +345,6 @@ var Reveal = (function(){
297
345
 
298
346
  }
299
347
 
300
- /**
301
- * Iterates through and decorates slides DOM elements with
302
- * appropriate classes.
303
- */
304
- function setupSlides() {
305
-
306
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
307
- horizontalSlides.forEach( function( horizontalSlide ) {
308
-
309
- var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
310
- verticalSlides.forEach( function( verticalSlide, y ) {
311
-
312
- if( y > 0 ) verticalSlide.classList.add( 'future' );
313
-
314
- } );
315
-
316
- } );
317
-
318
- }
319
-
320
348
  /**
321
349
  * Finds and stores references to DOM elements which are
322
350
  * required by the presentation. If a required element is
@@ -346,6 +374,9 @@ var Reveal = (function(){
346
374
  '<div class="navigate-up"></div>' +
347
375
  '<div class="navigate-down"></div>' );
348
376
 
377
+ // Slide number
378
+ dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
+
349
380
  // State background element [DEPRECATED]
350
381
  createSingletonNode( dom.wrapper, 'div', 'state-background', null );
351
382
 
@@ -353,17 +384,15 @@ var Reveal = (function(){
353
384
  createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
354
385
 
355
386
  // Cache references to elements
356
- if ( config.controls ) {
357
- dom.controls = document.querySelector( '.reveal .controls' );
387
+ dom.controls = document.querySelector( '.reveal .controls' );
358
388
 
359
- // There can be multiple instances of controls throughout the page
360
- dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
361
- dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
362
- dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
363
- dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
364
- dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
365
- dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
366
- }
389
+ // There can be multiple instances of controls throughout the page
390
+ dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
391
+ dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
392
+ dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
393
+ dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
394
+ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
+ dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
367
396
 
368
397
  }
369
398
 
@@ -421,7 +450,7 @@ var Reveal = (function(){
421
450
 
422
451
  if( data.background ) {
423
452
  // Auto-wrap image urls in url(...)
424
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
453
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
425
454
  element.style.backgroundImage = 'url('+ data.background +')';
426
455
  }
427
456
  else {
@@ -429,6 +458,10 @@ var Reveal = (function(){
429
458
  }
430
459
  }
431
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
+
432
465
  // Additional and optional background properties
433
466
  if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
434
467
  if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
@@ -469,6 +502,28 @@ var Reveal = (function(){
469
502
 
470
503
  } );
471
504
 
505
+ // Add parallax background if specified
506
+ if( config.parallaxBackgroundImage ) {
507
+
508
+ dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
509
+ dom.background.style.backgroundSize = config.parallaxBackgroundSize;
510
+
511
+ // Make sure the below properties are set on the element - these properties are
512
+ // needed for proper transitions to be set on the element via CSS. To remove
513
+ // annoying background slide-in effect when the presentation starts, apply
514
+ // these properties after short time delay
515
+ setTimeout( function() {
516
+ dom.wrapper.classList.add( 'has-parallax-background' );
517
+ }, 1 );
518
+
519
+ }
520
+ else {
521
+
522
+ dom.background.style.backgroundImage = '';
523
+ dom.wrapper.classList.remove( 'has-parallax-background' );
524
+
525
+ }
526
+
472
527
  }
473
528
 
474
529
  /**
@@ -477,6 +532,8 @@ var Reveal = (function(){
477
532
  */
478
533
  function configure( options ) {
479
534
 
535
+ var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
536
+
480
537
  dom.wrapper.classList.remove( config.transition );
481
538
 
482
539
  // New config options may be passed when this method
@@ -484,20 +541,15 @@ var Reveal = (function(){
484
541
  if( typeof options === 'object' ) extend( config, options );
485
542
 
486
543
  // Force linear transition based on browser capabilities
487
- if( supports3DTransforms === false ) config.transition = 'linear';
544
+ if( features.transforms3d === false ) config.transition = 'linear';
488
545
 
489
546
  dom.wrapper.classList.add( config.transition );
490
547
 
491
548
  dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
492
549
  dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
493
550
 
494
- if( dom.controls ) {
495
- dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
496
- }
497
-
498
- if( dom.progress ) {
499
- dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none';
500
- }
551
+ dom.controls.style.display = config.controls ? 'block' : 'none';
552
+ dom.progress.style.display = config.progress ? 'block' : 'none';
501
553
 
502
554
  if( config.rtl ) {
503
555
  dom.wrapper.classList.add( 'rtl' );
@@ -539,6 +591,20 @@ var Reveal = (function(){
539
591
  enablePreviewLinks( '[data-preview-link]' );
540
592
  }
541
593
 
594
+ // Auto-slide playback controls
595
+ if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
+ autoSlidePlayer = new Playback( dom.wrapper, function() {
597
+ return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
598
+ } );
599
+
600
+ autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
+ autoSlidePaused = false;
602
+ }
603
+ else if( autoSlidePlayer ) {
604
+ autoSlidePlayer.destroy();
605
+ autoSlidePlayer = null;
606
+ }
607
+
542
608
  // Load the theme in the config, if it's not already loaded
543
609
  if( config.theme && dom.theme ) {
544
610
  var themeURL = dom.theme.getAttribute( 'href' );
@@ -582,21 +648,37 @@ var Reveal = (function(){
582
648
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
583
649
  }
584
650
 
585
- if ( config.progress && dom.progress ) {
651
+ if( config.progress && dom.progress ) {
586
652
  dom.progress.addEventListener( 'click', onProgressClicked, false );
587
653
  }
588
654
 
589
- if ( config.controls && dom.controls ) {
590
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
591
- dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
592
- dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
593
- dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
594
- dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
595
- dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
596
- dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
597
- } );
655
+ if( config.focusBodyOnPageVisiblityChange ) {
656
+ var visibilityChange;
657
+
658
+ if( 'hidden' in document ) {
659
+ visibilityChange = 'visibilitychange';
660
+ }
661
+ else if( 'msHidden' in document ) {
662
+ visibilityChange = 'msvisibilitychange';
663
+ }
664
+ else if( 'webkitHidden' in document ) {
665
+ visibilityChange = 'webkitvisibilitychange';
666
+ }
667
+
668
+ if( visibilityChange ) {
669
+ document.addEventListener( visibilityChange, onPageVisibilityChange, false );
670
+ }
598
671
  }
599
672
 
673
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
674
+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
+ dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
+ dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
677
+ dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
678
+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
679
+ dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
680
+ } );
681
+
600
682
  }
601
683
 
602
684
  /**
@@ -624,16 +706,14 @@ var Reveal = (function(){
624
706
  dom.progress.removeEventListener( 'click', onProgressClicked, false );
625
707
  }
626
708
 
627
- if ( config.controls && dom.controls ) {
628
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
629
- dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
630
- dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
631
- dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
632
- dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
633
- dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
634
- dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
635
- } );
636
- }
709
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
710
+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
711
+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
712
+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
713
+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
714
+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
715
+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
716
+ } );
637
717
 
638
718
  }
639
719
 
@@ -778,7 +858,7 @@ var Reveal = (function(){
778
858
  */
779
859
  function hideAddressBar() {
780
860
 
781
- if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
861
+ if( config.hideAddressBar && isMobileDevice ) {
782
862
  // Events that should trigger the address bar to hide
783
863
  window.addEventListener( 'load', removeAddressBar, false );
784
864
  window.addEventListener( 'orientationchange', removeAddressBar, false );
@@ -792,15 +872,6 @@ var Reveal = (function(){
792
872
  */
793
873
  function removeAddressBar() {
794
874
 
795
- if( window.orientation === 0 ) {
796
- document.documentElement.style.overflow = 'scroll';
797
- document.body.style.height = '120%';
798
- }
799
- else {
800
- document.documentElement.style.overflow = '';
801
- document.body.style.height = '100%';
802
- }
803
-
804
875
  setTimeout( function() {
805
876
  window.scrollTo( 0, 1 );
806
877
  }, 10 );
@@ -825,7 +896,7 @@ var Reveal = (function(){
825
896
  */
826
897
  function enableRollingLinks() {
827
898
 
828
- if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
899
+ if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
829
900
  var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
830
901
 
831
902
  for( var i = 0, len = anchors.length; i < len; i++ ) {
@@ -948,38 +1019,6 @@ var Reveal = (function(){
948
1019
 
949
1020
  }
950
1021
 
951
- /**
952
- * Return a sorted fragments list, ordered by an increasing
953
- * "data-fragment-index" attribute.
954
- *
955
- * Fragments will be revealed in the order that they are returned by
956
- * this function, so you can use the index attributes to control the
957
- * order of fragment appearance.
958
- *
959
- * To maintain a sensible default fragment order, fragments are presumed
960
- * to be passed in document order. This function adds a "fragment-index"
961
- * attribute to each node if such an attribute is not already present,
962
- * and sets that attribute to an integer value which is the position of
963
- * the fragment within the fragments list.
964
- */
965
- function sortFragments( fragments ) {
966
-
967
- var a = toArray( fragments );
968
-
969
- a.forEach( function( el, idx ) {
970
- if( !el.hasAttribute( 'data-fragment-index' ) ) {
971
- el.setAttribute( 'data-fragment-index', idx );
972
- }
973
- } );
974
-
975
- a.sort( function( l, r ) {
976
- return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
977
- } );
978
-
979
- return a;
980
-
981
- }
982
-
983
1022
  /**
984
1023
  * Applies JavaScript-controlled layout rules to the
985
1024
  * presentation.
@@ -1045,7 +1084,7 @@ var Reveal = (function(){
1045
1084
  continue;
1046
1085
  }
1047
1086
 
1048
- if( config.center ) {
1087
+ if( config.center || slide.classList.contains( 'center' ) ) {
1049
1088
  // Vertical stacks are not centred since their section
1050
1089
  // children will be
1051
1090
  if( slide.classList.contains( 'stack' ) ) {
@@ -1062,6 +1101,7 @@ var Reveal = (function(){
1062
1101
  }
1063
1102
 
1064
1103
  updateProgress();
1104
+ updateParallax();
1065
1105
 
1066
1106
  }
1067
1107
 
@@ -1156,7 +1196,7 @@ var Reveal = (function(){
1156
1196
  var depth = window.innerWidth < 400 ? 1000 : 2500;
1157
1197
 
1158
1198
  dom.wrapper.classList.add( 'overview' );
1159
- dom.wrapper.classList.remove( 'exit-overview' );
1199
+ dom.wrapper.classList.remove( 'overview-deactivating' );
1160
1200
 
1161
1201
  clearTimeout( activateOverviewTimeout );
1162
1202
  clearTimeout( deactivateOverviewTimeout );
@@ -1164,7 +1204,7 @@ var Reveal = (function(){
1164
1204
  // Not the pretties solution, but need to let the overview
1165
1205
  // class apply first so that slides are measured accurately
1166
1206
  // before we can position them
1167
- activateOverviewTimeout = setTimeout( function(){
1207
+ activateOverviewTimeout = setTimeout( function() {
1168
1208
 
1169
1209
  var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1170
1210
 
@@ -1241,25 +1281,19 @@ var Reveal = (function(){
1241
1281
  // Temporarily add a class so that transitions can do different things
1242
1282
  // depending on whether they are exiting/entering overview, or just
1243
1283
  // moving from slide to slide
1244
- dom.wrapper.classList.add( 'exit-overview' );
1284
+ dom.wrapper.classList.add( 'overview-deactivating' );
1245
1285
 
1246
1286
  deactivateOverviewTimeout = setTimeout( function () {
1247
- dom.wrapper.classList.remove( 'exit-overview' );
1248
- }, 10);
1287
+ dom.wrapper.classList.remove( 'overview-deactivating' );
1288
+ }, 1 );
1249
1289
 
1250
1290
  // Select all slides
1251
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1252
-
1253
- for( var i = 0, len = slides.length; i < len; i++ ) {
1254
- var element = slides[i];
1255
-
1256
- element.style.display = '';
1257
-
1291
+ toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1258
1292
  // Resets all transforms to use the external styles
1259
- transformElement( element, '' );
1293
+ transformElement( slide, '' );
1260
1294
 
1261
- element.removeEventListener( 'click', onOverviewSlideClicked, true );
1262
- }
1295
+ slide.removeEventListener( 'click', onOverviewSlideClicked, true );
1296
+ } );
1263
1297
 
1264
1298
  slide( indexh, indexv );
1265
1299
 
@@ -1317,7 +1351,7 @@ var Reveal = (function(){
1317
1351
  // Prefer slide argument, otherwise use current slide
1318
1352
  slide = slide ? slide : currentSlide;
1319
1353
 
1320
- return slide && !!slide.parentNode.nodeName.match( /section/i );
1354
+ return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );
1321
1355
 
1322
1356
  }
1323
1357
 
@@ -1484,19 +1518,9 @@ var Reveal = (function(){
1484
1518
  // Store references to the previous and current slides
1485
1519
  currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
1486
1520
 
1487
-
1488
1521
  // Show fragment, if specified
1489
1522
  if( typeof f !== 'undefined' ) {
1490
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
1491
-
1492
- toArray( fragments ).forEach( function( fragment, indexf ) {
1493
- if( indexf < f ) {
1494
- fragment.classList.add( 'visible' );
1495
- }
1496
- else {
1497
- fragment.classList.remove( 'visible' );
1498
- }
1499
- } );
1523
+ navigateFragment( f );
1500
1524
  }
1501
1525
 
1502
1526
  // Dispatch an event if the slide changed
@@ -1546,10 +1570,14 @@ var Reveal = (function(){
1546
1570
  updateControls();
1547
1571
  updateProgress();
1548
1572
  updateBackground();
1573
+ updateParallax();
1574
+ updateSlideNumber();
1549
1575
 
1550
1576
  // Update the URL hash
1551
1577
  writeURL();
1552
1578
 
1579
+ cueAutoSlide();
1580
+
1553
1581
  }
1554
1582
 
1555
1583
  /**
@@ -1575,9 +1603,58 @@ var Reveal = (function(){
1575
1603
  // Re-create the slide backgrounds
1576
1604
  createBackgrounds();
1577
1605
 
1606
+ sortAllFragments();
1607
+
1578
1608
  updateControls();
1579
1609
  updateProgress();
1580
- updateBackground();
1610
+ updateBackground( true );
1611
+ updateSlideNumber();
1612
+
1613
+ }
1614
+
1615
+ /**
1616
+ * Resets all vertical slides so that only the first
1617
+ * is visible.
1618
+ */
1619
+ function resetVerticalSlides() {
1620
+
1621
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
+ horizontalSlides.forEach( function( horizontalSlide ) {
1623
+
1624
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1625
+ verticalSlides.forEach( function( verticalSlide, y ) {
1626
+
1627
+ if( y > 0 ) {
1628
+ verticalSlide.classList.remove( 'present' );
1629
+ verticalSlide.classList.remove( 'past' );
1630
+ verticalSlide.classList.add( 'future' );
1631
+ }
1632
+
1633
+ } );
1634
+
1635
+ } );
1636
+
1637
+ }
1638
+
1639
+ /**
1640
+ * Sorts and formats all of fragments in the
1641
+ * presentation.
1642
+ */
1643
+ function sortAllFragments() {
1644
+
1645
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
+ horizontalSlides.forEach( function( horizontalSlide ) {
1647
+
1648
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1649
+ verticalSlides.forEach( function( verticalSlide, y ) {
1650
+
1651
+ sortFragments( verticalSlide.querySelectorAll( '.fragment' ) );
1652
+
1653
+ } );
1654
+
1655
+ if( verticalSlides.length === 0 ) sortFragments( horizontalSlide.querySelectorAll( '.fragment' ) );
1656
+
1657
+ } );
1581
1658
 
1582
1659
  }
1583
1660
 
@@ -1630,16 +1707,27 @@ var Reveal = (function(){
1630
1707
  if( i < index ) {
1631
1708
  // Any element previous to index is given the 'past' class
1632
1709
  element.classList.add( reverse ? 'future' : 'past' );
1710
+
1711
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
+
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' );
1718
+ }
1633
1719
  }
1634
1720
  else if( i > index ) {
1635
1721
  // Any element subsequent to index is given the 'future' class
1636
1722
  element.classList.add( reverse ? 'past' : 'future' );
1637
1723
 
1638
- var fragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1724
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1639
1725
 
1640
1726
  // No fragments in future slides should be visible ahead of time
1641
- while( fragments.length ) {
1642
- fragments.pop().classList.remove( 'visible' );
1727
+ while( futureFragments.length ) {
1728
+ var futureFragment = futureFragments.pop();
1729
+ futureFragment.classList.remove( 'visible' );
1730
+ futureFragment.classList.remove( 'current-fragment' );
1643
1731
  }
1644
1732
  }
1645
1733
 
@@ -1660,18 +1748,6 @@ var Reveal = (function(){
1660
1748
  state = state.concat( slideState.split( ' ' ) );
1661
1749
  }
1662
1750
 
1663
- // If this slide has a data-autoslide attribute associated use this as
1664
- // autoSlide value otherwise use the global configured time
1665
- var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
1666
- if( slideAutoSlide ) {
1667
- autoSlide = parseInt( slideAutoSlide, 10 );
1668
- }
1669
- else {
1670
- autoSlide = config.autoSlide;
1671
- }
1672
-
1673
- cueAutoSlide();
1674
-
1675
1751
  }
1676
1752
  else {
1677
1753
  // Since there are no slides we can't be anywhere beyond the
@@ -1788,52 +1864,68 @@ var Reveal = (function(){
1788
1864
  }
1789
1865
 
1790
1866
  /**
1791
- * Updates the state of all control/navigation arrows.
1867
+ * Updates the slide number div to reflect the current slide.
1792
1868
  */
1793
- function updateControls() {
1869
+ function updateSlideNumber() {
1794
1870
 
1795
- if ( config.controls && dom.controls ) {
1871
+ // Update slide number if enabled
1872
+ if( config.slideNumber && dom.slideNumber) {
1796
1873
 
1797
- var routes = availableRoutes();
1798
- var fragments = availableFragments();
1874
+ // Display the number of the page using 'indexh - indexv' format
1875
+ var indexString = indexh;
1876
+ if( indexv > 0 ) {
1877
+ indexString += ' - ' + indexv;
1878
+ }
1799
1879
 
1800
- // Remove the 'enabled' class from all directions
1801
- dom.controlsLeft.concat( dom.controlsRight )
1802
- .concat( dom.controlsUp )
1803
- .concat( dom.controlsDown )
1804
- .concat( dom.controlsPrev )
1805
- .concat( dom.controlsNext ).forEach( function( node ) {
1806
- node.classList.remove( 'enabled' );
1807
- node.classList.remove( 'fragmented' );
1808
- } );
1880
+ dom.slideNumber.innerHTML = indexString;
1881
+ }
1882
+
1883
+ }
1809
1884
 
1810
- // Add the 'enabled' class to the available routes
1811
- if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1812
- if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1813
- if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1814
- if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1885
+ /**
1886
+ * Updates the state of all control/navigation arrows.
1887
+ */
1888
+ function updateControls() {
1815
1889
 
1816
- // Prev/next buttons
1817
- if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1818
- if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1890
+ var routes = availableRoutes();
1891
+ var fragments = availableFragments();
1892
+
1893
+ // Remove the 'enabled' class from all directions
1894
+ dom.controlsLeft.concat( dom.controlsRight )
1895
+ .concat( dom.controlsUp )
1896
+ .concat( dom.controlsDown )
1897
+ .concat( dom.controlsPrev )
1898
+ .concat( dom.controlsNext ).forEach( function( node ) {
1899
+ node.classList.remove( 'enabled' );
1900
+ node.classList.remove( 'fragmented' );
1901
+ } );
1819
1902
 
1820
- // Highlight fragment directions
1821
- if( currentSlide ) {
1903
+ // Add the 'enabled' class to the available routes
1904
+ if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1905
+ if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1906
+ if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1907
+ if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1822
1908
 
1823
- // Always apply fragment decorator to prev/next buttons
1824
- if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1825
- if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1909
+ // Prev/next buttons
1910
+ if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1911
+ if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1826
1912
 
1827
- // Apply fragment decorators to directional buttons based on
1828
- // what slide axis they are in
1829
- if( isVerticalSlide( currentSlide ) ) {
1830
- if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1831
- if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1832
- }
1833
- else {
1834
- if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1835
- if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1836
- }
1913
+ // Highlight fragment directions
1914
+ if( currentSlide ) {
1915
+
1916
+ // Always apply fragment decorator to prev/next buttons
1917
+ if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1918
+ if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1919
+
1920
+ // Apply fragment decorators to directional buttons based on
1921
+ // what slide axis they are in
1922
+ if( isVerticalSlide( currentSlide ) ) {
1923
+ if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1924
+ if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1925
+ }
1926
+ else {
1927
+ if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1928
+ if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1837
1929
  }
1838
1930
 
1839
1931
  }
@@ -1841,29 +1933,70 @@ var Reveal = (function(){
1841
1933
  }
1842
1934
 
1843
1935
  /**
1844
- * Updates the background elements to reflect the current
1936
+ * Updates the background elements to reflect the current
1845
1937
  * slide.
1938
+ *
1939
+ * @param {Boolean} includeAll If true, the backgrounds of
1940
+ * all vertical slides (not just the present) will be updated.
1846
1941
  */
1847
- function updateBackground() {
1942
+ function updateBackground( includeAll ) {
1943
+
1944
+ var currentBackground = null;
1848
1945
 
1849
- // Update the classes of all backgrounds to match the
1946
+ // Reverse past/future classes when in RTL mode
1947
+ var horizontalPast = config.rtl ? 'future' : 'past',
1948
+ horizontalFuture = config.rtl ? 'past' : 'future';
1949
+
1950
+ // Update the classes of all backgrounds to match the
1850
1951
  // states of their slides (past/present/future)
1851
1952
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1852
1953
 
1853
- // Reverse past/future classes when in RTL mode
1854
- var horizontalPast = config.rtl ? 'future' : 'past',
1855
- horizontalFuture = config.rtl ? 'past' : 'future';
1954
+ if( h < indexh ) {
1955
+ backgroundh.className = 'slide-background ' + horizontalPast;
1956
+ }
1957
+ else if ( h > indexh ) {
1958
+ backgroundh.className = 'slide-background ' + horizontalFuture;
1959
+ }
1960
+ else {
1961
+ backgroundh.className = 'slide-background present';
1856
1962
 
1857
- backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
1963
+ // Store a reference to the current background element
1964
+ currentBackground = backgroundh;
1965
+ }
1858
1966
 
1859
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1967
+ if( includeAll || h === indexh ) {
1968
+ toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1860
1969
 
1861
- backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
1970
+ if( v < indexv ) {
1971
+ backgroundv.className = 'slide-background past';
1972
+ }
1973
+ else if ( v > indexv ) {
1974
+ backgroundv.className = 'slide-background future';
1975
+ }
1976
+ else {
1977
+ backgroundv.className = 'slide-background present';
1862
1978
 
1863
- } );
1979
+ // Only if this is the present horizontal and vertical slide
1980
+ if( h === indexh ) currentBackground = backgroundv;
1981
+ }
1982
+
1983
+ } );
1984
+ }
1864
1985
 
1865
1986
  } );
1866
1987
 
1988
+ // Don't transition between identical backgrounds. This
1989
+ // prevents unwanted flicker.
1990
+ if( currentBackground ) {
1991
+ var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
+ var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
+ if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
1994
+ dom.background.classList.add( 'no-transition' );
1995
+ }
1996
+
1997
+ previousBackground = currentBackground;
1998
+ }
1999
+
1867
2000
  // Allow the first background to apply without transition
1868
2001
  setTimeout( function() {
1869
2002
  dom.background.classList.remove( 'no-transition' );
@@ -1871,6 +2004,42 @@ var Reveal = (function(){
1871
2004
 
1872
2005
  }
1873
2006
 
2007
+ /**
2008
+ * Updates the position of the parallax background based
2009
+ * on the current slide index.
2010
+ */
2011
+ function updateParallax() {
2012
+
2013
+ if( config.parallaxBackgroundImage ) {
2014
+
2015
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
+ verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
+
2018
+ var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
+ backgroundWidth, backgroundHeight;
2020
+
2021
+ if( backgroundSize.length === 1 ) {
2022
+ backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
2023
+ }
2024
+ else {
2025
+ backgroundWidth = parseInt( backgroundSize[0], 10 );
2026
+ backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
+ }
2028
+
2029
+ var slideWidth = dom.background.offsetWidth;
2030
+ var horizontalSlideCount = horizontalSlides.length;
2031
+ var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2032
+
2033
+ var slideHeight = dom.background.offsetHeight;
2034
+ var verticalSlideCount = verticalSlides.length;
2035
+ var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2036
+
2037
+ dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2038
+
2039
+ }
2040
+
2041
+ }
2042
+
1874
2043
  /**
1875
2044
  * Determine what available routes there are for navigation.
1876
2045
  *
@@ -1928,7 +2097,7 @@ var Reveal = (function(){
1928
2097
  */
1929
2098
  function startEmbeddedContent( slide ) {
1930
2099
 
1931
- if( slide ) {
2100
+ if( slide && !isSpeakerNotes() ) {
1932
2101
  // HTML5 media elements
1933
2102
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1934
2103
  if( el.hasAttribute( 'data-autoplay' ) ) {
@@ -1936,10 +2105,15 @@ var Reveal = (function(){
1936
2105
  }
1937
2106
  } );
1938
2107
 
2108
+ // iframe embeds
2109
+ toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
+ el.contentWindow.postMessage( 'slide:start', '*' );
2111
+ });
2112
+
1939
2113
  // YouTube embeds
1940
2114
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1941
2115
  if( el.hasAttribute( 'data-autoplay' ) ) {
1942
- el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
2116
+ el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
1943
2117
  }
1944
2118
  });
1945
2119
  }
@@ -1960,16 +2134,31 @@ var Reveal = (function(){
1960
2134
  }
1961
2135
  } );
1962
2136
 
2137
+ // iframe embeds
2138
+ toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
+ el.contentWindow.postMessage( 'slide:stop', '*' );
2140
+ });
2141
+
1963
2142
  // YouTube embeds
1964
2143
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1965
2144
  if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
1966
- el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
2145
+ el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
1967
2146
  }
1968
2147
  });
1969
2148
  }
1970
2149
 
1971
2150
  }
1972
2151
 
2152
+ /**
2153
+ * Checks if this presentation is running inside of the
2154
+ * speaker notes window.
2155
+ */
2156
+ function isSpeakerNotes() {
2157
+
2158
+ return !!window.location.search.match( /receiver/gi );
2159
+
2160
+ }
2161
+
1973
2162
  /**
1974
2163
  * Reads the current URL (hash) and navigates accordingly.
1975
2164
  */
@@ -2084,7 +2273,7 @@ var Reveal = (function(){
2084
2273
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2085
2274
  if( hasFragments ) {
2086
2275
  var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2087
- f = visibleFragments.length;
2276
+ f = visibleFragments.length - 1;
2088
2277
  }
2089
2278
  }
2090
2279
 
@@ -2093,83 +2282,226 @@ var Reveal = (function(){
2093
2282
  }
2094
2283
 
2095
2284
  /**
2096
- * Navigate to the next slide fragment.
2285
+ * Return a sorted fragments list, ordered by an increasing
2286
+ * "data-fragment-index" attribute.
2097
2287
  *
2098
- * @return {Boolean} true if there was a next fragment,
2099
- * false otherwise
2288
+ * Fragments will be revealed in the order that they are returned by
2289
+ * this function, so you can use the index attributes to control the
2290
+ * order of fragment appearance.
2291
+ *
2292
+ * To maintain a sensible default fragment order, fragments are presumed
2293
+ * to be passed in document order. This function adds a "fragment-index"
2294
+ * attribute to each node if such an attribute is not already present,
2295
+ * and sets that attribute to an integer value which is the position of
2296
+ * the fragment within the fragments list.
2100
2297
  */
2101
- function nextFragment() {
2102
-
2103
- if( currentSlide && config.fragments ) {
2104
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
2298
+ function sortFragments( fragments ) {
2105
2299
 
2106
- if( fragments.length ) {
2107
- // Find the index of the next fragment
2108
- var index = fragments[0].getAttribute( 'data-fragment-index' );
2300
+ fragments = toArray( fragments );
2109
2301
 
2110
- // Find all fragments with the same index
2111
- fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
2302
+ var ordered = [],
2303
+ unordered = [],
2304
+ sorted = [];
2112
2305
 
2113
- toArray( fragments ).forEach( function( element ) {
2114
- element.classList.add( 'visible' );
2115
- } );
2306
+ // Group ordered and unordered elements
2307
+ fragments.forEach( function( fragment, i ) {
2308
+ if( fragment.hasAttribute( 'data-fragment-index' ) ) {
2309
+ var index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
2116
2310
 
2117
- // Notify subscribers of the change
2118
- dispatchEvent( 'fragmentshown', { fragment: fragments[0], fragments: fragments } );
2311
+ if( !ordered[index] ) {
2312
+ ordered[index] = [];
2313
+ }
2119
2314
 
2120
- updateControls();
2121
- return true;
2315
+ ordered[index].push( fragment );
2122
2316
  }
2123
- }
2317
+ else {
2318
+ unordered.push( [ fragment ] );
2319
+ }
2320
+ } );
2124
2321
 
2125
- return false;
2322
+ // Append fragments without explicit indices in their
2323
+ // DOM order
2324
+ ordered = ordered.concat( unordered );
2325
+
2326
+ // Manually count the index up per group to ensure there
2327
+ // are no gaps
2328
+ var index = 0;
2329
+
2330
+ // Push all fragments in their sorted order to an array,
2331
+ // this flattens the groups
2332
+ ordered.forEach( function( group ) {
2333
+ group.forEach( function( fragment ) {
2334
+ sorted.push( fragment );
2335
+ fragment.setAttribute( 'data-fragment-index', index );
2336
+ } );
2337
+
2338
+ index ++;
2339
+ } );
2340
+
2341
+ return sorted;
2126
2342
 
2127
2343
  }
2128
2344
 
2129
2345
  /**
2130
- * Navigate to the previous slide fragment.
2346
+ * Navigate to the specified slide fragment.
2131
2347
  *
2132
- * @return {Boolean} true if there was a previous fragment,
2133
- * false otherwise
2348
+ * @param {Number} index The index of the fragment that
2349
+ * should be shown, -1 means all are invisible
2350
+ * @param {Number} offset Integer offset to apply to the
2351
+ * fragment index
2352
+ *
2353
+ * @return {Boolean} true if a change was made in any
2354
+ * fragments visibility as part of this call
2134
2355
  */
2135
- function previousFragment() {
2356
+ function navigateFragment( index, offset ) {
2136
2357
 
2137
2358
  if( currentSlide && config.fragments ) {
2138
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
2139
2359
 
2360
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
2140
2361
  if( fragments.length ) {
2141
- // Find the index of the previous fragment
2142
- var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
2143
2362
 
2144
- // Find all fragments with the same index
2145
- fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
2363
+ // If no index is specified, find the current
2364
+ if( typeof index !== 'number' ) {
2365
+ var lastVisibleFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
2366
+
2367
+ if( lastVisibleFragment ) {
2368
+ index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
2369
+ }
2370
+ else {
2371
+ index = -1;
2372
+ }
2373
+ }
2374
+
2375
+ // If an offset is specified, apply it to the index
2376
+ if( typeof offset === 'number' ) {
2377
+ index += offset;
2378
+ }
2379
+
2380
+ var fragmentsShown = [],
2381
+ fragmentsHidden = [];
2382
+
2383
+ toArray( fragments ).forEach( function( element, i ) {
2384
+
2385
+ if( element.hasAttribute( 'data-fragment-index' ) ) {
2386
+ i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );
2387
+ }
2388
+
2389
+ // Visible fragments
2390
+ if( i <= index ) {
2391
+ if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );
2392
+ element.classList.add( 'visible' );
2393
+ element.classList.remove( 'current-fragment' );
2394
+
2395
+ if( i === index ) {
2396
+ element.classList.add( 'current-fragment' );
2397
+ }
2398
+ }
2399
+ // Hidden fragments
2400
+ else {
2401
+ if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );
2402
+ element.classList.remove( 'visible' );
2403
+ element.classList.remove( 'current-fragment' );
2404
+ }
2405
+
2146
2406
 
2147
- toArray( fragments ).forEach( function( f ) {
2148
- f.classList.remove( 'visible' );
2149
2407
  } );
2150
2408
 
2151
- // Notify subscribers of the change
2152
- dispatchEvent( 'fragmenthidden', { fragment: fragments[0], fragments: fragments } );
2409
+ if( fragmentsHidden.length ) {
2410
+ dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );
2411
+ }
2412
+
2413
+ if( fragmentsShown.length ) {
2414
+ dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );
2415
+ }
2153
2416
 
2154
2417
  updateControls();
2155
- return true;
2418
+
2419
+ return !!( fragmentsShown.length || fragmentsHidden.length );
2420
+
2156
2421
  }
2422
+
2157
2423
  }
2158
2424
 
2159
2425
  return false;
2160
2426
 
2161
2427
  }
2162
2428
 
2429
+ /**
2430
+ * Navigate to the next slide fragment.
2431
+ *
2432
+ * @return {Boolean} true if there was a next fragment,
2433
+ * false otherwise
2434
+ */
2435
+ function nextFragment() {
2436
+
2437
+ return navigateFragment( null, 1 );
2438
+
2439
+ }
2440
+
2441
+ /**
2442
+ * Navigate to the previous slide fragment.
2443
+ *
2444
+ * @return {Boolean} true if there was a previous fragment,
2445
+ * false otherwise
2446
+ */
2447
+ function previousFragment() {
2448
+
2449
+ return navigateFragment( null, -1 );
2450
+
2451
+ }
2452
+
2163
2453
  /**
2164
2454
  * Cues a new automated slide if enabled in the config.
2165
2455
  */
2166
2456
  function cueAutoSlide() {
2167
2457
 
2168
- clearTimeout( autoSlideTimeout );
2458
+ cancelAutoSlide();
2459
+
2460
+ if( currentSlide ) {
2461
+
2462
+ var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
+ var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
+
2465
+ // 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 ) {
2470
+ autoSlide = parseInt( slideAutoSlide, 10 );
2471
+ }
2472
+ else if( parentAutoSlide ) {
2473
+ autoSlide = parseInt( parentAutoSlide, 10 );
2474
+ }
2475
+ else {
2476
+ autoSlide = config.autoSlide;
2477
+ }
2478
+
2479
+ // If there are media elements with data-autoplay,
2480
+ // automatically set the autoSlide duration to the
2481
+ // length of that media
2482
+ toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2483
+ if( el.hasAttribute( 'data-autoplay' ) ) {
2484
+ if( autoSlide && el.duration * 1000 > autoSlide ) {
2485
+ autoSlide = ( el.duration * 1000 ) + 1000;
2486
+ }
2487
+ }
2488
+ } );
2489
+
2490
+ // Cue the next auto-slide if:
2491
+ // - There is an autoSlide value
2492
+ // - Auto-sliding isn't paused by the user
2493
+ // - The presentation isn't paused
2494
+ // - The overview isn't active
2495
+ // - The presentation isn't over
2496
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
2497
+ autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
+ autoSlideStartTime = Date.now();
2499
+ }
2500
+
2501
+ if( autoSlidePlayer ) {
2502
+ autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );
2503
+ }
2169
2504
 
2170
- // Cue the next auto-slide if enabled
2171
- if( autoSlide && !isPaused() && !isOverview() ) {
2172
- autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2173
2505
  }
2174
2506
 
2175
2507
  }
@@ -2180,6 +2512,25 @@ var Reveal = (function(){
2180
2512
  function cancelAutoSlide() {
2181
2513
 
2182
2514
  clearTimeout( autoSlideTimeout );
2515
+ autoSlideTimeout = -1;
2516
+
2517
+ }
2518
+
2519
+ function pauseAutoSlide() {
2520
+
2521
+ autoSlidePaused = true;
2522
+ clearTimeout( autoSlideTimeout );
2523
+
2524
+ if( autoSlidePlayer ) {
2525
+ autoSlidePlayer.setPlaying( false );
2526
+ }
2527
+
2528
+ }
2529
+
2530
+ function resumeAutoSlide() {
2531
+
2532
+ autoSlidePaused = false;
2533
+ cueAutoSlide();
2183
2534
 
2184
2535
  }
2185
2536
 
@@ -2279,14 +2630,25 @@ var Reveal = (function(){
2279
2630
  // ----------------------------- EVENTS -------------------------------//
2280
2631
  // --------------------------------------------------------------------//
2281
2632
 
2633
+ /**
2634
+ * Called by all event handlers that are based on user
2635
+ * input.
2636
+ */
2637
+ function onUserInput( event ) {
2638
+
2639
+ if( config.autoSlideStoppable ) {
2640
+ pauseAutoSlide();
2641
+ }
2642
+
2643
+ }
2282
2644
 
2283
2645
  /**
2284
2646
  * Handler for the document level 'keydown' event.
2285
- *
2286
- * @param {Object} event
2287
2647
  */
2288
2648
  function onDocumentKeyDown( event ) {
2289
2649
 
2650
+ onUserInput( event );
2651
+
2290
2652
  // Check if there's a focused element that could be using
2291
2653
  // the keyboard
2292
2654
  var activeElement = document.activeElement;
@@ -2373,8 +2735,13 @@ var Reveal = (function(){
2373
2735
  event.preventDefault();
2374
2736
  }
2375
2737
  // ESC or O key
2376
- else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && supports3DTransforms ) {
2377
- toggleOverview();
2738
+ else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
+ if( dom.preview ) {
2740
+ closePreview();
2741
+ }
2742
+ else {
2743
+ toggleOverview();
2744
+ }
2378
2745
 
2379
2746
  event.preventDefault();
2380
2747
  }
@@ -2416,6 +2783,8 @@ var Reveal = (function(){
2416
2783
 
2417
2784
  // Each touch should only trigger one action
2418
2785
  if( !touch.captured ) {
2786
+ onUserInput( event );
2787
+
2419
2788
  var currentX = event.touches[0].clientX;
2420
2789
  var currentY = event.touches[0].clientY;
2421
2790
 
@@ -2569,6 +2938,8 @@ var Reveal = (function(){
2569
2938
  */
2570
2939
  function onProgressClicked( event ) {
2571
2940
 
2941
+ onUserInput( event );
2942
+
2572
2943
  event.preventDefault();
2573
2944
 
2574
2945
  var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
@@ -2581,12 +2952,12 @@ var Reveal = (function(){
2581
2952
  /**
2582
2953
  * Event handler for navigation control buttons.
2583
2954
  */
2584
- function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
2585
- function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
2586
- function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
2587
- function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
2588
- function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
2589
- function onNavigateNextClicked( event ) { event.preventDefault(); navigateNext(); }
2955
+ function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
2956
+ function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
2957
+ function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
2958
+ function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
2959
+ function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
2960
+ function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }
2590
2961
 
2591
2962
  /**
2592
2963
  * Handler for the window level 'hashchange' event.
@@ -2606,6 +2977,24 @@ var Reveal = (function(){
2606
2977
 
2607
2978
  }
2608
2979
 
2980
+ /**
2981
+ * Handle for the window level 'visibilitychange' event.
2982
+ */
2983
+ function onPageVisibilityChange( event ) {
2984
+
2985
+ var isHidden = document.webkitHidden ||
2986
+ document.msHidden ||
2987
+ document.hidden;
2988
+
2989
+ // If, after clicking a link or similar and we're coming back,
2990
+ // focus the document.body to ensure we can use keyboard shortcuts
2991
+ if( isHidden === false && document.activeElement !== document.body ) {
2992
+ document.activeElement.blur();
2993
+ document.body.focus();
2994
+ }
2995
+
2996
+ }
2997
+
2609
2998
  /**
2610
2999
  * Invoked when a slide is and we're in the overview.
2611
3000
  */
@@ -2652,6 +3041,191 @@ var Reveal = (function(){
2652
3041
 
2653
3042
  }
2654
3043
 
3044
+ /**
3045
+ * Handles click on the auto-sliding controls element.
3046
+ */
3047
+ function onAutoSlidePlayerClick( event ) {
3048
+
3049
+ // Replay
3050
+ if( Reveal.isLastSlide() && config.loop === false ) {
3051
+ slide( 0, 0 );
3052
+ resumeAutoSlide();
3053
+ }
3054
+ // Resume
3055
+ else if( autoSlidePaused ) {
3056
+ resumeAutoSlide();
3057
+ }
3058
+ // Pause
3059
+ else {
3060
+ pauseAutoSlide();
3061
+ }
3062
+
3063
+ }
3064
+
3065
+
3066
+ // --------------------------------------------------------------------//
3067
+ // ------------------------ PLAYBACK COMPONENT ------------------------//
3068
+ // --------------------------------------------------------------------//
3069
+
3070
+
3071
+ /**
3072
+ * Constructor for the playback component, which displays
3073
+ * play/pause/progress controls.
3074
+ *
3075
+ * @param {HTMLElement} container The component will append
3076
+ * itself to this
3077
+ * @param {Function} progressCheck A method which will be
3078
+ * called frequently to get the current progress on a range
3079
+ * of 0-1
3080
+ */
3081
+ function Playback( container, progressCheck ) {
3082
+
3083
+ // Cosmetics
3084
+ this.diameter = 50;
3085
+ this.thickness = 3;
3086
+
3087
+ // Flags if we are currently playing
3088
+ this.playing = false;
3089
+
3090
+ // Current progress on a 0-1 range
3091
+ this.progress = 0;
3092
+
3093
+ // Used to loop the animation smoothly
3094
+ this.progressOffset = 1;
3095
+
3096
+ this.container = container;
3097
+ this.progressCheck = progressCheck;
3098
+
3099
+ this.canvas = document.createElement( 'canvas' );
3100
+ this.canvas.className = 'playback';
3101
+ this.canvas.width = this.diameter;
3102
+ this.canvas.height = this.diameter;
3103
+ this.context = this.canvas.getContext( '2d' );
3104
+
3105
+ this.container.appendChild( this.canvas );
3106
+
3107
+ this.render();
3108
+
3109
+ }
3110
+
3111
+ Playback.prototype.setPlaying = function( value ) {
3112
+
3113
+ var wasPlaying = this.playing;
3114
+
3115
+ this.playing = value;
3116
+
3117
+ // Start repainting if we weren't already
3118
+ if( !wasPlaying && this.playing ) {
3119
+ this.animate();
3120
+ }
3121
+ else {
3122
+ this.render();
3123
+ }
3124
+
3125
+ };
3126
+
3127
+ Playback.prototype.animate = function() {
3128
+
3129
+ var progressBefore = this.progress;
3130
+
3131
+ this.progress = this.progressCheck();
3132
+
3133
+ // When we loop, offset the progress so that it eases
3134
+ // smoothly rather than immediately resetting
3135
+ if( progressBefore > 0.8 && this.progress < 0.2 ) {
3136
+ this.progressOffset = this.progress;
3137
+ }
3138
+
3139
+ this.render();
3140
+
3141
+ if( this.playing ) {
3142
+ features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );
3143
+ }
3144
+
3145
+ };
3146
+
3147
+ /**
3148
+ * Renders the current progress and playback state.
3149
+ */
3150
+ Playback.prototype.render = function() {
3151
+
3152
+ var progress = this.playing ? this.progress : 0,
3153
+ radius = ( this.diameter / 2 ) - this.thickness,
3154
+ x = this.diameter / 2,
3155
+ y = this.diameter / 2,
3156
+ iconSize = 14;
3157
+
3158
+ // Ease towards 1
3159
+ this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
3160
+
3161
+ var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
3162
+ var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
3163
+
3164
+ this.context.save();
3165
+ this.context.clearRect( 0, 0, this.diameter, this.diameter );
3166
+
3167
+ // Solid background color
3168
+ this.context.beginPath();
3169
+ this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
3170
+ this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
3171
+ this.context.fill();
3172
+
3173
+ // Draw progress track
3174
+ this.context.beginPath();
3175
+ this.context.arc( x, y, radius, 0, Math.PI * 2, false );
3176
+ this.context.lineWidth = this.thickness;
3177
+ this.context.strokeStyle = '#666';
3178
+ this.context.stroke();
3179
+
3180
+ if( this.playing ) {
3181
+ // Draw progress on top of track
3182
+ this.context.beginPath();
3183
+ this.context.arc( x, y, radius, startAngle, endAngle, false );
3184
+ this.context.lineWidth = this.thickness;
3185
+ this.context.strokeStyle = '#fff';
3186
+ this.context.stroke();
3187
+ }
3188
+
3189
+ this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
3190
+
3191
+ // Draw play/pause icons
3192
+ if( this.playing ) {
3193
+ this.context.fillStyle = '#fff';
3194
+ this.context.fillRect( 0, 0, iconSize / 2 - 2, iconSize );
3195
+ this.context.fillRect( iconSize / 2 + 2, 0, iconSize / 2 - 2, iconSize );
3196
+ }
3197
+ else {
3198
+ this.context.beginPath();
3199
+ this.context.translate( 2, 0 );
3200
+ this.context.moveTo( 0, 0 );
3201
+ this.context.lineTo( iconSize - 2, iconSize / 2 );
3202
+ this.context.lineTo( 0, iconSize );
3203
+ this.context.fillStyle = '#fff';
3204
+ this.context.fill();
3205
+ }
3206
+
3207
+ this.context.restore();
3208
+
3209
+ };
3210
+
3211
+ Playback.prototype.on = function( type, listener ) {
3212
+ this.canvas.addEventListener( type, listener, false );
3213
+ };
3214
+
3215
+ Playback.prototype.off = function( type, listener ) {
3216
+ this.canvas.removeEventListener( type, listener, false );
3217
+ };
3218
+
3219
+ Playback.prototype.destroy = function() {
3220
+
3221
+ this.playing = false;
3222
+
3223
+ if( this.canvas.parentNode ) {
3224
+ this.container.removeChild( this.canvas );
3225
+ }
3226
+
3227
+ };
3228
+
2655
3229
 
2656
3230
  // --------------------------------------------------------------------//
2657
3231
  // ------------------------------- API --------------------------------//
@@ -2671,6 +3245,9 @@ var Reveal = (function(){
2671
3245
  down: navigateDown,
2672
3246
  prev: navigatePrev,
2673
3247
  next: navigateNext,
3248
+
3249
+ // Fragment methods
3250
+ navigateFragment: navigateFragment,
2674
3251
  prevFragment: previousFragment,
2675
3252
  nextFragment: nextFragment,
2676
3253
 
@@ -2745,10 +3322,22 @@ var Reveal = (function(){
2745
3322
  getQueryHash: function() {
2746
3323
  var query = {};
2747
3324
 
2748
- location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
3325
+ location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {
2749
3326
  query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
2750
3327
  } );
2751
3328
 
3329
+ // Basic deserialization
3330
+ for( var i in query ) {
3331
+ var value = query[ i ];
3332
+
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 );
3339
+ }
3340
+
2752
3341
  return query;
2753
3342
  },
2754
3343
 
@@ -2759,12 +3348,17 @@ var Reveal = (function(){
2759
3348
 
2760
3349
  // Returns true if we're currently on the last slide
2761
3350
  isLastSlide: function() {
2762
- if( currentSlide && currentSlide.classList.contains( '.stack' ) ) {
2763
- return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
2764
- }
2765
- else {
2766
- return document.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
3351
+ if( currentSlide ) {
3352
+ // Does this slide has next a sibling?
3353
+ if( currentSlide.nextElementSibling ) return false;
3354
+
3355
+ // If it's vertical, does its parent have a next sibling?
3356
+ if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
3357
+
3358
+ return true;
2767
3359
  }
3360
+
3361
+ return false;
2768
3362
  },
2769
3363
 
2770
3364
  // Checks if reveal.js has been loaded and is ready for use