slide_hero 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +28 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +161 -0
  8. data/Rakefile +9 -0
  9. data/bin/slidehero +37 -0
  10. data/config.ru +14 -0
  11. data/lib/slide_hero/code.rb +16 -0
  12. data/lib/slide_hero/dsl.rb +10 -0
  13. data/lib/slide_hero/grouped_slides.rb +21 -0
  14. data/lib/slide_hero/list.rb +29 -0
  15. data/lib/slide_hero/list_point.rb +26 -0
  16. data/lib/slide_hero/point.rb +27 -0
  17. data/lib/slide_hero/presentation.rb +36 -0
  18. data/lib/slide_hero/slide.rb +47 -0
  19. data/lib/slide_hero/version.rb +3 -0
  20. data/lib/slide_hero/views/_footer.html.erb +0 -0
  21. data/lib/slide_hero/views/_header.html.erb +0 -0
  22. data/lib/slide_hero/views/code.html.erb +3 -0
  23. data/lib/slide_hero/views/empty.html +12 -0
  24. data/lib/slide_hero/views/grouped_slides.html.erb +5 -0
  25. data/lib/slide_hero/views/layout.html.erb +54 -0
  26. data/lib/slide_hero/views/ordered_list.html.erb +5 -0
  27. data/lib/slide_hero/views/slide.html.erb +6 -0
  28. data/lib/slide_hero/views/unordered_list.html.erb +5 -0
  29. data/lib/slide_hero.rb +15 -0
  30. data/slide_hero.gemspec +29 -0
  31. data/templates/new_presentation.tt +11 -0
  32. data/test/fixtures/testclass.rb +5 -0
  33. data/test/minitest_helper.rb +18 -0
  34. data/test/slide_hero/code_spec.rb +29 -0
  35. data/test/slide_hero/dsl_spec.rb +27 -0
  36. data/test/slide_hero/grouped_slides_spec.rb +29 -0
  37. data/test/slide_hero/list_point_spec.rb +34 -0
  38. data/test/slide_hero/list_spec.rb +49 -0
  39. data/test/slide_hero/point_spec.rb +25 -0
  40. data/test/slide_hero/presentation_spec.rb +56 -0
  41. data/test/slide_hero/slide_spec.rb +147 -0
  42. data/test/slide_hero_spec.rb +15 -0
  43. data/vendor/reveal.js/.gitignore +6 -0
  44. data/vendor/reveal.js/.travis.yml +5 -0
  45. data/vendor/reveal.js/Gruntfile.js +132 -0
  46. data/vendor/reveal.js/LICENSE +19 -0
  47. data/vendor/reveal.js/README.md +798 -0
  48. data/vendor/reveal.js/css/print/paper.css +176 -0
  49. data/vendor/reveal.js/css/print/pdf.css +190 -0
  50. data/vendor/reveal.js/css/reveal.css +1649 -0
  51. data/vendor/reveal.js/css/reveal.min.css +7 -0
  52. data/vendor/reveal.js/css/theme/README.md +23 -0
  53. data/vendor/reveal.js/css/theme/beige.css +142 -0
  54. data/vendor/reveal.js/css/theme/default.css +142 -0
  55. data/vendor/reveal.js/css/theme/moon.css +142 -0
  56. data/vendor/reveal.js/css/theme/night.css +130 -0
  57. data/vendor/reveal.js/css/theme/serif.css +132 -0
  58. data/vendor/reveal.js/css/theme/simple.css +132 -0
  59. data/vendor/reveal.js/css/theme/sky.css +139 -0
  60. data/vendor/reveal.js/css/theme/solarized.css +142 -0
  61. data/vendor/reveal.js/css/theme/source/beige.scss +50 -0
  62. data/vendor/reveal.js/css/theme/source/default.scss +42 -0
  63. data/vendor/reveal.js/css/theme/source/moon.scss +68 -0
  64. data/vendor/reveal.js/css/theme/source/night.scss +35 -0
  65. data/vendor/reveal.js/css/theme/source/serif.scss +35 -0
  66. data/vendor/reveal.js/css/theme/source/simple.scss +38 -0
  67. data/vendor/reveal.js/css/theme/source/sky.scss +46 -0
  68. data/vendor/reveal.js/css/theme/source/solarized.scss +74 -0
  69. data/vendor/reveal.js/css/theme/template/mixins.scss +29 -0
  70. data/vendor/reveal.js/css/theme/template/settings.scss +34 -0
  71. data/vendor/reveal.js/css/theme/template/theme.scss +163 -0
  72. data/vendor/reveal.js/examples/assets/image1.png +0 -0
  73. data/vendor/reveal.js/examples/assets/image2.png +0 -0
  74. data/vendor/reveal.js/examples/barebones.html +42 -0
  75. data/vendor/reveal.js/examples/embedded-media.html +49 -0
  76. data/vendor/reveal.js/examples/math.html +185 -0
  77. data/vendor/reveal.js/examples/slide-backgrounds.html +101 -0
  78. data/vendor/reveal.js/js/reveal.js +2788 -0
  79. data/vendor/reveal.js/js/reveal.min.js +8 -0
  80. data/vendor/reveal.js/lib/css/zenburn.css +115 -0
  81. data/vendor/reveal.js/lib/font/league_gothic-webfont.eot +0 -0
  82. data/vendor/reveal.js/lib/font/league_gothic-webfont.svg +230 -0
  83. data/vendor/reveal.js/lib/font/league_gothic-webfont.ttf +0 -0
  84. data/vendor/reveal.js/lib/font/league_gothic-webfont.woff +0 -0
  85. data/vendor/reveal.js/lib/font/league_gothic_license +2 -0
  86. data/vendor/reveal.js/lib/js/classList.js +2 -0
  87. data/vendor/reveal.js/lib/js/head.min.js +8 -0
  88. data/vendor/reveal.js/lib/js/html5shiv.js +7 -0
  89. data/vendor/reveal.js/package.json +45 -0
  90. data/vendor/reveal.js/plugin/highlight/highlight.js +31 -0
  91. data/vendor/reveal.js/plugin/leap/leap.js +157 -0
  92. data/vendor/reveal.js/plugin/markdown/example.html +98 -0
  93. data/vendor/reveal.js/plugin/markdown/example.md +31 -0
  94. data/vendor/reveal.js/plugin/markdown/markdown.js +220 -0
  95. data/vendor/reveal.js/plugin/markdown/marked.js +37 -0
  96. data/vendor/reveal.js/plugin/math/math.js +64 -0
  97. data/vendor/reveal.js/plugin/multiplex/client.js +13 -0
  98. data/vendor/reveal.js/plugin/multiplex/index.js +56 -0
  99. data/vendor/reveal.js/plugin/multiplex/master.js +50 -0
  100. data/vendor/reveal.js/plugin/notes/notes.html +259 -0
  101. data/vendor/reveal.js/plugin/notes/notes.js +78 -0
  102. data/vendor/reveal.js/plugin/notes-server/client.js +57 -0
  103. data/vendor/reveal.js/plugin/notes-server/index.js +59 -0
  104. data/vendor/reveal.js/plugin/notes-server/notes.html +142 -0
  105. data/vendor/reveal.js/plugin/postmessage/example.html +39 -0
  106. data/vendor/reveal.js/plugin/postmessage/postmessage.js +42 -0
  107. data/vendor/reveal.js/plugin/print-pdf/print-pdf.js +44 -0
  108. data/vendor/reveal.js/plugin/remotes/remotes.js +39 -0
  109. data/vendor/reveal.js/plugin/search/search.js +196 -0
  110. data/vendor/reveal.js/plugin/zoom-js/zoom.js +256 -0
  111. metadata +277 -0
