slide_hero 0.0.3 → 0.0.4

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/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