@@ -0,0 +1,2788 @@
1
+ /*!
2
+ * reveal.js
3
+ * http://lab.hakim.se/reveal-js
4
+ * MIT licensed
5
+ *
6
+ * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
7
+ */
8
+ var Reveal = (function(){
9
+
10
+ 'use strict';
11
+
12
+ var SLIDES_SELECTOR = '.reveal .slides section',
13
+ HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
+ VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
+ HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-child',
16
+
17
+ // Configurations defaults, can be overridden at initialization time
18
+ config = {
19
+
20
+ // The "normal" size of the presentation, aspect ratio will be preserved
21
+ // when the presentation is scaled to fit different resolutions
22
+ width: 960,
23
+ height: 700,
24
+
25
+ // Factor of the display size that should remain empty around the content
26
+ margin: 0.1,
27
+
28
+ // Bounds for smallest/largest possible scale to apply to content
29
+ minScale: 0.2,
30
+ maxScale: 1.0,
31
+
32
+ // Display controls in the bottom right corner
33
+ controls: true,
34
+
35
+ // Display a presentation progress bar
36
+ progress: true,
37
+
38
+ // Push each slide change to the browser history
39
+ history: false,
40
+
41
+ // Enable keyboard shortcuts for navigation
42
+ keyboard: true,
43
+
44
+ // Enable the slide overview mode
45
+ overview: true,
46
+
47
+ // Vertical centring of slides
48
+ center: true,
49
+
50
+ // Enables touch navigation on devices with touch input
51
+ touch: true,
52
+
53
+ // Loop the presentation
54
+ loop: false,
55
+
56
+ // Change the presentation direction to be RTL
57
+ rtl: false,
58
+
59
+ // Turns fragments on and off globally
60
+ fragments: true,
61
+
62
+ // Flags if the presentation is running in an embedded mode,
63
+ // i.e. contained within a limited portion of the screen
64
+ embedded: false,
65
+
66
+ // Number of milliseconds between automatically proceeding to the
67
+ // next slide, disabled when set to 0, this value can be overwritten
68
+ // by using a data-autoslide attribute on your slides
69
+ autoSlide: 0,
70
+
71
+ // Enable slide navigation via mouse wheel
72
+ mouseWheel: false,
73
+
74
+ // Apply a 3D roll to links on hover
75
+ rollingLinks: false,
76
+
77
+ // Opens links in an iframe preview overlay
78
+ previewLinks: false,
79
+
80
+ // Theme (see /css/theme)
81
+ theme: null,
82
+
83
+ // Transition style
84
+ transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
85
+
86
+ // Transition speed
87
+ transitionSpeed: 'default', // default/fast/slow
88
+
89
+ // Transition style for full page slide backgrounds
90
+ backgroundTransition: 'default', // default/linear/none
91
+
92
+ // Number of slides away from the current that are visible
93
+ viewDistance: 3,
94
+
95
+ // Script dependencies to load
96
+ dependencies: []
97
+ },
98
+
99
+ // Flags if reveal.js is loaded (has dispatched the 'ready' event)
100
+ loaded = false,
101
+
102
+ // The current auto-slide duration
103
+ autoSlide = 0,
104
+
105
+ // The horizontal and vertical index of the currently active slide
106
+ indexh,
107
+ indexv,
108
+
109
+ // The previous and current slide HTML elements
110
+ previousSlide,
111
+ currentSlide,
112
+
113
+ // Slides may hold a data-state attribute which we pick up and apply
114
+ // as a class to the body. This list contains the combined state of
115
+ // all current slides.
116
+ state = [],
117
+
118
+ // The current scale of the presentation (see width/height config)
119
+ scale = 1,
120
+
121
+ // Cached references to DOM elements
122
+ dom = {},
123
+
124
+ // Client support for CSS 3D transforms, see #checkCapabilities()
125
+ supports3DTransforms,
126
+
127
+ // Client support for CSS 2D transforms, see #checkCapabilities()
128
+ supports2DTransforms,
129
+
130
+ // Client is a mobile device, see #checkCapabilities()
131
+ isMobileDevice,
132
+
133
+ // Throttles mouse wheel navigation
134
+ lastMouseWheelStep = 0,
135
+
136
+ // An interval used to automatically move on to the next slide
137
+ autoSlideTimeout = 0,
138
+
139
+ // Delays updates to the URL due to a Chrome thumbnailer bug
140
+ writeURLTimeout = 0,
141
+
142
+ // A delay used to activate the overview mode
143
+ activateOverviewTimeout = 0,
144
+
145
+ // A delay used to deactivate the overview mode
146
+ deactivateOverviewTimeout = 0,
147
+
148
+ // Flags if the interaction event listeners are bound
149
+ eventsAreBound = false,
150
+
151
+ // Holds information about the currently ongoing touch input
152
+ touch = {
153
+ startX: 0,
154
+ startY: 0,
155
+ startSpan: 0,
156
+ startCount: 0,
157
+ captured: false,
158
+ threshold: 40
159
+ };
160
+
161
+ /**
162
+ * Starts up the presentation if the client is capable.
163
+ */
164
+ function initialize( options ) {
165
+
166
+ checkCapabilities();
167
+
168
+ if( !supports2DTransforms && !supports3DTransforms ) {
169
+ document.body.setAttribute( 'class', 'no-transforms' );
170
+
171
+ // If the browser doesn't support core features we won't be
172
+ // using JavaScript to control the presentation
173
+ return;
174
+ }
175
+
176
+ // Force a layout when the whole page, incl fonts, has loaded
177
+ window.addEventListener( 'load', layout, false );
178
+
179
+ // Copy options over to our config object
180
+ extend( config, options );
181
+
182
+ // Hide the address bar in mobile browsers
183
+ hideAddressBar();
184
+
185
+ // Loads the dependencies and continues to #start() once done
186
+ load();
187
+
188
+ }
189
+
190
+ /**
191
+ * Inspect the client to see what it's capable of, this
192
+ * should only happens once per runtime.
193
+ */
194
+ function checkCapabilities() {
195
+
196
+ supports3DTransforms = 'WebkitPerspective' in document.body.style ||
197
+ 'MozPerspective' in document.body.style ||
198
+ 'msPerspective' in document.body.style ||
199
+ 'OPerspective' in document.body.style ||
200
+ 'perspective' in document.body.style;
201
+
202
+ supports2DTransforms = 'WebkitTransform' in document.body.style ||
203
+ 'MozTransform' in document.body.style ||
204
+ 'msTransform' in document.body.style ||
205
+ 'OTransform' in document.body.style ||
206
+ 'transform' in document.body.style;
207
+
208
+ isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
209
+
210
+ }
211
+
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
+ */
219
+ function load() {
220
+
221
+ var scripts = [],
222
+ scriptsAsync = [];
223
+
224
+ for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
225
+ var s = config.dependencies[i];
226
+
227
+ // Load if there's no condition or the condition is truthy
228
+ if( !s.condition || s.condition() ) {
229
+ if( s.async ) {
230
+ scriptsAsync.push( s.src );
231
+ }
232
+ else {
233
+ scripts.push( s.src );
234
+ }
235
+
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
+ }
240
+ }
241
+ }
242
+
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
+ if( scripts.length ) {
254
+ head.ready( proceed );
255
+
256
+ // Load synchronous scripts
257
+ head.js.apply( null, scripts );
258
+ }
259
+ else {
260
+ proceed();
261
+ }
262
+
263
+ }
264
+
265
+ /**
266
+ * Starts up reveal.js by binding input events and navigating
267
+ * to the current URL deeplink if there is one.
268
+ */
269
+ function start() {
270
+
271
+ // Make sure we've got all the DOM elements we need
272
+ setupDOM();
273
+
274
+ // Decorate the slide DOM elements with state classes (past/future)
275
+ setupSlides();
276
+
277
+ // Updates the presentation to match the current configuration values
278
+ configure();
279
+
280
+ // Read the initial hash
281
+ readURL();
282
+
283
+ // Notify listeners that the presentation is ready but use a 1ms
284
+ // timeout to ensure it's not fired synchronously after #initialize()
285
+ setTimeout( function() {
286
+ // Enable transitions now that we're loaded
287
+ dom.slides.classList.remove( 'no-transition' );
288
+
289
+ loaded = true;
290
+
291
+ dispatchEvent( 'ready', {
292
+ 'indexh': indexh,
293
+ 'indexv': indexv,
294
+ 'currentSlide': currentSlide
295
+ } );
296
+ }, 1 );
297
+
298
+ }
299
+
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
+ /**
321
+ * Finds and stores references to DOM elements which are
322
+ * required by the presentation. If a required element is
323
+ * not found, it is created.
324
+ */
325
+ function setupDOM() {
326
+
327
+ // Cache references to key DOM elements
328
+ dom.theme = document.querySelector( '#theme' );
329
+ dom.wrapper = document.querySelector( '.reveal' );
330
+ dom.slides = document.querySelector( '.reveal .slides' );
331
+
332
+ // Prevent transitions while we're loading
333
+ dom.slides.classList.add( 'no-transition' );
334
+
335
+ // Background element
336
+ dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
337
+
338
+ // Progress bar
339
+ dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );
340
+ dom.progressbar = dom.progress.querySelector( 'span' );
341
+
342
+ // Arrow controls
343
+ createSingletonNode( dom.wrapper, 'aside', 'controls',
344
+ '<div class="navigate-left"></div>' +
345
+ '<div class="navigate-right"></div>' +
346
+ '<div class="navigate-up"></div>' +
347
+ '<div class="navigate-down"></div>' );
348
+
349
+ // State background element [DEPRECATED]
350
+ createSingletonNode( dom.wrapper, 'div', 'state-background', null );
351
+
352
+ // Overlay graphic which is displayed during the paused mode
353
+ createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
354
+
355
+ // Cache references to elements
356
+ if ( config.controls ) {
357
+ dom.controls = document.querySelector( '.reveal .controls' );
358
+
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
+ }
367
+
368
+ }
369
+
370
+ /**
371
+ * Creates an HTML element and returns a reference to it.
372
+ * If the element already exists the existing instance will
373
+ * be returned.
374
+ */
375
+ function createSingletonNode( container, tagname, classname, innerHTML ) {
376
+
377
+ var node = container.querySelector( '.' + classname );
378
+ if( !node ) {
379
+ node = document.createElement( tagname );
380
+ node.classList.add( classname );
381
+ if( innerHTML !== null ) {
382
+ node.innerHTML = innerHTML;
383
+ }
384
+ container.appendChild( node );
385
+ }
386
+ return node;
387
+
388
+ }
389
+
390
+ /**
391
+ * Creates the slide background elements and appends them
392
+ * to the background container. One element is created per
393
+ * slide no matter if the given slide has visible background.
394
+ */
395
+ function createBackgrounds() {
396
+
397
+ if( isPrintingPDF() ) {
398
+ document.body.classList.add( 'print-pdf' );
399
+ }
400
+
401
+ // Clear prior backgrounds
402
+ dom.background.innerHTML = '';
403
+ dom.background.classList.add( 'no-transition' );
404
+
405
+ // Helper method for creating a background element for the
406
+ // given slide
407
+ function _createBackground( slide, container ) {
408
+
409
+ var data = {
410
+ background: slide.getAttribute( 'data-background' ),
411
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
412
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
413
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
414
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
415
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
416
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
417
+ };
418
+
419
+ var element = document.createElement( 'div' );
420
+ element.className = 'slide-background';
421
+
422
+ if( data.background ) {
423
+ // Auto-wrap image urls in url(...)
424
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
425
+ element.style.backgroundImage = 'url('+ data.background +')';
426
+ }
427
+ else {
428
+ element.style.background = data.background;
429
+ }
430
+ }
431
+
432
+ // Additional and optional background properties
433
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
434
+ if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
435
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
436
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
437
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
438
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
439
+
440
+ container.appendChild( element );
441
+
442
+ return element;
443
+
444
+ }
445
+
446
+ // Iterate over all horizontal slides
447
+ toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
448
+
449
+ var backgroundStack;
450
+
451
+ if( isPrintingPDF() ) {
452
+ backgroundStack = _createBackground( slideh, slideh );
453
+ }
454
+ else {
455
+ backgroundStack = _createBackground( slideh, dom.background );
456
+ }
457
+
458
+ // Iterate over all vertical slides
459
+ toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
460
+
461
+ if( isPrintingPDF() ) {
462
+ _createBackground( slidev, slidev );
463
+ }
464
+ else {
465
+ _createBackground( slidev, backgroundStack );
466
+ }
467
+
468
+ } );
469
+
470
+ } );
471
+
472
+ }
473
+
474
+ /**
475
+ * Applies the configuration settings from the config
476
+ * object. May be called multiple times.
477
+ */
478
+ function configure( options ) {
479
+
480
+ dom.wrapper.classList.remove( config.transition );
481
+
482
+ // New config options may be passed when this method
483
+ // is invoked through the API after initialization
484
+ if( typeof options === 'object' ) extend( config, options );
485
+
486
+ // Force linear transition based on browser capabilities
487
+ if( supports3DTransforms === false ) config.transition = 'linear';
488
+
489
+ dom.wrapper.classList.add( config.transition );
490
+
491
+ dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
492
+ dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
493
+
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
+ }
501
+
502
+ if( config.rtl ) {
503
+ dom.wrapper.classList.add( 'rtl' );
504
+ }
505
+ else {
506
+ dom.wrapper.classList.remove( 'rtl' );
507
+ }
508
+
509
+ if( config.center ) {
510
+ dom.wrapper.classList.add( 'center' );
511
+ }
512
+ else {
513
+ dom.wrapper.classList.remove( 'center' );
514
+ }
515
+
516
+ if( config.mouseWheel ) {
517
+ document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
518
+ document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
519
+ }
520
+ else {
521
+ document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
522
+ document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
523
+ }
524
+
525
+ // Rolling 3D links
526
+ if( config.rollingLinks ) {
527
+ enableRollingLinks();
528
+ }
529
+ else {
530
+ disableRollingLinks();
531
+ }
532
+
533
+ // Iframe link previews
534
+ if( config.previewLinks ) {
535
+ enablePreviewLinks();
536
+ }
537
+ else {
538
+ disablePreviewLinks();
539
+ enablePreviewLinks( '[data-preview-link]' );
540
+ }
541
+
542
+ // Load the theme in the config, if it's not already loaded
543
+ if( config.theme && dom.theme ) {
544
+ var themeURL = dom.theme.getAttribute( 'href' );
545
+ var themeFinder = /[^\/]*?(?=\.css)/;
546
+ var themeName = themeURL.match(themeFinder)[0];
547
+
548
+ if( config.theme !== themeName ) {
549
+ themeURL = themeURL.replace(themeFinder, config.theme);
550
+ dom.theme.setAttribute( 'href', themeURL );
551
+ }
552
+ }
553
+
554
+ sync();
555
+
556
+ }
557
+
558
+ /**
559
+ * Binds all event listeners.
560
+ */
561
+ function addEventListeners() {
562
+
563
+ eventsAreBound = true;
564
+
565
+ window.addEventListener( 'hashchange', onWindowHashChange, false );
566
+ window.addEventListener( 'resize', onWindowResize, false );
567
+
568
+ if( config.touch ) {
569
+ dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
570
+ dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
571
+ dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
572
+
573
+ // Support pointer-style touch interaction as well
574
+ if( window.navigator.msPointerEnabled ) {
575
+ dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
576
+ dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
577
+ dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
578
+ }
579
+ }
580
+
581
+ if( config.keyboard ) {
582
+ document.addEventListener( 'keydown', onDocumentKeyDown, false );
583
+ }
584
+
585
+ if ( config.progress && dom.progress ) {
586
+ dom.progress.addEventListener( 'click', onProgressClicked, false );
587
+ }
588
+
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
+ } );
598
+ }
599
+
600
+ }
601
+
602
+ /**
603
+ * Unbinds all event listeners.
604
+ */
605
+ function removeEventListeners() {
606
+
607
+ eventsAreBound = false;
608
+
609
+ document.removeEventListener( 'keydown', onDocumentKeyDown, false );
610
+ window.removeEventListener( 'hashchange', onWindowHashChange, false );
611
+ window.removeEventListener( 'resize', onWindowResize, false );
612
+
613
+ dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
614
+ dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
615
+ dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
616
+
617
+ if( window.navigator.msPointerEnabled ) {
618
+ dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
619
+ dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
620
+ dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
621
+ }
622
+
623
+ if ( config.progress && dom.progress ) {
624
+ dom.progress.removeEventListener( 'click', onProgressClicked, false );
625
+ }
626
+
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
+ }
637
+
638
+ }
639
+
640
+ /**
641
+ * Extend object a with the properties of object b.
642
+ * If there's a conflict, object b takes precedence.
643
+ */
644
+ function extend( a, b ) {
645
+
646
+ for( var i in b ) {
647
+ a[ i ] = b[ i ];
648
+ }
649
+
650
+ }
651
+
652
+ /**
653
+ * Converts the target object to an array.
654
+ */
655
+ function toArray( o ) {
656
+
657
+ return Array.prototype.slice.call( o );
658
+
659
+ }
660
+
661
+ /**
662
+ * Measures the distance in pixels between point a
663
+ * and point b.
664
+ *
665
+ * @param {Object} a point with x/y properties
666
+ * @param {Object} b point with x/y properties
667
+ */
668
+ function distanceBetween( a, b ) {
669
+
670
+ var dx = a.x - b.x,
671
+ dy = a.y - b.y;
672
+
673
+ return Math.sqrt( dx*dx + dy*dy );
674
+
675
+ }
676
+
677
+ /**
678
+ * Applies a CSS transform to the target element.
679
+ */
680
+ function transformElement( element, transform ) {
681
+
682
+ element.style.WebkitTransform = transform;
683
+ element.style.MozTransform = transform;
684
+ element.style.msTransform = transform;
685
+ element.style.OTransform = transform;
686
+ element.style.transform = transform;
687
+
688
+ }
689
+
690
+ /**
691
+ * Retrieves the height of the given element by looking
692
+ * at the position and height of its immediate children.
693
+ */
694
+ function getAbsoluteHeight( element ) {
695
+
696
+ var height = 0;
697
+
698
+ if( element ) {
699
+ var absoluteChildren = 0;
700
+
701
+ toArray( element.childNodes ).forEach( function( child ) {
702
+
703
+ if( typeof child.offsetTop === 'number' && child.style ) {
704
+ // Count # of abs children
705
+ if( child.style.position === 'absolute' ) {
706
+ absoluteChildren += 1;
707
+ }
708
+
709
+ height = Math.max( height, child.offsetTop + child.offsetHeight );
710
+ }
711
+
712
+ } );
713
+
714
+ // If there are no absolute children, use offsetHeight
715
+ if( absoluteChildren === 0 ) {
716
+ height = element.offsetHeight;
717
+ }
718
+
719
+ }
720
+
721
+ return height;
722
+
723
+ }
724
+
725
+ /**
726
+ * Returns the remaining height within the parent of the
727
+ * target element after subtracting the height of all
728
+ * siblings.
729
+ *
730
+ * remaining height = [parent height] - [ siblings height]
731
+ */
732
+ function getRemainingHeight( element, height ) {
733
+
734
+ height = height || 0;
735
+
736
+ if( element ) {
737
+ var parent = element.parentNode;
738
+ var siblings = parent.childNodes;
739
+
740
+ // Subtract the height of each sibling
741
+ toArray( siblings ).forEach( function( sibling ) {
742
+
743
+ if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
744
+
745
+ var styles = window.getComputedStyle( sibling ),
746
+ marginTop = parseInt( styles.marginTop, 10 ),
747
+ marginBottom = parseInt( styles.marginBottom, 10 );
748
+
749
+ height -= sibling.offsetHeight + marginTop + marginBottom;
750
+
751
+ }
752
+
753
+ } );
754
+
755
+ var elementStyles = window.getComputedStyle( element );
756
+
757
+ // Subtract the margins of the target element
758
+ height -= parseInt( elementStyles.marginTop, 10 ) +
759
+ parseInt( elementStyles.marginBottom, 10 );
760
+
761
+ }
762
+
763
+ return height;
764
+
765
+ }
766
+
767
+ /**
768
+ * Checks if this instance is being used to print a PDF.
769
+ */
770
+ function isPrintingPDF() {
771
+
772
+ return ( /print-pdf/gi ).test( window.location.search );
773
+
774
+ }
775
+
776
+ /**
777
+ * Hides the address bar if we're on a mobile device.
778
+ */
779
+ function hideAddressBar() {
780
+
781
+ if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
782
+ // Events that should trigger the address bar to hide
783
+ window.addEventListener( 'load', removeAddressBar, false );
784
+ window.addEventListener( 'orientationchange', removeAddressBar, false );
785
+ }
786
+
787
+ }
788
+
789
+ /**
790
+ * Causes the address bar to hide on mobile devices,
791
+ * more vertical space ftw.
792
+ */
793
+ function removeAddressBar() {
794
+
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
+ setTimeout( function() {
805
+ window.scrollTo( 0, 1 );
806
+ }, 10 );
807
+
808
+ }
809
+
810
+ /**
811
+ * Dispatches an event of the specified type from the
812
+ * reveal DOM element.
813
+ */
814
+ function dispatchEvent( type, properties ) {
815
+
816
+ var event = document.createEvent( "HTMLEvents", 1, 2 );
817
+ event.initEvent( type, true, true );
818
+ extend( event, properties );
819
+ dom.wrapper.dispatchEvent( event );
820
+
821
+ }
822
+
823
+ /**
824
+ * Wrap all links in 3D goodness.
825
+ */
826
+ function enableRollingLinks() {
827
+
828
+ if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
829
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
830
+
831
+ for( var i = 0, len = anchors.length; i < len; i++ ) {
832
+ var anchor = anchors[i];
833
+
834
+ if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
835
+ var span = document.createElement('span');
836
+ span.setAttribute('data-title', anchor.text);
837
+ span.innerHTML = anchor.innerHTML;
838
+
839
+ anchor.classList.add( 'roll' );
840
+ anchor.innerHTML = '';
841
+ anchor.appendChild(span);
842
+ }
843
+ }
844
+ }
845
+
846
+ }
847
+
848
+ /**
849
+ * Unwrap all 3D links.
850
+ */
851
+ function disableRollingLinks() {
852
+
853
+ var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
854
+
855
+ for( var i = 0, len = anchors.length; i < len; i++ ) {
856
+ var anchor = anchors[i];
857
+ var span = anchor.querySelector( 'span' );
858
+
859
+ if( span ) {
860
+ anchor.classList.remove( 'roll' );
861
+ anchor.innerHTML = span.innerHTML;
862
+ }
863
+ }
864
+
865
+ }
866
+
867
+ /**
868
+ * Bind preview frame links.
869
+ */
870
+ function enablePreviewLinks( selector ) {
871
+
872
+ var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
873
+
874
+ anchors.forEach( function( element ) {
875
+ if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
876
+ element.addEventListener( 'click', onPreviewLinkClicked, false );
877
+ }
878
+ } );
879
+
880
+ }
881
+
882
+ /**
883
+ * Unbind preview frame links.
884
+ */
885
+ function disablePreviewLinks() {
886
+
887
+ var anchors = toArray( document.querySelectorAll( 'a' ) );
888
+
889
+ anchors.forEach( function( element ) {
890
+ if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
891
+ element.removeEventListener( 'click', onPreviewLinkClicked, false );
892
+ }
893
+ } );
894
+
895
+ }
896
+
897
+ /**
898
+ * Opens a preview window for the target URL.
899
+ */
900
+ function openPreview( url ) {
901
+
902
+ closePreview();
903
+
904
+ dom.preview = document.createElement( 'div' );
905
+ dom.preview.classList.add( 'preview-link-overlay' );
906
+ dom.wrapper.appendChild( dom.preview );
907
+
908
+ dom.preview.innerHTML = [
909
+ '<header>',
910
+ '<a class="close" href="#"><span class="icon"></span></a>',
911
+ '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
912
+ '</header>',
913
+ '<div class="spinner"></div>',
914
+ '<div class="viewport">',
915
+ '<iframe src="'+ url +'"></iframe>',
916
+ '</div>'
917
+ ].join('');
918
+
919
+ dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
920
+ dom.preview.classList.add( 'loaded' );
921
+ }, false );
922
+
923
+ dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
924
+ closePreview();
925
+ event.preventDefault();
926
+ }, false );
927
+
928
+ dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
929
+ closePreview();
930
+ }, false );
931
+
932
+ setTimeout( function() {
933
+ dom.preview.classList.add( 'visible' );
934
+ }, 1 );
935
+
936
+ }
937
+
938
+ /**
939
+ * Closes the iframe preview window.
940
+ */
941
+ function closePreview() {
942
+
943
+ if( dom.preview ) {
944
+ dom.preview.setAttribute( 'src', '' );
945
+ dom.preview.parentNode.removeChild( dom.preview );
946
+ dom.preview = null;
947
+ }
948
+
949
+ }
950
+
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
+ /**
984
+ * Applies JavaScript-controlled layout rules to the
985
+ * presentation.
986
+ */
987
+ function layout() {
988
+
989
+ if( dom.wrapper && !isPrintingPDF() ) {
990
+
991
+ // Available space to scale within
992
+ var availableWidth = dom.wrapper.offsetWidth,
993
+ availableHeight = dom.wrapper.offsetHeight;
994
+
995
+ // Reduce available space by margin
996
+ availableWidth -= ( availableHeight * config.margin );
997
+ availableHeight -= ( availableHeight * config.margin );
998
+
999
+ // Dimensions of the content
1000
+ var slideWidth = config.width,
1001
+ slideHeight = config.height,
1002
+ slidePadding = 20; // TODO Dig this out of DOM
1003
+
1004
+ // Layout the contents of the slides
1005
+ layoutSlideContents( config.width, config.height, slidePadding );
1006
+
1007
+ // Slide width may be a percentage of available width
1008
+ if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
1009
+ slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
1010
+ }
1011
+
1012
+ // Slide height may be a percentage of available height
1013
+ if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
1014
+ slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
1015
+ }
1016
+
1017
+ dom.slides.style.width = slideWidth + 'px';
1018
+ dom.slides.style.height = slideHeight + 'px';
1019
+
1020
+ // Determine scale of content to fit within available space
1021
+ scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1022
+
1023
+ // Respect max/min scale settings
1024
+ scale = Math.max( scale, config.minScale );
1025
+ scale = Math.min( scale, config.maxScale );
1026
+
1027
+ // Prefer applying scale via zoom since Chrome blurs scaled content
1028
+ // with nested transforms
1029
+ if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
1030
+ dom.slides.style.zoom = scale;
1031
+ }
1032
+ // Apply scale transform as a fallback
1033
+ else {
1034
+ transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1035
+ }
1036
+
1037
+ // Select all slides, vertical and horizontal
1038
+ var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1039
+
1040
+ for( var i = 0, len = slides.length; i < len; i++ ) {
1041
+ var slide = slides[ i ];
1042
+
1043
+ // Don't bother updating invisible slides
1044
+ if( slide.style.display === 'none' ) {
1045
+ continue;
1046
+ }
1047
+
1048
+ if( config.center ) {
1049
+ // Vertical stacks are not centred since their section
1050
+ // children will be
1051
+ if( slide.classList.contains( 'stack' ) ) {
1052
+ slide.style.top = 0;
1053
+ }
1054
+ else {
1055
+ slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1056
+ }
1057
+ }
1058
+ else {
1059
+ slide.style.top = '';
1060
+ }
1061
+
1062
+ }
1063
+
1064
+ updateProgress();
1065
+
1066
+ }
1067
+
1068
+ }
1069
+
1070
+ /**
1071
+ * Applies layout logic to the contents of all slides in
1072
+ * the presentation.
1073
+ */
1074
+ function layoutSlideContents( width, height, padding ) {
1075
+
1076
+ // Handle sizing of elements with the 'stretch' class
1077
+ toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1078
+
1079
+ // Determine how much vertical space we can use
1080
+ var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1081
+
1082
+ // Consider the aspect ratio of media elements
1083
+ if( /(img|video)/gi.test( element.nodeName ) ) {
1084
+ var nw = element.naturalWidth || element.videoWidth,
1085
+ nh = element.naturalHeight || element.videoHeight;
1086
+
1087
+ var es = Math.min( width / nw, remainingHeight / nh );
1088
+
1089
+ element.style.width = ( nw * es ) + 'px';
1090
+ element.style.height = ( nh * es ) + 'px';
1091
+
1092
+ }
1093
+ else {
1094
+ element.style.width = width + 'px';
1095
+ element.style.height = remainingHeight + 'px';
1096
+ }
1097
+
1098
+ } );
1099
+
1100
+ }
1101
+
1102
+ /**
1103
+ * Stores the vertical index of a stack so that the same
1104
+ * vertical slide can be selected when navigating to and
1105
+ * from the stack.
1106
+ *
1107
+ * @param {HTMLElement} stack The vertical stack element
1108
+ * @param {int} v Index to memorize
1109
+ */
1110
+ function setPreviousVerticalIndex( stack, v ) {
1111
+
1112
+ if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
1113
+ stack.setAttribute( 'data-previous-indexv', v || 0 );
1114
+ }
1115
+
1116
+ }
1117
+
1118
+ /**
1119
+ * Retrieves the vertical index which was stored using
1120
+ * #setPreviousVerticalIndex() or 0 if no previous index
1121
+ * exists.
1122
+ *
1123
+ * @param {HTMLElement} stack The vertical stack element
1124
+ */
1125
+ function getPreviousVerticalIndex( stack ) {
1126
+
1127
+ if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
1128
+ // Prefer manually defined start-indexv
1129
+ var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
1130
+
1131
+ return parseInt( stack.getAttribute( attributeName ) || 0, 10 );
1132
+ }
1133
+
1134
+ return 0;
1135
+
1136
+ }
1137
+
1138
+ /**
1139
+ * Displays the overview of slides (quick nav) by
1140
+ * scaling down and arranging all slide elements.
1141
+ *
1142
+ * Experimental feature, might be dropped if perf
1143
+ * can't be improved.
1144
+ */
1145
+ function activateOverview() {
1146
+
1147
+ // Only proceed if enabled in config
1148
+ if( config.overview ) {
1149
+
1150
+ // Don't auto-slide while in overview mode
1151
+ cancelAutoSlide();
1152
+
1153
+ var wasActive = dom.wrapper.classList.contains( 'overview' );
1154
+
1155
+ // Vary the depth of the overview based on screen size
1156
+ var depth = window.innerWidth < 400 ? 1000 : 2500;
1157
+
1158
+ dom.wrapper.classList.add( 'overview' );
1159
+ dom.wrapper.classList.remove( 'exit-overview' );
1160
+
1161
+ clearTimeout( activateOverviewTimeout );
1162
+ clearTimeout( deactivateOverviewTimeout );
1163
+
1164
+ // Not the pretties solution, but need to let the overview
1165
+ // class apply first so that slides are measured accurately
1166
+ // before we can position them
1167
+ activateOverviewTimeout = setTimeout( function(){
1168
+
1169
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1170
+
1171
+ for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1172
+ var hslide = horizontalSlides[i],
1173
+ hoffset = config.rtl ? -105 : 105;
1174
+
1175
+ hslide.setAttribute( 'data-index-h', i );
1176
+
1177
+ // Apply CSS transform
1178
+ transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1179
+
1180
+ if( hslide.classList.contains( 'stack' ) ) {
1181
+
1182
+ var verticalSlides = hslide.querySelectorAll( 'section' );
1183
+
1184
+ for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1185
+ var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1186
+
1187
+ var vslide = verticalSlides[j];
1188
+
1189
+ vslide.setAttribute( 'data-index-h', i );
1190
+ vslide.setAttribute( 'data-index-v', j );
1191
+
1192
+ // Apply CSS transform
1193
+ transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1194
+
1195
+ // Navigate to this slide on click
1196
+ vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1197
+ }
1198
+
1199
+ }
1200
+ else {
1201
+
1202
+ // Navigate to this slide on click
1203
+ hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1204
+
1205
+ }
1206
+ }
1207
+
1208
+ updateSlidesVisibility();
1209
+
1210
+ layout();
1211
+
1212
+ if( !wasActive ) {
1213
+ // Notify observers of the overview showing
1214
+ dispatchEvent( 'overviewshown', {
1215
+ 'indexh': indexh,
1216
+ 'indexv': indexv,
1217
+ 'currentSlide': currentSlide
1218
+ } );
1219
+ }
1220
+
1221
+ }, 10 );
1222
+
1223
+ }
1224
+
1225
+ }
1226
+
1227
+ /**
1228
+ * Exits the slide overview and enters the currently
1229
+ * active slide.
1230
+ */
1231
+ function deactivateOverview() {
1232
+
1233
+ // Only proceed if enabled in config
1234
+ if( config.overview ) {
1235
+
1236
+ clearTimeout( activateOverviewTimeout );
1237
+ clearTimeout( deactivateOverviewTimeout );
1238
+
1239
+ dom.wrapper.classList.remove( 'overview' );
1240
+
1241
+ // Temporarily add a class so that transitions can do different things
1242
+ // depending on whether they are exiting/entering overview, or just
1243
+ // moving from slide to slide
1244
+ dom.wrapper.classList.add( 'exit-overview' );
1245
+
1246
+ deactivateOverviewTimeout = setTimeout( function () {
1247
+ dom.wrapper.classList.remove( 'exit-overview' );
1248
+ }, 10);
1249
+
1250
+ // 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
+
1258
+ // Resets all transforms to use the external styles
1259
+ transformElement( element, '' );
1260
+
1261
+ element.removeEventListener( 'click', onOverviewSlideClicked, true );
1262
+ }
1263
+
1264
+ slide( indexh, indexv );
1265
+
1266
+ cueAutoSlide();
1267
+
1268
+ // Notify observers of the overview hiding
1269
+ dispatchEvent( 'overviewhidden', {
1270
+ 'indexh': indexh,
1271
+ 'indexv': indexv,
1272
+ 'currentSlide': currentSlide
1273
+ } );
1274
+
1275
+ }
1276
+ }
1277
+
1278
+ /**
1279
+ * Toggles the slide overview mode on and off.
1280
+ *
1281
+ * @param {Boolean} override Optional flag which overrides the
1282
+ * toggle logic and forcibly sets the desired state. True means
1283
+ * overview is open, false means it's closed.
1284
+ */
1285
+ function toggleOverview( override ) {
1286
+
1287
+ if( typeof override === 'boolean' ) {
1288
+ override ? activateOverview() : deactivateOverview();
1289
+ }
1290
+ else {
1291
+ isOverview() ? deactivateOverview() : activateOverview();
1292
+ }
1293
+
1294
+ }
1295
+
1296
+ /**
1297
+ * Checks if the overview is currently active.
1298
+ *
1299
+ * @return {Boolean} true if the overview is active,
1300
+ * false otherwise
1301
+ */
1302
+ function isOverview() {
1303
+
1304
+ return dom.wrapper.classList.contains( 'overview' );
1305
+
1306
+ }
1307
+
1308
+ /**
1309
+ * Checks if the current or specified slide is vertical
1310
+ * (nested within another slide).
1311
+ *
1312
+ * @param {HTMLElement} slide [optional] The slide to check
1313
+ * orientation of
1314
+ */
1315
+ function isVerticalSlide( slide ) {
1316
+
1317
+ // Prefer slide argument, otherwise use current slide
1318
+ slide = slide ? slide : currentSlide;
1319
+
1320
+ return slide && !!slide.parentNode.nodeName.match( /section/i );
1321
+
1322
+ }
1323
+
1324
+ /**
1325
+ * Handling the fullscreen functionality via the fullscreen API
1326
+ *
1327
+ * @see http://fullscreen.spec.whatwg.org/
1328
+ * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
1329
+ */
1330
+ function enterFullscreen() {
1331
+
1332
+ var element = document.body;
1333
+
1334
+ // Check which implementation is available
1335
+ var requestMethod = element.requestFullScreen ||
1336
+ element.webkitRequestFullscreen ||
1337
+ element.webkitRequestFullScreen ||
1338
+ element.mozRequestFullScreen ||
1339
+ element.msRequestFullScreen;
1340
+
1341
+ if( requestMethod ) {
1342
+ requestMethod.apply( element );
1343
+ }
1344
+
1345
+ }
1346
+
1347
+ /**
1348
+ * Enters the paused mode which fades everything on screen to
1349
+ * black.
1350
+ */
1351
+ function pause() {
1352
+
1353
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1354
+
1355
+ cancelAutoSlide();
1356
+ dom.wrapper.classList.add( 'paused' );
1357
+
1358
+ if( wasPaused === false ) {
1359
+ dispatchEvent( 'paused' );
1360
+ }
1361
+
1362
+ }
1363
+
1364
+ /**
1365
+ * Exits from the paused mode.
1366
+ */
1367
+ function resume() {
1368
+
1369
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1370
+ dom.wrapper.classList.remove( 'paused' );
1371
+
1372
+ cueAutoSlide();
1373
+
1374
+ if( wasPaused ) {
1375
+ dispatchEvent( 'resumed' );
1376
+ }
1377
+
1378
+ }
1379
+
1380
+ /**
1381
+ * Toggles the paused mode on and off.
1382
+ */
1383
+ function togglePause() {
1384
+
1385
+ if( isPaused() ) {
1386
+ resume();
1387
+ }
1388
+ else {
1389
+ pause();
1390
+ }
1391
+
1392
+ }
1393
+
1394
+ /**
1395
+ * Checks if we are currently in the paused mode.
1396
+ */
1397
+ function isPaused() {
1398
+
1399
+ return dom.wrapper.classList.contains( 'paused' );
1400
+
1401
+ }
1402
+
1403
+ /**
1404
+ * Steps from the current point in the presentation to the
1405
+ * slide which matches the specified horizontal and vertical
1406
+ * indices.
1407
+ *
1408
+ * @param {int} h Horizontal index of the target slide
1409
+ * @param {int} v Vertical index of the target slide
1410
+ * @param {int} f Optional index of a fragment within the
1411
+ * target slide to activate
1412
+ * @param {int} o Optional origin for use in multimaster environments
1413
+ */
1414
+ function slide( h, v, f, o ) {
1415
+
1416
+ // Remember where we were at before
1417
+ previousSlide = currentSlide;
1418
+
1419
+ // Query all horizontal slides in the deck
1420
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1421
+
1422
+ // If no vertical index is specified and the upcoming slide is a
1423
+ // stack, resume at its previous vertical index
1424
+ if( v === undefined ) {
1425
+ v = getPreviousVerticalIndex( horizontalSlides[ h ] );
1426
+ }
1427
+
1428
+ // If we were on a vertical stack, remember what vertical index
1429
+ // it was on so we can resume at the same position when returning
1430
+ if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {
1431
+ setPreviousVerticalIndex( previousSlide.parentNode, indexv );
1432
+ }
1433
+
1434
+ // Remember the state before this slide
1435
+ var stateBefore = state.concat();
1436
+
1437
+ // Reset the state array
1438
+ state.length = 0;
1439
+
1440
+ var indexhBefore = indexh || 0,
1441
+ indexvBefore = indexv || 0;
1442
+
1443
+ // Activate and transition to the new slide
1444
+ indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
1445
+ indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
1446
+
1447
+ // Update the visibility of slides now that the indices have changed
1448
+ updateSlidesVisibility();
1449
+
1450
+ layout();
1451
+
1452
+ // Apply the new state
1453
+ stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
1454
+ // Check if this state existed on the previous slide. If it
1455
+ // did, we will avoid adding it repeatedly
1456
+ for( var j = 0; j < stateBefore.length; j++ ) {
1457
+ if( stateBefore[j] === state[i] ) {
1458
+ stateBefore.splice( j, 1 );
1459
+ continue stateLoop;
1460
+ }
1461
+ }
1462
+
1463
+ document.documentElement.classList.add( state[i] );
1464
+
1465
+ // Dispatch custom event matching the state's name
1466
+ dispatchEvent( state[i] );
1467
+ }
1468
+
1469
+ // Clean up the remains of the previous state
1470
+ while( stateBefore.length ) {
1471
+ document.documentElement.classList.remove( stateBefore.pop() );
1472
+ }
1473
+
1474
+ // If the overview is active, re-activate it to update positions
1475
+ if( isOverview() ) {
1476
+ activateOverview();
1477
+ }
1478
+
1479
+ // Find the current horizontal slide and any possible vertical slides
1480
+ // within it
1481
+ var currentHorizontalSlide = horizontalSlides[ indexh ],
1482
+ currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
1483
+
1484
+ // Store references to the previous and current slides
1485
+ currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
1486
+
1487
+
1488
+ // Show fragment, if specified
1489
+ 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
+ } );
1500
+ }
1501
+
1502
+ // Dispatch an event if the slide changed
1503
+ var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
1504
+ if( slideChanged ) {
1505
+ dispatchEvent( 'slidechanged', {
1506
+ 'indexh': indexh,
1507
+ 'indexv': indexv,
1508
+ 'previousSlide': previousSlide,
1509
+ 'currentSlide': currentSlide,
1510
+ 'origin': o
1511
+ } );
1512
+ }
1513
+ else {
1514
+ // Ensure that the previous slide is never the same as the current
1515
+ previousSlide = null;
1516
+ }
1517
+
1518
+ // Solves an edge case where the previous slide maintains the
1519
+ // 'present' class when navigating between adjacent vertical
1520
+ // stacks
1521
+ if( previousSlide ) {
1522
+ previousSlide.classList.remove( 'present' );
1523
+
1524
+ // Reset all slides upon navigate to home
1525
+ // Issue: #285
1526
+ if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1527
+ // Launch async task
1528
+ setTimeout( function () {
1529
+ var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1530
+ for( i in slides ) {
1531
+ if( slides[i] ) {
1532
+ // Reset stack
1533
+ setPreviousVerticalIndex( slides[i], 0 );
1534
+ }
1535
+ }
1536
+ }, 0 );
1537
+ }
1538
+ }
1539
+
1540
+ // Handle embedded content
1541
+ if( slideChanged ) {
1542
+ stopEmbeddedContent( previousSlide );
1543
+ startEmbeddedContent( currentSlide );
1544
+ }
1545
+
1546
+ updateControls();
1547
+ updateProgress();
1548
+ updateBackground();
1549
+
1550
+ // Update the URL hash
1551
+ writeURL();
1552
+
1553
+ }
1554
+
1555
+ /**
1556
+ * Syncs the presentation with the current DOM. Useful
1557
+ * when new slides or control elements are added or when
1558
+ * the configuration has changed.
1559
+ */
1560
+ function sync() {
1561
+
1562
+ // Subscribe to input
1563
+ removeEventListeners();
1564
+ addEventListeners();
1565
+
1566
+ // Force a layout to make sure the current config is accounted for
1567
+ layout();
1568
+
1569
+ // Reflect the current autoSlide value
1570
+ autoSlide = config.autoSlide;
1571
+
1572
+ // Start auto-sliding if it's enabled
1573
+ cueAutoSlide();
1574
+
1575
+ // Re-create the slide backgrounds
1576
+ createBackgrounds();
1577
+
1578
+ updateControls();
1579
+ updateProgress();
1580
+ updateBackground();
1581
+
1582
+ }
1583
+
1584
+ /**
1585
+ * Updates one dimension of slides by showing the slide
1586
+ * with the specified index.
1587
+ *
1588
+ * @param {String} selector A CSS selector that will fetch
1589
+ * the group of slides we are working with
1590
+ * @param {Number} index The index of the slide that should be
1591
+ * shown
1592
+ *
1593
+ * @return {Number} The index of the slide that is now shown,
1594
+ * might differ from the passed in index if it was out of
1595
+ * bounds.
1596
+ */
1597
+ function updateSlides( selector, index ) {
1598
+
1599
+ // Select all slides and convert the NodeList result to
1600
+ // an array
1601
+ var slides = toArray( document.querySelectorAll( selector ) ),
1602
+ slidesLength = slides.length;
1603
+
1604
+ if( slidesLength ) {
1605
+
1606
+ // Should the index loop?
1607
+ if( config.loop ) {
1608
+ index %= slidesLength;
1609
+
1610
+ if( index < 0 ) {
1611
+ index = slidesLength + index;
1612
+ }
1613
+ }
1614
+
1615
+ // Enforce max and minimum index bounds
1616
+ index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
1617
+
1618
+ for( var i = 0; i < slidesLength; i++ ) {
1619
+ var element = slides[i];
1620
+
1621
+ var reverse = config.rtl && !isVerticalSlide( element );
1622
+
1623
+ element.classList.remove( 'past' );
1624
+ element.classList.remove( 'present' );
1625
+ element.classList.remove( 'future' );
1626
+
1627
+ // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1628
+ element.setAttribute( 'hidden', '' );
1629
+
1630
+ if( i < index ) {
1631
+ // Any element previous to index is given the 'past' class
1632
+ element.classList.add( reverse ? 'future' : 'past' );
1633
+ }
1634
+ else if( i > index ) {
1635
+ // Any element subsequent to index is given the 'future' class
1636
+ element.classList.add( reverse ? 'past' : 'future' );
1637
+
1638
+ var fragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1639
+
1640
+ // No fragments in future slides should be visible ahead of time
1641
+ while( fragments.length ) {
1642
+ fragments.pop().classList.remove( 'visible' );
1643
+ }
1644
+ }
1645
+
1646
+ // If this element contains vertical slides
1647
+ if( element.querySelector( 'section' ) ) {
1648
+ element.classList.add( 'stack' );
1649
+ }
1650
+ }
1651
+
1652
+ // Mark the current slide as present
1653
+ slides[index].classList.add( 'present' );
1654
+ slides[index].removeAttribute( 'hidden' );
1655
+
1656
+ // If this slide has a state associated with it, add it
1657
+ // onto the current state of the deck
1658
+ var slideState = slides[index].getAttribute( 'data-state' );
1659
+ if( slideState ) {
1660
+ state = state.concat( slideState.split( ' ' ) );
1661
+ }
1662
+
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
+ }
1676
+ else {
1677
+ // Since there are no slides we can't be anywhere beyond the
1678
+ // zeroth index
1679
+ index = 0;
1680
+ }
1681
+
1682
+ return index;
1683
+
1684
+ }
1685
+
1686
+ /**
1687
+ * Optimization method; hide all slides that are far away
1688
+ * from the present slide.
1689
+ */
1690
+ function updateSlidesVisibility() {
1691
+
1692
+ // Select all slides and convert the NodeList result to
1693
+ // an array
1694
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1695
+ horizontalSlidesLength = horizontalSlides.length,
1696
+ distanceX,
1697
+ distanceY;
1698
+
1699
+ if( horizontalSlidesLength ) {
1700
+
1701
+ // The number of steps away from the present slide that will
1702
+ // be visible
1703
+ var viewDistance = isOverview() ? 10 : config.viewDistance;
1704
+
1705
+ // Limit view distance on weaker devices
1706
+ if( isMobileDevice ) {
1707
+ viewDistance = isOverview() ? 6 : 1;
1708
+ }
1709
+
1710
+ for( var x = 0; x < horizontalSlidesLength; x++ ) {
1711
+ var horizontalSlide = horizontalSlides[x];
1712
+
1713
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
1714
+ verticalSlidesLength = verticalSlides.length;
1715
+
1716
+ // Loops so that it measures 1 between the first and last slides
1717
+ distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
1718
+
1719
+ // Show the horizontal slide if it's within the view distance
1720
+ horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
1721
+
1722
+ if( verticalSlidesLength ) {
1723
+
1724
+ var oy = getPreviousVerticalIndex( horizontalSlide );
1725
+
1726
+ for( var y = 0; y < verticalSlidesLength; y++ ) {
1727
+ var verticalSlide = verticalSlides[y];
1728
+
1729
+ distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
1730
+
1731
+ verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
1732
+ }
1733
+
1734
+ }
1735
+ }
1736
+
1737
+ }
1738
+
1739
+ }
1740
+
1741
+ /**
1742
+ * Updates the progress bar to reflect the current slide.
1743
+ */
1744
+ function updateProgress() {
1745
+
1746
+ // Update progress if enabled
1747
+ if( config.progress && dom.progress ) {
1748
+
1749
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1750
+
1751
+ // The number of past and total slides
1752
+ var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
1753
+ var pastCount = 0;
1754
+
1755
+ // Step through all slides and count the past ones
1756
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
1757
+
1758
+ var horizontalSlide = horizontalSlides[i];
1759
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1760
+
1761
+ for( var j = 0; j < verticalSlides.length; j++ ) {
1762
+
1763
+ // Stop as soon as we arrive at the present
1764
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
1765
+ break mainLoop;
1766
+ }
1767
+
1768
+ pastCount++;
1769
+
1770
+ }
1771
+
1772
+ // Stop as soon as we arrive at the present
1773
+ if( horizontalSlide.classList.contains( 'present' ) ) {
1774
+ break;
1775
+ }
1776
+
1777
+ // Don't count the wrapping section for vertical slides
1778
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
1779
+ pastCount++;
1780
+ }
1781
+
1782
+ }
1783
+
1784
+ dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
1785
+
1786
+ }
1787
+
1788
+ }
1789
+
1790
+ /**
1791
+ * Updates the state of all control/navigation arrows.
1792
+ */
1793
+ function updateControls() {
1794
+
1795
+ if ( config.controls && dom.controls ) {
1796
+
1797
+ var routes = availableRoutes();
1798
+ var fragments = availableFragments();
1799
+
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
+ } );
1809
+
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' ); } );
1815
+
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' ); } );
1819
+
1820
+ // Highlight fragment directions
1821
+ if( currentSlide ) {
1822
+
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' ); } );
1826
+
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
+ }
1837
+ }
1838
+
1839
+ }
1840
+
1841
+ }
1842
+
1843
+ /**
1844
+ * Updates the background elements to reflect the current
1845
+ * slide.
1846
+ */
1847
+ function updateBackground() {
1848
+
1849
+ // Update the classes of all backgrounds to match the
1850
+ // states of their slides (past/present/future)
1851
+ toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1852
+
1853
+ // Reverse past/future classes when in RTL mode
1854
+ var horizontalPast = config.rtl ? 'future' : 'past',
1855
+ horizontalFuture = config.rtl ? 'past' : 'future';
1856
+
1857
+ backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
1858
+
1859
+ toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1860
+
1861
+ backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
1862
+
1863
+ } );
1864
+
1865
+ } );
1866
+
1867
+ // Allow the first background to apply without transition
1868
+ setTimeout( function() {
1869
+ dom.background.classList.remove( 'no-transition' );
1870
+ }, 1 );
1871
+
1872
+ }
1873
+
1874
+ /**
1875
+ * Determine what available routes there are for navigation.
1876
+ *
1877
+ * @return {Object} containing four booleans: left/right/up/down
1878
+ */
1879
+ function availableRoutes() {
1880
+
1881
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
1882
+ verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
1883
+
1884
+ var routes = {
1885
+ left: indexh > 0 || config.loop,
1886
+ right: indexh < horizontalSlides.length - 1 || config.loop,
1887
+ up: indexv > 0,
1888
+ down: indexv < verticalSlides.length - 1
1889
+ };
1890
+
1891
+ // reverse horizontal controls for rtl
1892
+ if( config.rtl ) {
1893
+ var left = routes.left;
1894
+ routes.left = routes.right;
1895
+ routes.right = left;
1896
+ }
1897
+
1898
+ return routes;
1899
+
1900
+ }
1901
+
1902
+ /**
1903
+ * Returns an object describing the available fragment
1904
+ * directions.
1905
+ *
1906
+ * @return {Object} two boolean properties: prev/next
1907
+ */
1908
+ function availableFragments() {
1909
+
1910
+ if( currentSlide && config.fragments ) {
1911
+ var fragments = currentSlide.querySelectorAll( '.fragment' );
1912
+ var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
1913
+
1914
+ return {
1915
+ prev: fragments.length - hiddenFragments.length > 0,
1916
+ next: !!hiddenFragments.length
1917
+ };
1918
+ }
1919
+ else {
1920
+ return { prev: false, next: false };
1921
+ }
1922
+
1923
+ }
1924
+
1925
+ /**
1926
+ * Start playback of any embedded content inside of
1927
+ * the targeted slide.
1928
+ */
1929
+ function startEmbeddedContent( slide ) {
1930
+
1931
+ if( slide ) {
1932
+ // HTML5 media elements
1933
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1934
+ if( el.hasAttribute( 'data-autoplay' ) ) {
1935
+ el.play();
1936
+ }
1937
+ } );
1938
+
1939
+ // YouTube embeds
1940
+ toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1941
+ if( el.hasAttribute( 'data-autoplay' ) ) {
1942
+ el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
1943
+ }
1944
+ });
1945
+ }
1946
+
1947
+ }
1948
+
1949
+ /**
1950
+ * Stop playback of any embedded content inside of
1951
+ * the targeted slide.
1952
+ */
1953
+ function stopEmbeddedContent( slide ) {
1954
+
1955
+ if( slide ) {
1956
+ // HTML5 media elements
1957
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1958
+ if( !el.hasAttribute( 'data-ignore' ) ) {
1959
+ el.pause();
1960
+ }
1961
+ } );
1962
+
1963
+ // YouTube embeds
1964
+ toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1965
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
1966
+ el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
1967
+ }
1968
+ });
1969
+ }
1970
+
1971
+ }
1972
+
1973
+ /**
1974
+ * Reads the current URL (hash) and navigates accordingly.
1975
+ */
1976
+ function readURL() {
1977
+
1978
+ var hash = window.location.hash;
1979
+
1980
+ // Attempt to parse the hash as either an index or name
1981
+ var bits = hash.slice( 2 ).split( '/' ),
1982
+ name = hash.replace( /#|\//gi, '' );
1983
+
1984
+ // If the first bit is invalid and there is a name we can
1985
+ // assume that this is a named link
1986
+ if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
1987
+ // Find the slide with the specified name
1988
+ var element = document.querySelector( '#' + name );
1989
+
1990
+ if( element ) {
1991
+ // Find the position of the named slide and navigate to it
1992
+ var indices = Reveal.getIndices( element );
1993
+ slide( indices.h, indices.v );
1994
+ }
1995
+ // If the slide doesn't exist, navigate to the current slide
1996
+ else {
1997
+ slide( indexh || 0, indexv || 0 );
1998
+ }
1999
+ }
2000
+ else {
2001
+ // Read the index components of the hash
2002
+ var h = parseInt( bits[0], 10 ) || 0,
2003
+ v = parseInt( bits[1], 10 ) || 0;
2004
+
2005
+ if( h !== indexh || v !== indexv ) {
2006
+ slide( h, v );
2007
+ }
2008
+ }
2009
+
2010
+ }
2011
+
2012
+ /**
2013
+ * Updates the page URL (hash) to reflect the current
2014
+ * state.
2015
+ *
2016
+ * @param {Number} delay The time in ms to wait before
2017
+ * writing the hash
2018
+ */
2019
+ function writeURL( delay ) {
2020
+
2021
+ if( config.history ) {
2022
+
2023
+ // Make sure there's never more than one timeout running
2024
+ clearTimeout( writeURLTimeout );
2025
+
2026
+ // If a delay is specified, timeout this call
2027
+ if( typeof delay === 'number' ) {
2028
+ writeURLTimeout = setTimeout( writeURL, delay );
2029
+ }
2030
+ else {
2031
+ var url = '/';
2032
+
2033
+ // If the current slide has an ID, use that as a named link
2034
+ if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
2035
+ url = '/' + currentSlide.getAttribute( 'id' );
2036
+ }
2037
+ // Otherwise use the /h/v index
2038
+ else {
2039
+ if( indexh > 0 || indexv > 0 ) url += indexh;
2040
+ if( indexv > 0 ) url += '/' + indexv;
2041
+ }
2042
+
2043
+ window.location.hash = url;
2044
+ }
2045
+ }
2046
+
2047
+ }
2048
+
2049
+ /**
2050
+ * Retrieves the h/v location of the current, or specified,
2051
+ * slide.
2052
+ *
2053
+ * @param {HTMLElement} slide If specified, the returned
2054
+ * index will be for this slide rather than the currently
2055
+ * active one
2056
+ *
2057
+ * @return {Object} { h: <int>, v: <int>, f: <int> }
2058
+ */
2059
+ function getIndices( slide ) {
2060
+
2061
+ // By default, return the current indices
2062
+ var h = indexh,
2063
+ v = indexv,
2064
+ f;
2065
+
2066
+ // If a slide is specified, return the indices of that slide
2067
+ if( slide ) {
2068
+ var isVertical = isVerticalSlide( slide );
2069
+ var slideh = isVertical ? slide.parentNode : slide;
2070
+
2071
+ // Select all horizontal slides
2072
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2073
+
2074
+ // Now that we know which the horizontal slide is, get its index
2075
+ h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2076
+
2077
+ // If this is a vertical slide, grab the vertical index
2078
+ if( isVertical ) {
2079
+ v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
2080
+ }
2081
+ }
2082
+
2083
+ if( !slide && currentSlide ) {
2084
+ var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2085
+ if( hasFragments ) {
2086
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2087
+ f = visibleFragments.length;
2088
+ }
2089
+ }
2090
+
2091
+ return { h: h, v: v, f: f };
2092
+
2093
+ }
2094
+
2095
+ /**
2096
+ * Navigate to the next slide fragment.
2097
+ *
2098
+ * @return {Boolean} true if there was a next fragment,
2099
+ * false otherwise
2100
+ */
2101
+ function nextFragment() {
2102
+
2103
+ if( currentSlide && config.fragments ) {
2104
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
2105
+
2106
+ if( fragments.length ) {
2107
+ // Find the index of the next fragment
2108
+ var index = fragments[0].getAttribute( 'data-fragment-index' );
2109
+
2110
+ // Find all fragments with the same index
2111
+ fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
2112
+
2113
+ toArray( fragments ).forEach( function( element ) {
2114
+ element.classList.add( 'visible' );
2115
+ } );
2116
+
2117
+ // Notify subscribers of the change
2118
+ dispatchEvent( 'fragmentshown', { fragment: fragments[0], fragments: fragments } );
2119
+
2120
+ updateControls();
2121
+ return true;
2122
+ }
2123
+ }
2124
+
2125
+ return false;
2126
+
2127
+ }
2128
+
2129
+ /**
2130
+ * Navigate to the previous slide fragment.
2131
+ *
2132
+ * @return {Boolean} true if there was a previous fragment,
2133
+ * false otherwise
2134
+ */
2135
+ function previousFragment() {
2136
+
2137
+ if( currentSlide && config.fragments ) {
2138
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
2139
+
2140
+ if( fragments.length ) {
2141
+ // Find the index of the previous fragment
2142
+ var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
2143
+
2144
+ // Find all fragments with the same index
2145
+ fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
2146
+
2147
+ toArray( fragments ).forEach( function( f ) {
2148
+ f.classList.remove( 'visible' );
2149
+ } );
2150
+
2151
+ // Notify subscribers of the change
2152
+ dispatchEvent( 'fragmenthidden', { fragment: fragments[0], fragments: fragments } );
2153
+
2154
+ updateControls();
2155
+ return true;
2156
+ }
2157
+ }
2158
+
2159
+ return false;
2160
+
2161
+ }
2162
+
2163
+ /**
2164
+ * Cues a new automated slide if enabled in the config.
2165
+ */
2166
+ function cueAutoSlide() {
2167
+
2168
+ clearTimeout( autoSlideTimeout );
2169
+
2170
+ // Cue the next auto-slide if enabled
2171
+ if( autoSlide && !isPaused() && !isOverview() ) {
2172
+ autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2173
+ }
2174
+
2175
+ }
2176
+
2177
+ /**
2178
+ * Cancels any ongoing request to auto-slide.
2179
+ */
2180
+ function cancelAutoSlide() {
2181
+
2182
+ clearTimeout( autoSlideTimeout );
2183
+
2184
+ }
2185
+
2186
+ function navigateLeft() {
2187
+
2188
+ // Reverse for RTL
2189
+ if( config.rtl ) {
2190
+ if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
2191
+ slide( indexh + 1 );
2192
+ }
2193
+ }
2194
+ // Normal navigation
2195
+ else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
2196
+ slide( indexh - 1 );
2197
+ }
2198
+
2199
+ }
2200
+
2201
+ function navigateRight() {
2202
+
2203
+ // Reverse for RTL
2204
+ if( config.rtl ) {
2205
+ if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
2206
+ slide( indexh - 1 );
2207
+ }
2208
+ }
2209
+ // Normal navigation
2210
+ else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
2211
+ slide( indexh + 1 );
2212
+ }
2213
+
2214
+ }
2215
+
2216
+ function navigateUp() {
2217
+
2218
+ // Prioritize hiding fragments
2219
+ if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
2220
+ slide( indexh, indexv - 1 );
2221
+ }
2222
+
2223
+ }
2224
+
2225
+ function navigateDown() {
2226
+
2227
+ // Prioritize revealing fragments
2228
+ if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
2229
+ slide( indexh, indexv + 1 );
2230
+ }
2231
+
2232
+ }
2233
+
2234
+ /**
2235
+ * Navigates backwards, prioritized in the following order:
2236
+ * 1) Previous fragment
2237
+ * 2) Previous vertical slide
2238
+ * 3) Previous horizontal slide
2239
+ */
2240
+ function navigatePrev() {
2241
+
2242
+ // Prioritize revealing fragments
2243
+ if( previousFragment() === false ) {
2244
+ if( availableRoutes().up ) {
2245
+ navigateUp();
2246
+ }
2247
+ else {
2248
+ // Fetch the previous horizontal slide, if there is one
2249
+ var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
2250
+
2251
+ if( previousSlide ) {
2252
+ var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
2253
+ var h = indexh - 1;
2254
+ slide( h, v );
2255
+ }
2256
+ }
2257
+ }
2258
+
2259
+ }
2260
+
2261
+ /**
2262
+ * Same as #navigatePrev() but navigates forwards.
2263
+ */
2264
+ function navigateNext() {
2265
+
2266
+ // Prioritize revealing fragments
2267
+ if( nextFragment() === false ) {
2268
+ availableRoutes().down ? navigateDown() : navigateRight();
2269
+ }
2270
+
2271
+ // If auto-sliding is enabled we need to cue up
2272
+ // another timeout
2273
+ cueAutoSlide();
2274
+
2275
+ }
2276
+
2277
+
2278
+ // --------------------------------------------------------------------//
2279
+ // ----------------------------- EVENTS -------------------------------//
2280
+ // --------------------------------------------------------------------//
2281
+
2282
+
2283
+ /**
2284
+ * Handler for the document level 'keydown' event.
2285
+ *
2286
+ * @param {Object} event
2287
+ */
2288
+ function onDocumentKeyDown( event ) {
2289
+
2290
+ // Check if there's a focused element that could be using
2291
+ // the keyboard
2292
+ var activeElement = document.activeElement;
2293
+ var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
2294
+
2295
+ // Disregard the event if there's a focused element or a
2296
+ // keyboard modifier key is present
2297
+ if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2298
+
2299
+ // While paused only allow "unpausing" keyboard events (b and .)
2300
+ if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
2301
+ return false;
2302
+ }
2303
+
2304
+ var triggered = false;
2305
+
2306
+ // 1. User defined key bindings
2307
+ if( typeof config.keyboard === 'object' ) {
2308
+
2309
+ for( var key in config.keyboard ) {
2310
+
2311
+ // Check if this binding matches the pressed key
2312
+ if( parseInt( key, 10 ) === event.keyCode ) {
2313
+
2314
+ var value = config.keyboard[ key ];
2315
+
2316
+ // Callback function
2317
+ if( typeof value === 'function' ) {
2318
+ value.apply( null, [ event ] );
2319
+ }
2320
+ // String shortcuts to reveal.js API
2321
+ else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {
2322
+ Reveal[ value ].call();
2323
+ }
2324
+
2325
+ triggered = true;
2326
+
2327
+ }
2328
+
2329
+ }
2330
+
2331
+ }
2332
+
2333
+ // 2. System defined key bindings
2334
+ if( triggered === false ) {
2335
+
2336
+ // Assume true and try to prove false
2337
+ triggered = true;
2338
+
2339
+ switch( event.keyCode ) {
2340
+ // p, page up
2341
+ case 80: case 33: navigatePrev(); break;
2342
+ // n, page down
2343
+ case 78: case 34: navigateNext(); break;
2344
+ // h, left
2345
+ case 72: case 37: navigateLeft(); break;
2346
+ // l, right
2347
+ case 76: case 39: navigateRight(); break;
2348
+ // k, up
2349
+ case 75: case 38: navigateUp(); break;
2350
+ // j, down
2351
+ case 74: case 40: navigateDown(); break;
2352
+ // home
2353
+ case 36: slide( 0 ); break;
2354
+ // end
2355
+ case 35: slide( Number.MAX_VALUE ); break;
2356
+ // space
2357
+ case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2358
+ // return
2359
+ case 13: isOverview() ? deactivateOverview() : triggered = false; break;
2360
+ // b, period, Logitech presenter tools "black screen" button
2361
+ case 66: case 190: case 191: togglePause(); break;
2362
+ // f
2363
+ case 70: enterFullscreen(); break;
2364
+ default:
2365
+ triggered = false;
2366
+ }
2367
+
2368
+ }
2369
+
2370
+ // If the input resulted in a triggered action we should prevent
2371
+ // the browsers default behavior
2372
+ if( triggered ) {
2373
+ event.preventDefault();
2374
+ }
2375
+ // ESC or O key
2376
+ else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && supports3DTransforms ) {
2377
+ toggleOverview();
2378
+
2379
+ event.preventDefault();
2380
+ }
2381
+
2382
+ // If auto-sliding is enabled we need to cue up
2383
+ // another timeout
2384
+ cueAutoSlide();
2385
+
2386
+ }
2387
+
2388
+ /**
2389
+ * Handler for the 'touchstart' event, enables support for
2390
+ * swipe and pinch gestures.
2391
+ */
2392
+ function onTouchStart( event ) {
2393
+
2394
+ touch.startX = event.touches[0].clientX;
2395
+ touch.startY = event.touches[0].clientY;
2396
+ touch.startCount = event.touches.length;
2397
+
2398
+ // If there's two touches we need to memorize the distance
2399
+ // between those two points to detect pinching
2400
+ if( event.touches.length === 2 && config.overview ) {
2401
+ touch.startSpan = distanceBetween( {
2402
+ x: event.touches[1].clientX,
2403
+ y: event.touches[1].clientY
2404
+ }, {
2405
+ x: touch.startX,
2406
+ y: touch.startY
2407
+ } );
2408
+ }
2409
+
2410
+ }
2411
+
2412
+ /**
2413
+ * Handler for the 'touchmove' event.
2414
+ */
2415
+ function onTouchMove( event ) {
2416
+
2417
+ // Each touch should only trigger one action
2418
+ if( !touch.captured ) {
2419
+ var currentX = event.touches[0].clientX;
2420
+ var currentY = event.touches[0].clientY;
2421
+
2422
+ // If the touch started with two points and still has
2423
+ // two active touches; test for the pinch gesture
2424
+ if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
2425
+
2426
+ // The current distance in pixels between the two touch points
2427
+ var currentSpan = distanceBetween( {
2428
+ x: event.touches[1].clientX,
2429
+ y: event.touches[1].clientY
2430
+ }, {
2431
+ x: touch.startX,
2432
+ y: touch.startY
2433
+ } );
2434
+
2435
+ // If the span is larger than the desire amount we've got
2436
+ // ourselves a pinch
2437
+ if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
2438
+ touch.captured = true;
2439
+
2440
+ if( currentSpan < touch.startSpan ) {
2441
+ activateOverview();
2442
+ }
2443
+ else {
2444
+ deactivateOverview();
2445
+ }
2446
+ }
2447
+
2448
+ event.preventDefault();
2449
+
2450
+ }
2451
+ // There was only one touch point, look for a swipe
2452
+ else if( event.touches.length === 1 && touch.startCount !== 2 ) {
2453
+
2454
+ var deltaX = currentX - touch.startX,
2455
+ deltaY = currentY - touch.startY;
2456
+
2457
+ if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
2458
+ touch.captured = true;
2459
+ navigateLeft();
2460
+ }
2461
+ else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
2462
+ touch.captured = true;
2463
+ navigateRight();
2464
+ }
2465
+ else if( deltaY > touch.threshold ) {
2466
+ touch.captured = true;
2467
+ navigateUp();
2468
+ }
2469
+ else if( deltaY < -touch.threshold ) {
2470
+ touch.captured = true;
2471
+ navigateDown();
2472
+ }
2473
+
2474
+ // If we're embedded, only block touch events if they have
2475
+ // triggered an action
2476
+ if( config.embedded ) {
2477
+ if( touch.captured || isVerticalSlide( currentSlide ) ) {
2478
+ event.preventDefault();
2479
+ }
2480
+ }
2481
+ // Not embedded? Block them all to avoid needless tossing
2482
+ // around of the viewport in iOS
2483
+ else {
2484
+ event.preventDefault();
2485
+ }
2486
+
2487
+ }
2488
+ }
2489
+ // There's a bug with swiping on some Android devices unless
2490
+ // the default action is always prevented
2491
+ else if( navigator.userAgent.match( /android/gi ) ) {
2492
+ event.preventDefault();
2493
+ }
2494
+
2495
+ }
2496
+
2497
+ /**
2498
+ * Handler for the 'touchend' event.
2499
+ */
2500
+ function onTouchEnd( event ) {
2501
+
2502
+ touch.captured = false;
2503
+
2504
+ }
2505
+
2506
+ /**
2507
+ * Convert pointer down to touch start.
2508
+ */
2509
+ function onPointerDown( event ) {
2510
+
2511
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2512
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2513
+ onTouchStart( event );
2514
+ }
2515
+
2516
+ }
2517
+
2518
+ /**
2519
+ * Convert pointer move to touch move.
2520
+ */
2521
+ function onPointerMove( event ) {
2522
+
2523
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2524
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2525
+ onTouchMove( event );
2526
+ }
2527
+
2528
+ }
2529
+
2530
+ /**
2531
+ * Convert pointer up to touch end.
2532
+ */
2533
+ function onPointerUp( event ) {
2534
+
2535
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2536
+ event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2537
+ onTouchEnd( event );
2538
+ }
2539
+
2540
+ }
2541
+
2542
+ /**
2543
+ * Handles mouse wheel scrolling, throttled to avoid skipping
2544
+ * multiple slides.
2545
+ */
2546
+ function onDocumentMouseScroll( event ) {
2547
+
2548
+ if( Date.now() - lastMouseWheelStep > 600 ) {
2549
+
2550
+ lastMouseWheelStep = Date.now();
2551
+
2552
+ var delta = event.detail || -event.wheelDelta;
2553
+ if( delta > 0 ) {
2554
+ navigateNext();
2555
+ }
2556
+ else {
2557
+ navigatePrev();
2558
+ }
2559
+
2560
+ }
2561
+
2562
+ }
2563
+
2564
+ /**
2565
+ * Clicking on the progress bar results in a navigation to the
2566
+ * closest approximate horizontal slide using this equation:
2567
+ *
2568
+ * ( clickX / presentationWidth ) * numberOfSlides
2569
+ */
2570
+ function onProgressClicked( event ) {
2571
+
2572
+ event.preventDefault();
2573
+
2574
+ var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2575
+ var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2576
+
2577
+ slide( slideIndex );
2578
+
2579
+ }
2580
+
2581
+ /**
2582
+ * Event handler for navigation control buttons.
2583
+ */
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(); }
2590
+
2591
+ /**
2592
+ * Handler for the window level 'hashchange' event.
2593
+ */
2594
+ function onWindowHashChange( event ) {
2595
+
2596
+ readURL();
2597
+
2598
+ }
2599
+
2600
+ /**
2601
+ * Handler for the window level 'resize' event.
2602
+ */
2603
+ function onWindowResize( event ) {
2604
+
2605
+ layout();
2606
+
2607
+ }
2608
+
2609
+ /**
2610
+ * Invoked when a slide is and we're in the overview.
2611
+ */
2612
+ function onOverviewSlideClicked( event ) {
2613
+
2614
+ // TODO There's a bug here where the event listeners are not
2615
+ // removed after deactivating the overview.
2616
+ if( eventsAreBound && isOverview() ) {
2617
+ event.preventDefault();
2618
+
2619
+ var element = event.target;
2620
+
2621
+ while( element && !element.nodeName.match( /section/gi ) ) {
2622
+ element = element.parentNode;
2623
+ }
2624
+
2625
+ if( element && !element.classList.contains( 'disabled' ) ) {
2626
+
2627
+ deactivateOverview();
2628
+
2629
+ if( element.nodeName.match( /section/gi ) ) {
2630
+ var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
2631
+ v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
2632
+
2633
+ slide( h, v );
2634
+ }
2635
+
2636
+ }
2637
+ }
2638
+
2639
+ }
2640
+
2641
+ /**
2642
+ * Handles clicks on links that are set to preview in the
2643
+ * iframe overlay.
2644
+ */
2645
+ function onPreviewLinkClicked( event ) {
2646
+
2647
+ var url = event.target.getAttribute( 'href' );
2648
+ if( url ) {
2649
+ openPreview( url );
2650
+ event.preventDefault();
2651
+ }
2652
+
2653
+ }
2654
+
2655
+
2656
+ // --------------------------------------------------------------------//
2657
+ // ------------------------------- API --------------------------------//
2658
+ // --------------------------------------------------------------------//
2659
+
2660
+
2661
+ return {
2662
+ initialize: initialize,
2663
+ configure: configure,
2664
+ sync: sync,
2665
+
2666
+ // Navigation methods
2667
+ slide: slide,
2668
+ left: navigateLeft,
2669
+ right: navigateRight,
2670
+ up: navigateUp,
2671
+ down: navigateDown,
2672
+ prev: navigatePrev,
2673
+ next: navigateNext,
2674
+ prevFragment: previousFragment,
2675
+ nextFragment: nextFragment,
2676
+
2677
+ // Deprecated aliases
2678
+ navigateTo: slide,
2679
+ navigateLeft: navigateLeft,
2680
+ navigateRight: navigateRight,
2681
+ navigateUp: navigateUp,
2682
+ navigateDown: navigateDown,
2683
+ navigatePrev: navigatePrev,
2684
+ navigateNext: navigateNext,
2685
+
2686
+ // Forces an update in slide layout
2687
+ layout: layout,
2688
+
2689
+ // Returns an object with the available routes as booleans (left/right/top/bottom)
2690
+ availableRoutes: availableRoutes,
2691
+
2692
+ // Returns an object with the available fragments as booleans (prev/next)
2693
+ availableFragments: availableFragments,
2694
+
2695
+ // Toggles the overview mode on/off
2696
+ toggleOverview: toggleOverview,
2697
+
2698
+ // Toggles the "black screen" mode on/off
2699
+ togglePause: togglePause,
2700
+
2701
+ // State checks
2702
+ isOverview: isOverview,
2703
+ isPaused: isPaused,
2704
+
2705
+ // Adds or removes all internal event listeners (such as keyboard)
2706
+ addEventListeners: addEventListeners,
2707
+ removeEventListeners: removeEventListeners,
2708
+
2709
+ // Returns the indices of the current, or specified, slide
2710
+ getIndices: getIndices,
2711
+
2712
+ // Returns the slide at the specified index, y is optional
2713
+ getSlide: function( x, y ) {
2714
+ var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
2715
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
2716
+
2717
+ if( typeof y !== 'undefined' ) {
2718
+ return verticalSlides ? verticalSlides[ y ] : undefined;
2719
+ }
2720
+
2721
+ return horizontalSlide;
2722
+ },
2723
+
2724
+ // Returns the previous slide element, may be null
2725
+ getPreviousSlide: function() {
2726
+ return previousSlide;
2727
+ },
2728
+
2729
+ // Returns the current slide element
2730
+ getCurrentSlide: function() {
2731
+ return currentSlide;
2732
+ },
2733
+
2734
+ // Returns the current scale of the presentation content
2735
+ getScale: function() {
2736
+ return scale;
2737
+ },
2738
+
2739
+ // Returns the current configuration object
2740
+ getConfig: function() {
2741
+ return config;
2742
+ },
2743
+
2744
+ // Helper method, retrieves query string as a key/value hash
2745
+ getQueryHash: function() {
2746
+ var query = {};
2747
+
2748
+ location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
2749
+ query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
2750
+ } );
2751
+
2752
+ return query;
2753
+ },
2754
+
2755
+ // Returns true if we're currently on the first slide
2756
+ isFirstSlide: function() {
2757
+ return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
2758
+ },
2759
+
2760
+ // Returns true if we're currently on the last slide
2761
+ 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;
2767
+ }
2768
+ },
2769
+
2770
+ // Checks if reveal.js has been loaded and is ready for use
2771
+ isReady: function() {
2772
+ return loaded;
2773
+ },
2774
+
2775
+ // Forward event binding to the reveal DOM element
2776
+ addEventListener: function( type, listener, useCapture ) {
2777
+ if( 'addEventListener' in window ) {
2778
+ ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
2779
+ }
2780
+ },
2781
+ removeEventListener: function( type, listener, useCapture ) {
2782
+ if( 'addEventListener' in window ) {
2783
+ ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
2784
+ }
2785
+ }
2786
+ };
2787
+
2788
+ })();