slippery 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/assets/deck.js/core/deck.core.css +60 -0
  4. data/assets/deck.js/core/deck.core.js +728 -0
  5. data/assets/deck.js/core/deck.core.scss +65 -0
  6. data/assets/deck.js/core/print.css +25 -0
  7. data/assets/deck.js/core/print.scss +14 -0
  8. data/assets/deck.js/css/common.css +216 -0
  9. data/assets/deck.js/css/common.scss +233 -0
  10. data/assets/deck.js/css/home.css +790 -0
  11. data/assets/deck.js/css/home.scss +892 -0
  12. data/assets/deck.js/extensions/goto/deck.goto.css +36 -0
  13. data/assets/deck.js/extensions/goto/deck.goto.html +7 -0
  14. data/assets/deck.js/extensions/goto/deck.goto.js +190 -0
  15. data/assets/deck.js/extensions/goto/deck.goto.scss +39 -0
  16. data/assets/deck.js/extensions/hash/deck.hash.css +13 -0
  17. data/assets/deck.js/extensions/hash/deck.hash.html +2 -0
  18. data/assets/deck.js/extensions/hash/deck.hash.js +145 -0
  19. data/assets/deck.js/extensions/hash/deck.hash.scss +13 -0
  20. data/assets/deck.js/extensions/menu/deck.menu.css +45 -0
  21. data/assets/deck.js/extensions/menu/deck.menu.js +225 -0
  22. data/assets/deck.js/extensions/menu/deck.menu.scss +55 -0
  23. data/assets/deck.js/extensions/navigation/deck.navigation.css +42 -0
  24. data/assets/deck.js/extensions/navigation/deck.navigation.html +5 -0
  25. data/assets/deck.js/extensions/navigation/deck.navigation.js +94 -0
  26. data/assets/deck.js/extensions/navigation/deck.navigation.scss +44 -0
  27. data/assets/deck.js/extensions/scale/deck.scale.css +20 -0
  28. data/assets/deck.js/extensions/scale/deck.scale.js +190 -0
  29. data/assets/deck.js/extensions/scale/deck.scale.scss +22 -0
  30. data/assets/deck.js/extensions/status/deck.status.css +18 -0
  31. data/assets/deck.js/extensions/status/deck.status.html +6 -0
  32. data/assets/deck.js/extensions/status/deck.status.js +108 -0
  33. data/assets/deck.js/extensions/status/deck.status.scss +18 -0
  34. data/assets/deck.js/modernizr.custom.js +4 -0
  35. data/assets/deck.js/themes/style/_reset.scss +300 -0
  36. data/assets/deck.js/themes/style/neon.css +421 -0
  37. data/assets/deck.js/themes/style/neon.scss +148 -0
  38. data/assets/deck.js/themes/style/swiss.css +389 -0
  39. data/assets/deck.js/themes/style/swiss.scss +109 -0
  40. data/assets/deck.js/themes/style/web-2.0.css +500 -0
  41. data/assets/deck.js/themes/style/web-2.0.scss +228 -0
  42. data/assets/deck.js/themes/transition/fade.css +35 -0
  43. data/assets/deck.js/themes/transition/fade.scss +59 -0
  44. data/assets/deck.js/themes/transition/horizontal-slide.css +53 -0
  45. data/assets/deck.js/themes/transition/horizontal-slide.scss +72 -0
  46. data/assets/deck.js/themes/transition/vertical-slide.css +67 -0
  47. data/assets/deck.js/themes/transition/vertical-slide.scss +92 -0
  48. data/assets/highlight.js/CHANGES.md +953 -0
  49. data/assets/highlight.js/LICENSE +24 -0
  50. data/assets/highlight.js/README.md +101 -0
  51. data/assets/highlight.js/README.ru.md +101 -0
  52. data/assets/highlight.js/highlight-0.8.default.min.css +1 -0
  53. data/assets/highlight.js/highlight-0.8.min.js +1 -0
  54. data/assets/highlight.js/highlight.pack.js +1 -0
  55. data/assets/highlight.js/styles/arta.css +141 -0
  56. data/assets/highlight.js/styles/ascetic.css +53 -0
  57. data/assets/highlight.js/styles/atelier-dune.dark.css +95 -0
  58. data/assets/highlight.js/styles/atelier-dune.light.css +95 -0
  59. data/assets/highlight.js/styles/atelier-forest.dark.css +95 -0
  60. data/assets/highlight.js/styles/atelier-forest.light.css +95 -0
  61. data/assets/highlight.js/styles/atelier-heath.dark.css +95 -0
  62. data/assets/highlight.js/styles/atelier-heath.light.css +95 -0
  63. data/assets/highlight.js/styles/atelier-lakeside.dark.css +95 -0
  64. data/assets/highlight.js/styles/atelier-lakeside.light.css +95 -0
  65. data/assets/highlight.js/styles/atelier-seaside.dark.css +95 -0
  66. data/assets/highlight.js/styles/atelier-seaside.light.css +95 -0
  67. data/assets/highlight.js/styles/brown_paper.css +105 -0
  68. data/assets/highlight.js/styles/brown_papersq.png +0 -0
  69. data/assets/highlight.js/styles/codepen-embed.css +108 -0
  70. data/assets/highlight.js/styles/color-brewer.css +169 -0
  71. data/assets/highlight.js/styles/dark.css +105 -0
  72. data/assets/highlight.js/styles/default.css +153 -0
  73. data/assets/highlight.js/styles/docco.css +136 -0
  74. data/assets/highlight.js/styles/far.css +112 -0
  75. data/assets/highlight.js/styles/foundation.css +136 -0
  76. data/assets/highlight.js/styles/github.css +127 -0
  77. data/assets/highlight.js/styles/googlecode.css +148 -0
  78. data/assets/highlight.js/styles/hybrid.css +171 -0
  79. data/assets/highlight.js/styles/idea.css +126 -0
  80. data/assets/highlight.js/styles/ir_black.css +110 -0
  81. data/assets/highlight.js/styles/kimbie.dark.css +96 -0
  82. data/assets/highlight.js/styles/kimbie.light.css +96 -0
  83. data/assets/highlight.js/styles/magula.css +122 -0
  84. data/assets/highlight.js/styles/mono-blue.css +70 -0
  85. data/assets/highlight.js/styles/monokai.css +127 -0
  86. data/assets/highlight.js/styles/monokai_sublime.css +148 -0
  87. data/assets/highlight.js/styles/obsidian.css +154 -0
  88. data/assets/highlight.js/styles/paraiso.dark.css +95 -0
  89. data/assets/highlight.js/styles/paraiso.light.css +95 -0
  90. data/assets/highlight.js/styles/pojoaque.css +108 -0
  91. data/assets/highlight.js/styles/pojoaque.jpg +0 -0
  92. data/assets/highlight.js/styles/railscasts.css +185 -0
  93. data/assets/highlight.js/styles/rainbow.css +109 -0
  94. data/assets/highlight.js/styles/school_book.css +113 -0
  95. data/assets/highlight.js/styles/school_book.png +0 -0
  96. data/assets/highlight.js/styles/solarized_dark.css +109 -0
  97. data/assets/highlight.js/styles/solarized_light.css +109 -0
  98. data/assets/highlight.js/styles/sunburst.css +165 -0
  99. data/assets/highlight.js/styles/tomorrow-night-blue.css +95 -0
  100. data/assets/highlight.js/styles/tomorrow-night-bright.css +94 -0
  101. data/assets/highlight.js/styles/tomorrow-night-eighties.css +94 -0
  102. data/assets/highlight.js/styles/tomorrow-night.css +95 -0
  103. data/assets/highlight.js/styles/tomorrow.css +92 -0
  104. data/assets/highlight.js/styles/vs.css +94 -0
  105. data/assets/highlight.js/styles/xcode.css +159 -0
  106. data/assets/highlight.js/styles/zenburn.css +119 -0
  107. data/assets/jquery/jquery-2.1.0.min.js +4 -0
  108. data/assets/reveal.js/css/print/paper.css +1 -1
  109. data/assets/reveal.js/css/print/pdf.css +2 -2
  110. data/assets/reveal.js/css/reveal.css +372 -108
  111. data/assets/reveal.js/css/reveal.min.css +2 -2
  112. data/assets/reveal.js/css/theme/README.md +3 -1
  113. data/assets/reveal.js/css/theme/beige.css +7 -1
  114. data/assets/reveal.js/css/theme/blood.css +175 -0
  115. data/assets/reveal.js/css/theme/default.css +7 -1
  116. data/assets/reveal.js/css/theme/moon.css +7 -1
  117. data/assets/reveal.js/css/theme/night.css +7 -1
  118. data/assets/reveal.js/css/theme/serif.css +7 -1
  119. data/assets/reveal.js/css/theme/simple.css +7 -1
  120. data/assets/reveal.js/css/theme/sky.css +7 -1
  121. data/assets/reveal.js/css/theme/solarized.css +7 -1
  122. data/assets/reveal.js/css/theme/source/blood.scss +91 -0
  123. data/assets/reveal.js/css/theme/template/settings.scss +1 -0
  124. data/assets/reveal.js/css/theme/template/theme.scss +9 -2
  125. data/assets/reveal.js/js/reveal.js +1238 -433
  126. data/assets/reveal.js/js/reveal.min.js +4 -3
  127. data/assets/reveal.js/lib/css/zenburn.css +16 -17
  128. data/assets/reveal.js/plugin/highlight/highlight.js +3 -2
  129. data/assets/reveal.js/plugin/leap/leap.js +3 -0
  130. data/assets/reveal.js/plugin/markdown/example.html +37 -5
  131. data/assets/reveal.js/plugin/markdown/example.md +2 -0
  132. data/assets/reveal.js/plugin/markdown/markdown.js +373 -171
  133. data/assets/reveal.js/plugin/math/math.js +64 -0
  134. data/assets/reveal.js/plugin/multiplex/master.js +2 -1
  135. data/assets/reveal.js/plugin/notes/notes.html +33 -19
  136. data/assets/reveal.js/plugin/notes/notes.js +25 -47
  137. data/assets/reveal.js/plugin/remotes/remotes.js +4 -4
  138. data/assets/reveal.js/plugin/zoom-js/zoom.js +3 -1
  139. data/assets/reveal.old/css/print/paper.css +176 -0
  140. data/assets/reveal.old/css/print/pdf.css +190 -0
  141. data/assets/reveal.old/css/reveal.css +1616 -0
  142. data/assets/reveal.old/css/reveal.min.css +7 -0
  143. data/assets/reveal.old/css/theme/README.md +23 -0
  144. data/assets/reveal.old/css/theme/beige.css +142 -0
  145. data/assets/reveal.old/css/theme/default.css +142 -0
  146. data/assets/reveal.old/css/theme/moon.css +142 -0
  147. data/assets/reveal.old/css/theme/night.css +130 -0
  148. data/assets/reveal.old/css/theme/serif.css +132 -0
  149. data/assets/reveal.old/css/theme/simple.css +132 -0
  150. data/assets/reveal.old/css/theme/sky.css +139 -0
  151. data/assets/reveal.old/css/theme/solarized.css +142 -0
  152. data/assets/reveal.old/css/theme/source/beige.scss +50 -0
  153. data/assets/reveal.old/css/theme/source/default.scss +42 -0
  154. data/assets/reveal.old/css/theme/source/moon.scss +68 -0
  155. data/assets/reveal.old/css/theme/source/night.scss +35 -0
  156. data/assets/reveal.old/css/theme/source/serif.scss +35 -0
  157. data/assets/reveal.old/css/theme/source/simple.scss +38 -0
  158. data/assets/reveal.old/css/theme/source/sky.scss +46 -0
  159. data/assets/reveal.old/css/theme/source/solarized.scss +74 -0
  160. data/assets/reveal.old/css/theme/template/mixins.scss +29 -0
  161. data/assets/reveal.old/css/theme/template/settings.scss +33 -0
  162. data/assets/reveal.old/css/theme/template/theme.scss +163 -0
  163. data/assets/{reveal.js → reveal.old}/js/head.min.js +0 -0
  164. data/assets/reveal.old/js/reveal.js +2577 -0
  165. data/assets/reveal.old/js/reveal.min.js +8 -0
  166. data/assets/reveal.old/lib/css/zenburn.css +115 -0
  167. data/assets/reveal.old/lib/font/league_gothic-webfont.eot +0 -0
  168. data/assets/reveal.old/lib/font/league_gothic-webfont.svg +230 -0
  169. data/assets/reveal.old/lib/font/league_gothic-webfont.ttf +0 -0
  170. data/assets/reveal.old/lib/font/league_gothic-webfont.woff +0 -0
  171. data/assets/reveal.old/lib/font/league_gothic_license +2 -0
  172. data/assets/reveal.old/lib/js/classList.js +2 -0
  173. data/assets/reveal.old/lib/js/head.min.js +8 -0
  174. data/assets/reveal.old/lib/js/html5shiv.js +7 -0
  175. data/assets/reveal.old/plugin/highlight/highlight.js +31 -0
  176. data/assets/reveal.old/plugin/leap/leap.js +154 -0
  177. data/assets/reveal.old/plugin/markdown/example.html +97 -0
  178. data/assets/reveal.old/plugin/markdown/example.md +29 -0
  179. data/assets/reveal.old/plugin/markdown/markdown.js +190 -0
  180. data/assets/reveal.old/plugin/markdown/marked.js +37 -0
  181. data/assets/reveal.old/plugin/multiplex/client.js +13 -0
  182. data/assets/reveal.old/plugin/multiplex/index.js +56 -0
  183. data/assets/reveal.old/plugin/multiplex/master.js +50 -0
  184. data/assets/reveal.old/plugin/notes-server/client.js +57 -0
  185. data/assets/reveal.old/plugin/notes-server/index.js +59 -0
  186. data/assets/reveal.old/plugin/notes-server/notes.html +142 -0
  187. data/assets/reveal.old/plugin/notes/notes.html +253 -0
  188. data/assets/reveal.old/plugin/notes/notes.js +100 -0
  189. data/assets/reveal.old/plugin/postmessage/example.html +39 -0
  190. data/assets/reveal.old/plugin/postmessage/postmessage.js +42 -0
  191. data/assets/reveal.old/plugin/print-pdf/print-pdf.js +44 -0
  192. data/assets/reveal.old/plugin/remotes/remotes.js +39 -0
  193. data/assets/reveal.old/plugin/search/search.js +196 -0
  194. data/assets/reveal.old/plugin/zoom-js/zoom.js +256 -0
  195. data/bin/slippery +1 -0
  196. data/code_of_conduct.md +32 -0
  197. data/lib/slippery.rb +5 -3
  198. data/lib/slippery/document.rb +2 -2
  199. data/lib/slippery/presentation.rb +10 -1
  200. data/lib/slippery/processor.rb +18 -0
  201. data/lib/slippery/processor_helpers.rb +16 -2
  202. data/lib/slippery/processors/add_highlight.rb +9 -2
  203. data/lib/slippery/processors/deck_js.rb +81 -0
  204. data/lib/slippery/processors/fathom_js.rb +31 -0
  205. data/lib/slippery/processors/impress_js/add_impress_js.rb +2 -2
  206. data/lib/slippery/processors/jquery.rb +12 -0
  207. data/lib/slippery/processors/reveal_js/add_reveal_js.rb +12 -9
  208. data/lib/slippery/rake_tasks.rb +59 -31
  209. data/lib/slippery/version.rb +1 -1
  210. data/slippery.gemspec +5 -4
  211. metadata +215 -33
  212. data/lib/slippery/converter.rb +0 -132
@@ -12,7 +12,7 @@ body {
12
12
  .reveal {
13
13
  font-family: $mainFont;
14
14
  font-size: $mainFontSize;
15
- font-weight: 200;
15
+ font-weight: normal;
16
16
  letter-spacing: -0.02em;
17
17
  color: $mainColor;
18
18
  }
@@ -33,7 +33,7 @@ body {
33
33
  .reveal h4,
34
34
  .reveal h5,
35
35
  .reveal h6 {
36
- margin: 0 0 20px 0;
36
+ margin: $headingMargin;
37
37
  color: $headingColor;
38
38
 
39
39
  font-family: $headingFont;
@@ -160,4 +160,11 @@ body {
160
160
  transition: width 800ms cubic-bezier(0.260, 0.860, 0.440, 0.985);
161
161
  }
162
162
 
163
+ /*********************************************
164
+ * SLIDE NUMBER
165
+ *********************************************/
166
+ .reveal .slide-number {
167
+ color: $linkColor;
168
+ }
169
+
163
170
 
@@ -3,7 +3,7 @@
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2013 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2014 Hakim El Hattab, http://hakim.se
7
7
  */
8
8
  var Reveal = (function(){
9
9
 
@@ -12,7 +12,7 @@ var Reveal = (function(){
12
12
  var SLIDES_SELECTOR = '.reveal .slides section',
13
13
  HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
14
  VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-child',
15
+ HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
16
16
 
17
17
  // Configurations defaults, can be overridden at initialization time
18
18
  config = {
@@ -35,6 +35,9 @@ var Reveal = (function(){
35
35
  // Display a presentation progress bar
36
36
  progress: true,
37
37
 
38
+ // Display the page number of the current slide
39
+ slideNumber: false,
40
+
38
41
  // Push each slide change to the browser history
39
42
  history: false,
40
43
 
@@ -44,7 +47,7 @@ var Reveal = (function(){
44
47
  // Enable the slide overview mode
45
48
  overview: true,
46
49
 
47
- // Vertical centring of slides
50
+ // Vertical centering of slides
48
51
  center: true,
49
52
 
50
53
  // Enables touch navigation on devices with touch input
@@ -59,20 +62,33 @@ var Reveal = (function(){
59
62
  // Turns fragments on and off globally
60
63
  fragments: true,
61
64
 
65
+ // Flags if the presentation is running in an embedded mode,
66
+ // i.e. contained within a limited portion of the screen
67
+ embedded: false,
68
+
62
69
  // Number of milliseconds between automatically proceeding to the
63
70
  // next slide, disabled when set to 0, this value can be overwritten
64
71
  // by using a data-autoslide attribute on your slides
65
72
  autoSlide: 0,
66
73
 
74
+ // Stop auto-sliding after user input
75
+ autoSlideStoppable: true,
76
+
67
77
  // Enable slide navigation via mouse wheel
68
78
  mouseWheel: false,
69
79
 
70
80
  // Apply a 3D roll to links on hover
71
- rollingLinks: true,
81
+ rollingLinks: false,
82
+
83
+ // Hides the address bar on mobile devices
84
+ hideAddressBar: true,
72
85
 
73
86
  // Opens links in an iframe preview overlay
74
87
  previewLinks: false,
75
88
 
89
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
+ focusBodyOnPageVisiblityChange: true,
91
+
76
92
  // Theme (see /css/theme)
77
93
  theme: null,
78
94
 
@@ -83,23 +99,35 @@ var Reveal = (function(){
83
99
  transitionSpeed: 'default', // default/fast/slow
84
100
 
85
101
  // Transition style for full page slide backgrounds
86
- backgroundTransition: 'default', // default/linear
102
+ backgroundTransition: 'default', // default/linear/none
103
+
104
+ // Parallax background image
105
+ parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
106
+
107
+ // Parallax background size
108
+ parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
109
+
110
+ // Number of slides away from the current that are visible
111
+ viewDistance: 3,
87
112
 
88
113
  // Script dependencies to load
89
114
  dependencies: []
115
+
90
116
  },
91
117
 
92
- // The current auto-slide duration
93
- autoSlide = 0,
118
+ // Flags if reveal.js is loaded (has dispatched the 'ready' event)
119
+ loaded = false,
94
120
 
95
121
  // The horizontal and vertical index of the currently active slide
96
- indexh = 0,
97
- indexv = 0,
122
+ indexh,
123
+ indexv,
98
124
 
99
125
  // The previous and current slide HTML elements
100
126
  previousSlide,
101
127
  currentSlide,
102
128
 
129
+ previousBackground,
130
+
103
131
  // Slides may hold a data-state attribute which we pick up and apply
104
132
  // as a class to the body. This list contains the combined state of
105
133
  // all current slides.
@@ -111,26 +139,15 @@ var Reveal = (function(){
111
139
  // Cached references to DOM elements
112
140
  dom = {},
113
141
 
114
- // Detect support for CSS 3D transforms
115
- supports3DTransforms = 'WebkitPerspective' in document.body.style ||
116
- 'MozPerspective' in document.body.style ||
117
- 'msPerspective' in document.body.style ||
118
- 'OPerspective' in document.body.style ||
119
- 'perspective' in document.body.style,
142
+ // Features supported by the browser, see #checkCapabilities()
143
+ features = {},
120
144
 
121
- // Detect support for CSS 2D transforms
122
- supports2DTransforms = 'WebkitTransform' in document.body.style ||
123
- 'MozTransform' in document.body.style ||
124
- 'msTransform' in document.body.style ||
125
- 'OTransform' in document.body.style ||
126
- 'transform' in document.body.style,
145
+ // Client is a mobile device, see #checkCapabilities()
146
+ isMobileDevice,
127
147
 
128
148
  // Throttles mouse wheel navigation
129
149
  lastMouseWheelStep = 0,
130
150
 
131
- // An interval used to automatically move on to the next slide
132
- autoSlideTimeout = 0,
133
-
134
151
  // Delays updates to the URL due to a Chrome thumbnailer bug
135
152
  writeURLTimeout = 0,
136
153
 
@@ -143,14 +160,23 @@ var Reveal = (function(){
143
160
  // Flags if the interaction event listeners are bound
144
161
  eventsAreBound = false,
145
162
 
163
+ // The current auto-slide duration
164
+ autoSlide = 0,
165
+
166
+ // Auto slide properties
167
+ autoSlidePlayer,
168
+ autoSlideTimeout = 0,
169
+ autoSlideStartTime = -1,
170
+ autoSlidePaused = false,
171
+
146
172
  // Holds information about the currently ongoing touch input
147
173
  touch = {
148
174
  startX: 0,
149
175
  startY: 0,
150
176
  startSpan: 0,
151
177
  startCount: 0,
152
- handled: false,
153
- threshold: 80
178
+ captured: false,
179
+ threshold: 40
154
180
  };
155
181
 
156
182
  /**
@@ -158,7 +184,9 @@ var Reveal = (function(){
158
184
  */
159
185
  function initialize( options ) {
160
186
 
161
- if( !supports2DTransforms && !supports3DTransforms ) {
187
+ checkCapabilities();
188
+
189
+ if( !features.transforms2d && !features.transforms3d ) {
162
190
  document.body.setAttribute( 'class', 'no-transforms' );
163
191
 
164
192
  // If the browser doesn't support core features we won't be
@@ -169,8 +197,15 @@ var Reveal = (function(){
169
197
  // Force a layout when the whole page, incl fonts, has loaded
170
198
  window.addEventListener( 'load', layout, false );
171
199
 
200
+ var query = Reveal.getQueryHash();
201
+
202
+ // Do not accept new dependencies via query config to avoid
203
+ // the potential of malicious script injection
204
+ if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
205
+
172
206
  // Copy options over to our config object
173
207
  extend( config, options );
208
+ extend( config, query );
174
209
 
175
210
  // Hide the address bar in mobile browsers
176
211
  hideAddressBar();
@@ -180,6 +215,136 @@ var Reveal = (function(){
180
215
 
181
216
  }
182
217
 
218
+ /**
219
+ * Inspect the client to see what it's capable of, this
220
+ * should only happens once per runtime.
221
+ */
222
+ function checkCapabilities() {
223
+
224
+ features.transforms3d = 'WebkitPerspective' in document.body.style ||
225
+ 'MozPerspective' in document.body.style ||
226
+ 'msPerspective' in document.body.style ||
227
+ 'OPerspective' in document.body.style ||
228
+ 'perspective' in document.body.style;
229
+
230
+ features.transforms2d = 'WebkitTransform' in document.body.style ||
231
+ 'MozTransform' in document.body.style ||
232
+ 'msTransform' in document.body.style ||
233
+ 'OTransform' in document.body.style ||
234
+ 'transform' in document.body.style;
235
+
236
+ features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
237
+ features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
238
+
239
+ features.canvas = !!document.createElement( 'canvas' ).getContext;
240
+
241
+ isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
242
+
243
+ }
244
+
245
+
246
+ /**
247
+ * Loads the dependencies of reveal.js. Dependencies are
248
+ * defined via the configuration option 'dependencies'
249
+ * and will be loaded prior to starting/binding reveal.js.
250
+ * Some dependencies may have an 'async' flag, if so they
251
+ * will load after reveal.js has been started up.
252
+ */
253
+ function load() {
254
+
255
+ var scripts = [],
256
+ scriptsAsync = [],
257
+ scriptsToPreload = 0;
258
+
259
+ // Called once synchronous scripts finish loading
260
+ function proceed() {
261
+ if( scriptsAsync.length ) {
262
+ // Load asynchronous scripts
263
+ head.js.apply( null, scriptsAsync );
264
+ }
265
+
266
+ start();
267
+ }
268
+
269
+ function loadScript( s ) {
270
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
271
+ // Extension may contain callback functions
272
+ if( typeof s.callback === 'function' ) {
273
+ s.callback.apply( this );
274
+ }
275
+
276
+ if( --scriptsToPreload === 0 ) {
277
+ proceed();
278
+ }
279
+ });
280
+ }
281
+
282
+ for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
283
+ var s = config.dependencies[i];
284
+
285
+ // Load if there's no condition or the condition is truthy
286
+ if( !s.condition || s.condition() ) {
287
+ if( s.async ) {
288
+ scriptsAsync.push( s.src );
289
+ }
290
+ else {
291
+ scripts.push( s.src );
292
+ }
293
+
294
+ loadScript( s );
295
+ }
296
+ }
297
+
298
+ if( scripts.length ) {
299
+ scriptsToPreload = scripts.length;
300
+
301
+ // Load synchronous scripts
302
+ head.js.apply( null, scripts );
303
+ }
304
+ else {
305
+ proceed();
306
+ }
307
+
308
+ }
309
+
310
+ /**
311
+ * Starts up reveal.js by binding input events and navigating
312
+ * to the current URL deeplink if there is one.
313
+ */
314
+ function start() {
315
+
316
+ // Make sure we've got all the DOM elements we need
317
+ setupDOM();
318
+
319
+ // Resets all vertical slides so that only the first is visible
320
+ resetVerticalSlides();
321
+
322
+ // Updates the presentation to match the current configuration values
323
+ configure();
324
+
325
+ // Read the initial hash
326
+ readURL();
327
+
328
+ // Update all backgrounds
329
+ updateBackground( true );
330
+
331
+ // Notify listeners that the presentation is ready but use a 1ms
332
+ // timeout to ensure it's not fired synchronously after #initialize()
333
+ setTimeout( function() {
334
+ // Enable transitions now that we're loaded
335
+ dom.slides.classList.remove( 'no-transition' );
336
+
337
+ loaded = true;
338
+
339
+ dispatchEvent( 'ready', {
340
+ 'indexh': indexh,
341
+ 'indexv': indexv,
342
+ 'currentSlide': currentSlide
343
+ } );
344
+ }, 1 );
345
+
346
+ }
347
+
183
348
  /**
184
349
  * Finds and stores references to DOM elements which are
185
350
  * required by the presentation. If a required element is
@@ -192,61 +357,62 @@ var Reveal = (function(){
192
357
  dom.wrapper = document.querySelector( '.reveal' );
193
358
  dom.slides = document.querySelector( '.reveal .slides' );
194
359
 
360
+ // Prevent transitions while we're loading
361
+ dom.slides.classList.add( 'no-transition' );
362
+
195
363
  // Background element
196
- if( !document.querySelector( '.reveal .backgrounds' ) ) {
197
- dom.background = document.createElement( 'div' );
198
- dom.background.classList.add( 'backgrounds' );
199
- dom.wrapper.appendChild( dom.background );
200
- }
364
+ dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
201
365
 
202
366
  // Progress bar
203
- if( !dom.wrapper.querySelector( '.progress' ) ) {
204
- var progressElement = document.createElement( 'div' );
205
- progressElement.classList.add( 'progress' );
206
- progressElement.innerHTML = '<span></span>';
207
- dom.wrapper.appendChild( progressElement );
208
- }
367
+ dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );
368
+ dom.progressbar = dom.progress.querySelector( 'span' );
209
369
 
210
370
  // Arrow controls
211
- if( !dom.wrapper.querySelector( '.controls' ) ) {
212
- var controlsElement = document.createElement( 'aside' );
213
- controlsElement.classList.add( 'controls' );
214
- controlsElement.innerHTML = '<div class="navigate-left"></div>' +
215
- '<div class="navigate-right"></div>' +
216
- '<div class="navigate-up"></div>' +
217
- '<div class="navigate-down"></div>';
218
- dom.wrapper.appendChild( controlsElement );
219
- }
371
+ createSingletonNode( dom.wrapper, 'aside', 'controls',
372
+ '<div class="navigate-left"></div>' +
373
+ '<div class="navigate-right"></div>' +
374
+ '<div class="navigate-up"></div>' +
375
+ '<div class="navigate-down"></div>' );
376
+
377
+ // Slide number
378
+ dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
220
379
 
221
380
  // State background element [DEPRECATED]
222
- if( !dom.wrapper.querySelector( '.state-background' ) ) {
223
- var stateBackgroundElement = document.createElement( 'div' );
224
- stateBackgroundElement.classList.add( 'state-background' );
225
- dom.wrapper.appendChild( stateBackgroundElement );
226
- }
381
+ createSingletonNode( dom.wrapper, 'div', 'state-background', null );
227
382
 
228
383
  // Overlay graphic which is displayed during the paused mode
229
- if( !dom.wrapper.querySelector( '.pause-overlay' ) ) {
230
- var pausedElement = document.createElement( 'div' );
231
- pausedElement.classList.add( 'pause-overlay' );
232
- dom.wrapper.appendChild( pausedElement );
233
- }
384
+ createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
234
385
 
235
386
  // Cache references to elements
236
- dom.progress = document.querySelector( '.reveal .progress' );
237
- dom.progressbar = document.querySelector( '.reveal .progress span' );
387
+ dom.controls = document.querySelector( '.reveal .controls' );
388
+
389
+ // There can be multiple instances of controls throughout the page
390
+ dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
391
+ dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
392
+ dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
393
+ dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
394
+ dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
+ dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
+
397
+ }
238
398
 
239
- if ( config.controls ) {
240
- dom.controls = document.querySelector( '.reveal .controls' );
399
+ /**
400
+ * Creates an HTML element and returns a reference to it.
401
+ * If the element already exists the existing instance will
402
+ * be returned.
403
+ */
404
+ function createSingletonNode( container, tagname, classname, innerHTML ) {
241
405
 
242
- // There can be multiple instances of controls throughout the page
243
- dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
244
- dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
245
- dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
246
- dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
247
- dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
248
- dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
406
+ var node = container.querySelector( '.' + classname );
407
+ if( !node ) {
408
+ node = document.createElement( tagname );
409
+ node.classList.add( classname );
410
+ if( innerHTML !== null ) {
411
+ node.innerHTML = innerHTML;
412
+ }
413
+ container.appendChild( node );
249
414
  }
415
+ return node;
250
416
 
251
417
  }
252
418
 
@@ -284,7 +450,7 @@ var Reveal = (function(){
284
450
 
285
451
  if( data.background ) {
286
452
  // Auto-wrap image urls in url(...)
287
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
453
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
288
454
  element.style.backgroundImage = 'url('+ data.background +')';
289
455
  }
290
456
  else {
@@ -292,6 +458,10 @@ var Reveal = (function(){
292
458
  }
293
459
  }
294
460
 
461
+ if( data.background || data.backgroundColor || data.backgroundImage ) {
462
+ element.setAttribute( 'data-background-hash', data.background + data.backgroundSize + data.backgroundImage + data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + data.backgroundTransition );
463
+ }
464
+
295
465
  // Additional and optional background properties
296
466
  if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
297
467
  if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
@@ -332,98 +502,27 @@ var Reveal = (function(){
332
502
 
333
503
  } );
334
504
 
335
- }
505
+ // Add parallax background if specified
506
+ if( config.parallaxBackgroundImage ) {
336
507
 
337
- /**
338
- * Hides the address bar if we're on a mobile device.
339
- */
340
- function hideAddressBar() {
508
+ dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
509
+ dom.background.style.backgroundSize = config.parallaxBackgroundSize;
341
510
 
342
- if( /iphone|ipod|android/gi.test( navigator.userAgent ) && !/crios/gi.test( navigator.userAgent ) ) {
343
- // Events that should trigger the address bar to hide
344
- window.addEventListener( 'load', removeAddressBar, false );
345
- window.addEventListener( 'orientationchange', removeAddressBar, false );
346
- }
511
+ // Make sure the below properties are set on the element - these properties are
512
+ // needed for proper transitions to be set on the element via CSS. To remove
513
+ // annoying background slide-in effect when the presentation starts, apply
514
+ // these properties after short time delay
515
+ setTimeout( function() {
516
+ dom.wrapper.classList.add( 'has-parallax-background' );
517
+ }, 1 );
347
518
 
348
- }
349
-
350
- /**
351
- * Loads the dependencies of reveal.js. Dependencies are
352
- * defined via the configuration option 'dependencies'
353
- * and will be loaded prior to starting/binding reveal.js.
354
- * Some dependencies may have an 'async' flag, if so they
355
- * will load after reveal.js has been started up.
356
- */
357
- function load() {
358
-
359
- var scripts = [],
360
- scriptsAsync = [];
361
-
362
- for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
363
- var s = config.dependencies[i];
364
-
365
- // Load if there's no condition or the condition is truthy
366
- if( !s.condition || s.condition() ) {
367
- if( s.async ) {
368
- scriptsAsync.push( s.src );
369
- }
370
- else {
371
- scripts.push( s.src );
372
- }
373
-
374
- // Extension may contain callback functions
375
- if( typeof s.callback === 'function' ) {
376
- head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback );
377
- }
378
- }
379
- }
380
-
381
- // Called once synchronous scripts finish loading
382
- function proceed() {
383
- if( scriptsAsync.length ) {
384
- // Load asynchronous scripts
385
- head.js.apply( null, scriptsAsync );
386
- }
387
-
388
- start();
389
- }
390
-
391
- if( scripts.length ) {
392
- head.ready( proceed );
393
-
394
- // Load synchronous scripts
395
- head.js.apply( null, scripts );
396
519
  }
397
520
  else {
398
- proceed();
399
- }
400
-
401
- }
402
-
403
- /**
404
- * Starts up reveal.js by binding input events and navigating
405
- * to the current URL deeplink if there is one.
406
- */
407
- function start() {
408
-
409
- // Make sure we've got all the DOM elements we need
410
- setupDOM();
411
521
 
412
- // Updates the presentation to match the current configuration values
413
- configure();
522
+ dom.background.style.backgroundImage = '';
523
+ dom.wrapper.classList.remove( 'has-parallax-background' );
414
524
 
415
- // Read the initial hash
416
- readURL();
417
-
418
- // Notify listeners that the presentation is ready but use a 1ms
419
- // timeout to ensure it's not fired synchronously after #initialize()
420
- setTimeout( function() {
421
- dispatchEvent( 'ready', {
422
- 'indexh': indexh,
423
- 'indexv': indexv,
424
- 'currentSlide': currentSlide
425
- } );
426
- }, 1 );
525
+ }
427
526
 
428
527
  }
429
528
 
@@ -433,6 +532,8 @@ var Reveal = (function(){
433
532
  */
434
533
  function configure( options ) {
435
534
 
535
+ var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
536
+
436
537
  dom.wrapper.classList.remove( config.transition );
437
538
 
438
539
  // New config options may be passed when this method
@@ -440,20 +541,15 @@ var Reveal = (function(){
440
541
  if( typeof options === 'object' ) extend( config, options );
441
542
 
442
543
  // Force linear transition based on browser capabilities
443
- if( supports3DTransforms === false ) config.transition = 'linear';
544
+ if( features.transforms3d === false ) config.transition = 'linear';
444
545
 
445
546
  dom.wrapper.classList.add( config.transition );
446
547
 
447
548
  dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
448
549
  dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
449
550
 
450
- if( dom.controls ) {
451
- dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
452
- }
453
-
454
- if( dom.progress ) {
455
- dom.progress.style.display = ( config.progress && dom.progress ) ? 'block' : 'none';
456
- }
551
+ dom.controls.style.display = config.controls ? 'block' : 'none';
552
+ dom.progress.style.display = config.progress ? 'block' : 'none';
457
553
 
458
554
  if( config.rtl ) {
459
555
  dom.wrapper.classList.add( 'rtl' );
@@ -495,6 +591,20 @@ var Reveal = (function(){
495
591
  enablePreviewLinks( '[data-preview-link]' );
496
592
  }
497
593
 
594
+ // Auto-slide playback controls
595
+ if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
+ autoSlidePlayer = new Playback( dom.wrapper, function() {
597
+ return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
598
+ } );
599
+
600
+ autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
+ autoSlidePaused = false;
602
+ }
603
+ else if( autoSlidePlayer ) {
604
+ autoSlidePlayer.destroy();
605
+ autoSlidePlayer = null;
606
+ }
607
+
498
608
  // Load the theme in the config, if it's not already loaded
499
609
  if( config.theme && dom.theme ) {
500
610
  var themeURL = dom.theme.getAttribute( 'href' );
@@ -538,21 +648,37 @@ var Reveal = (function(){
538
648
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
539
649
  }
540
650
 
541
- if ( config.progress && dom.progress ) {
651
+ if( config.progress && dom.progress ) {
542
652
  dom.progress.addEventListener( 'click', onProgressClicked, false );
543
653
  }
544
654
 
545
- if ( config.controls && dom.controls ) {
546
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
547
- dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
548
- dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
549
- dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
550
- dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
551
- dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
552
- dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
553
- } );
655
+ if( config.focusBodyOnPageVisiblityChange ) {
656
+ var visibilityChange;
657
+
658
+ if( 'hidden' in document ) {
659
+ visibilityChange = 'visibilitychange';
660
+ }
661
+ else if( 'msHidden' in document ) {
662
+ visibilityChange = 'msvisibilitychange';
663
+ }
664
+ else if( 'webkitHidden' in document ) {
665
+ visibilityChange = 'webkitvisibilitychange';
666
+ }
667
+
668
+ if( visibilityChange ) {
669
+ document.addEventListener( visibilityChange, onPageVisibilityChange, false );
670
+ }
554
671
  }
555
672
 
673
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
674
+ dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
+ dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
+ dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
677
+ dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
678
+ dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
679
+ dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
680
+ } );
681
+
556
682
  }
557
683
 
558
684
  /**
@@ -580,16 +706,14 @@ var Reveal = (function(){
580
706
  dom.progress.removeEventListener( 'click', onProgressClicked, false );
581
707
  }
582
708
 
583
- if ( config.controls && dom.controls ) {
584
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
585
- dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
586
- dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
587
- dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
588
- dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
589
- dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
590
- dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
591
- } );
592
- }
709
+ [ 'touchstart', 'click' ].forEach( function( eventName ) {
710
+ dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
711
+ dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
712
+ dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
713
+ dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
714
+ dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
715
+ dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
716
+ } );
593
717
 
594
718
  }
595
719
 
@@ -630,6 +754,19 @@ var Reveal = (function(){
630
754
 
631
755
  }
632
756
 
757
+ /**
758
+ * Applies a CSS transform to the target element.
759
+ */
760
+ function transformElement( element, transform ) {
761
+
762
+ element.style.WebkitTransform = transform;
763
+ element.style.MozTransform = transform;
764
+ element.style.msTransform = transform;
765
+ element.style.OTransform = transform;
766
+ element.style.transform = transform;
767
+
768
+ }
769
+
633
770
  /**
634
771
  * Retrieves the height of the given element by looking
635
772
  * at the position and height of its immediate children.
@@ -665,6 +802,48 @@ var Reveal = (function(){
665
802
 
666
803
  }
667
804
 
805
+ /**
806
+ * Returns the remaining height within the parent of the
807
+ * target element after subtracting the height of all
808
+ * siblings.
809
+ *
810
+ * remaining height = [parent height] - [ siblings height]
811
+ */
812
+ function getRemainingHeight( element, height ) {
813
+
814
+ height = height || 0;
815
+
816
+ if( element ) {
817
+ var parent = element.parentNode;
818
+ var siblings = parent.childNodes;
819
+
820
+ // Subtract the height of each sibling
821
+ toArray( siblings ).forEach( function( sibling ) {
822
+
823
+ if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
824
+
825
+ var styles = window.getComputedStyle( sibling ),
826
+ marginTop = parseInt( styles.marginTop, 10 ),
827
+ marginBottom = parseInt( styles.marginBottom, 10 );
828
+
829
+ height -= sibling.offsetHeight + marginTop + marginBottom;
830
+
831
+ }
832
+
833
+ } );
834
+
835
+ var elementStyles = window.getComputedStyle( element );
836
+
837
+ // Subtract the margins of the target element
838
+ height -= parseInt( elementStyles.marginTop, 10 ) +
839
+ parseInt( elementStyles.marginBottom, 10 );
840
+
841
+ }
842
+
843
+ return height;
844
+
845
+ }
846
+
668
847
  /**
669
848
  * Checks if this instance is being used to print a PDF.
670
849
  */
@@ -674,21 +853,25 @@ var Reveal = (function(){
674
853
 
675
854
  }
676
855
 
856
+ /**
857
+ * Hides the address bar if we're on a mobile device.
858
+ */
859
+ function hideAddressBar() {
860
+
861
+ if( config.hideAddressBar && isMobileDevice ) {
862
+ // Events that should trigger the address bar to hide
863
+ window.addEventListener( 'load', removeAddressBar, false );
864
+ window.addEventListener( 'orientationchange', removeAddressBar, false );
865
+ }
866
+
867
+ }
868
+
677
869
  /**
678
870
  * Causes the address bar to hide on mobile devices,
679
871
  * more vertical space ftw.
680
872
  */
681
873
  function removeAddressBar() {
682
874
 
683
- if( window.orientation === 0 ) {
684
- document.documentElement.style.overflow = 'scroll';
685
- document.body.style.height = '120%';
686
- }
687
- else {
688
- document.documentElement.style.overflow = '';
689
- document.body.style.height = '100%';
690
- }
691
-
692
875
  setTimeout( function() {
693
876
  window.scrollTo( 0, 1 );
694
877
  }, 10 );
@@ -713,7 +896,7 @@ var Reveal = (function(){
713
896
  */
714
897
  function enableRollingLinks() {
715
898
 
716
- if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
899
+ if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
717
900
  var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
718
901
 
719
902
  for( var i = 0, len = anchors.length; i < len; i++ ) {
@@ -836,38 +1019,6 @@ var Reveal = (function(){
836
1019
 
837
1020
  }
838
1021
 
839
- /**
840
- * Return a sorted fragments list, ordered by an increasing
841
- * "data-fragment-index" attribute.
842
- *
843
- * Fragments will be revealed in the order that they are returned by
844
- * this function, so you can use the index attributes to control the
845
- * order of fragment appearance.
846
- *
847
- * To maintain a sensible default fragment order, fragments are presumed
848
- * to be passed in document order. This function adds a "fragment-index"
849
- * attribute to each node if such an attribute is not already present,
850
- * and sets that attribute to an integer value which is the position of
851
- * the fragment within the fragments list.
852
- */
853
- function sortFragments( fragments ) {
854
-
855
- var a = toArray( fragments );
856
-
857
- a.forEach( function( el, idx ) {
858
- if( !el.hasAttribute( 'data-fragment-index' ) ) {
859
- el.setAttribute( 'data-fragment-index', idx );
860
- }
861
- } );
862
-
863
- a.sort( function( l, r ) {
864
- return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
865
- } );
866
-
867
- return a;
868
-
869
- }
870
-
871
1022
  /**
872
1023
  * Applies JavaScript-controlled layout rules to the
873
1024
  * presentation.
@@ -886,7 +1037,11 @@ var Reveal = (function(){
886
1037
 
887
1038
  // Dimensions of the content
888
1039
  var slideWidth = config.width,
889
- slideHeight = config.height;
1040
+ slideHeight = config.height,
1041
+ slidePadding = 20; // TODO Dig this out of DOM
1042
+
1043
+ // Layout the contents of the slides
1044
+ layoutSlideContents( config.width, config.height, slidePadding );
890
1045
 
891
1046
  // Slide width may be a percentage of available width
892
1047
  if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
@@ -915,13 +1070,7 @@ var Reveal = (function(){
915
1070
  }
916
1071
  // Apply scale transform as a fallback
917
1072
  else {
918
- var transform = 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)';
919
-
920
- dom.slides.style.WebkitTransform = transform;
921
- dom.slides.style.MozTransform = transform;
922
- dom.slides.style.msTransform = transform;
923
- dom.slides.style.OTransform = transform;
924
- dom.slides.style.transform = transform;
1073
+ transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
925
1074
  }
926
1075
 
927
1076
  // Select all slides, vertical and horizontal
@@ -935,14 +1084,14 @@ var Reveal = (function(){
935
1084
  continue;
936
1085
  }
937
1086
 
938
- if( config.center ) {
1087
+ if( config.center || slide.classList.contains( 'center' ) ) {
939
1088
  // Vertical stacks are not centred since their section
940
1089
  // children will be
941
1090
  if( slide.classList.contains( 'stack' ) ) {
942
1091
  slide.style.top = 0;
943
1092
  }
944
1093
  else {
945
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - 20, -slideHeight / 2 ) + 'px';
1094
+ slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
946
1095
  }
947
1096
  }
948
1097
  else {
@@ -952,11 +1101,44 @@ var Reveal = (function(){
952
1101
  }
953
1102
 
954
1103
  updateProgress();
1104
+ updateParallax();
955
1105
 
956
1106
  }
957
1107
 
958
1108
  }
959
1109
 
1110
+ /**
1111
+ * Applies layout logic to the contents of all slides in
1112
+ * the presentation.
1113
+ */
1114
+ function layoutSlideContents( width, height, padding ) {
1115
+
1116
+ // Handle sizing of elements with the 'stretch' class
1117
+ toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
+
1119
+ // Determine how much vertical space we can use
1120
+ var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1121
+
1122
+ // Consider the aspect ratio of media elements
1123
+ if( /(img|video)/gi.test( element.nodeName ) ) {
1124
+ var nw = element.naturalWidth || element.videoWidth,
1125
+ nh = element.naturalHeight || element.videoHeight;
1126
+
1127
+ var es = Math.min( width / nw, remainingHeight / nh );
1128
+
1129
+ element.style.width = ( nw * es ) + 'px';
1130
+ element.style.height = ( nh * es ) + 'px';
1131
+
1132
+ }
1133
+ else {
1134
+ element.style.width = width + 'px';
1135
+ element.style.height = remainingHeight + 'px';
1136
+ }
1137
+
1138
+ } );
1139
+
1140
+ }
1141
+
960
1142
  /**
961
1143
  * Stores the vertical index of a stack so that the same
962
1144
  * vertical slide can be selected when navigating to and
@@ -1010,8 +1192,11 @@ var Reveal = (function(){
1010
1192
 
1011
1193
  var wasActive = dom.wrapper.classList.contains( 'overview' );
1012
1194
 
1195
+ // Vary the depth of the overview based on screen size
1196
+ var depth = window.innerWidth < 400 ? 1000 : 2500;
1197
+
1013
1198
  dom.wrapper.classList.add( 'overview' );
1014
- dom.wrapper.classList.remove( 'exit-overview' );
1199
+ dom.wrapper.classList.remove( 'overview-deactivating' );
1015
1200
 
1016
1201
  clearTimeout( activateOverviewTimeout );
1017
1202
  clearTimeout( deactivateOverviewTimeout );
@@ -1019,22 +1204,18 @@ var Reveal = (function(){
1019
1204
  // Not the pretties solution, but need to let the overview
1020
1205
  // class apply first so that slides are measured accurately
1021
1206
  // before we can position them
1022
- activateOverviewTimeout = setTimeout( function(){
1207
+ activateOverviewTimeout = setTimeout( function() {
1023
1208
 
1024
1209
  var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1025
1210
 
1026
1211
  for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1027
1212
  var hslide = horizontalSlides[i],
1028
- hoffset = config.rtl ? -105 : 105,
1029
- htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)';
1213
+ hoffset = config.rtl ? -105 : 105;
1030
1214
 
1031
1215
  hslide.setAttribute( 'data-index-h', i );
1032
- hslide.style.display = 'block';
1033
- hslide.style.WebkitTransform = htransform;
1034
- hslide.style.MozTransform = htransform;
1035
- hslide.style.msTransform = htransform;
1036
- hslide.style.OTransform = htransform;
1037
- hslide.style.transform = htransform;
1216
+
1217
+ // Apply CSS transform
1218
+ transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1038
1219
 
1039
1220
  if( hslide.classList.contains( 'stack' ) ) {
1040
1221
 
@@ -1043,17 +1224,13 @@ var Reveal = (function(){
1043
1224
  for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1044
1225
  var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1045
1226
 
1046
- var vslide = verticalSlides[j],
1047
- vtransform = 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)';
1227
+ var vslide = verticalSlides[j];
1048
1228
 
1049
1229
  vslide.setAttribute( 'data-index-h', i );
1050
1230
  vslide.setAttribute( 'data-index-v', j );
1051
- vslide.style.display = 'block';
1052
- vslide.style.WebkitTransform = vtransform;
1053
- vslide.style.MozTransform = vtransform;
1054
- vslide.style.msTransform = vtransform;
1055
- vslide.style.OTransform = vtransform;
1056
- vslide.style.transform = vtransform;
1231
+
1232
+ // Apply CSS transform
1233
+ transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1057
1234
 
1058
1235
  // Navigate to this slide on click
1059
1236
  vslide.addEventListener( 'click', onOverviewSlideClicked, true );
@@ -1068,6 +1245,8 @@ var Reveal = (function(){
1068
1245
  }
1069
1246
  }
1070
1247
 
1248
+ updateSlidesVisibility();
1249
+
1071
1250
  layout();
1072
1251
 
1073
1252
  if( !wasActive ) {
@@ -1102,29 +1281,19 @@ var Reveal = (function(){
1102
1281
  // Temporarily add a class so that transitions can do different things
1103
1282
  // depending on whether they are exiting/entering overview, or just
1104
1283
  // moving from slide to slide
1105
- dom.wrapper.classList.add( 'exit-overview' );
1284
+ dom.wrapper.classList.add( 'overview-deactivating' );
1106
1285
 
1107
1286
  deactivateOverviewTimeout = setTimeout( function () {
1108
- dom.wrapper.classList.remove( 'exit-overview' );
1109
- }, 10);
1287
+ dom.wrapper.classList.remove( 'overview-deactivating' );
1288
+ }, 1 );
1110
1289
 
1111
1290
  // Select all slides
1112
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1113
-
1114
- for( var i = 0, len = slides.length; i < len; i++ ) {
1115
- var element = slides[i];
1116
-
1117
- element.style.display = '';
1118
-
1291
+ toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1119
1292
  // Resets all transforms to use the external styles
1120
- element.style.WebkitTransform = '';
1121
- element.style.MozTransform = '';
1122
- element.style.msTransform = '';
1123
- element.style.OTransform = '';
1124
- element.style.transform = '';
1293
+ transformElement( slide, '' );
1125
1294
 
1126
- element.removeEventListener( 'click', onOverviewSlideClicked, true );
1127
- }
1295
+ slide.removeEventListener( 'click', onOverviewSlideClicked, true );
1296
+ } );
1128
1297
 
1129
1298
  slide( indexh, indexv );
1130
1299
 
@@ -1182,7 +1351,7 @@ var Reveal = (function(){
1182
1351
  // Prefer slide argument, otherwise use current slide
1183
1352
  slide = slide ? slide : currentSlide;
1184
1353
 
1185
- return slide && !!slide.parentNode.nodeName.match( /section/i );
1354
+ return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );
1186
1355
 
1187
1356
  }
1188
1357
 
@@ -1302,13 +1471,16 @@ var Reveal = (function(){
1302
1471
  // Reset the state array
1303
1472
  state.length = 0;
1304
1473
 
1305
- var indexhBefore = indexh,
1306
- indexvBefore = indexv;
1474
+ var indexhBefore = indexh || 0,
1475
+ indexvBefore = indexv || 0;
1307
1476
 
1308
1477
  // Activate and transition to the new slide
1309
1478
  indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
1310
1479
  indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
1311
1480
 
1481
+ // Update the visibility of slides now that the indices have changed
1482
+ updateSlidesVisibility();
1483
+
1312
1484
  layout();
1313
1485
 
1314
1486
  // Apply the new state
@@ -1338,10 +1510,6 @@ var Reveal = (function(){
1338
1510
  activateOverview();
1339
1511
  }
1340
1512
 
1341
- // Update the URL hash after a delay since updating it mid-transition
1342
- // is likely to cause visual lag
1343
- writeURL( 1500 );
1344
-
1345
1513
  // Find the current horizontal slide and any possible vertical slides
1346
1514
  // within it
1347
1515
  var currentHorizontalSlide = horizontalSlides[ indexh ],
@@ -1350,19 +1518,9 @@ var Reveal = (function(){
1350
1518
  // Store references to the previous and current slides
1351
1519
  currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
1352
1520
 
1353
-
1354
1521
  // Show fragment, if specified
1355
1522
  if( typeof f !== 'undefined' ) {
1356
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
1357
-
1358
- toArray( fragments ).forEach( function( fragment, indexf ) {
1359
- if( indexf < f ) {
1360
- fragment.classList.add( 'visible' );
1361
- }
1362
- else {
1363
- fragment.classList.remove( 'visible' );
1364
- }
1365
- } );
1523
+ navigateFragment( f );
1366
1524
  }
1367
1525
 
1368
1526
  // Dispatch an event if the slide changed
@@ -1412,6 +1570,13 @@ var Reveal = (function(){
1412
1570
  updateControls();
1413
1571
  updateProgress();
1414
1572
  updateBackground();
1573
+ updateParallax();
1574
+ updateSlideNumber();
1575
+
1576
+ // Update the URL hash
1577
+ writeURL();
1578
+
1579
+ cueAutoSlide();
1415
1580
 
1416
1581
  }
1417
1582
 
@@ -1438,9 +1603,58 @@ var Reveal = (function(){
1438
1603
  // Re-create the slide backgrounds
1439
1604
  createBackgrounds();
1440
1605
 
1606
+ sortAllFragments();
1607
+
1441
1608
  updateControls();
1442
1609
  updateProgress();
1443
- updateBackground();
1610
+ updateBackground( true );
1611
+ updateSlideNumber();
1612
+
1613
+ }
1614
+
1615
+ /**
1616
+ * Resets all vertical slides so that only the first
1617
+ * is visible.
1618
+ */
1619
+ function resetVerticalSlides() {
1620
+
1621
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
+ horizontalSlides.forEach( function( horizontalSlide ) {
1623
+
1624
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1625
+ verticalSlides.forEach( function( verticalSlide, y ) {
1626
+
1627
+ if( y > 0 ) {
1628
+ verticalSlide.classList.remove( 'present' );
1629
+ verticalSlide.classList.remove( 'past' );
1630
+ verticalSlide.classList.add( 'future' );
1631
+ }
1632
+
1633
+ } );
1634
+
1635
+ } );
1636
+
1637
+ }
1638
+
1639
+ /**
1640
+ * Sorts and formats all of fragments in the
1641
+ * presentation.
1642
+ */
1643
+ function sortAllFragments() {
1644
+
1645
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
+ horizontalSlides.forEach( function( horizontalSlide ) {
1647
+
1648
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1649
+ verticalSlides.forEach( function( verticalSlide, y ) {
1650
+
1651
+ sortFragments( verticalSlide.querySelectorAll( '.fragment' ) );
1652
+
1653
+ } );
1654
+
1655
+ if( verticalSlides.length === 0 ) sortFragments( horizontalSlide.querySelectorAll( '.fragment' ) );
1656
+
1657
+ } );
1444
1658
 
1445
1659
  }
1446
1660
 
@@ -1481,16 +1695,6 @@ var Reveal = (function(){
1481
1695
  for( var i = 0; i < slidesLength; i++ ) {
1482
1696
  var element = slides[i];
1483
1697
 
1484
- // Optimization; hide all slides that are three or more steps
1485
- // away from the present slide
1486
- if( isOverview() === false ) {
1487
- // The distance loops so that it measures 1 between the first
1488
- // and last slides
1489
- var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
1490
-
1491
- element.style.display = distance > 3 ? 'none' : 'block';
1492
- }
1493
-
1494
1698
  var reverse = config.rtl && !isVerticalSlide( element );
1495
1699
 
1496
1700
  element.classList.remove( 'past' );
@@ -1503,10 +1707,28 @@ var Reveal = (function(){
1503
1707
  if( i < index ) {
1504
1708
  // Any element previous to index is given the 'past' class
1505
1709
  element.classList.add( reverse ? 'future' : 'past' );
1710
+
1711
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
+
1713
+ // Show all fragments on prior slides
1714
+ while( pastFragments.length ) {
1715
+ var pastFragment = pastFragments.pop();
1716
+ pastFragment.classList.add( 'visible' );
1717
+ pastFragment.classList.remove( 'current-fragment' );
1718
+ }
1506
1719
  }
1507
1720
  else if( i > index ) {
1508
1721
  // Any element subsequent to index is given the 'future' class
1509
1722
  element.classList.add( reverse ? 'past' : 'future' );
1723
+
1724
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
+
1726
+ // No fragments in future slides should be visible ahead of time
1727
+ while( futureFragments.length ) {
1728
+ var futureFragment = futureFragments.pop();
1729
+ futureFragment.classList.remove( 'visible' );
1730
+ futureFragment.classList.remove( 'current-fragment' );
1731
+ }
1510
1732
  }
1511
1733
 
1512
1734
  // If this element contains vertical slides
@@ -1526,16 +1748,6 @@ var Reveal = (function(){
1526
1748
  state = state.concat( slideState.split( ' ' ) );
1527
1749
  }
1528
1750
 
1529
- // If this slide has a data-autoslide attribtue associated use this as
1530
- // autoSlide value otherwise use the global configured time
1531
- var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
1532
- if( slideAutoSlide ) {
1533
- autoSlide = parseInt( slideAutoSlide, 10 );
1534
- }
1535
- else {
1536
- autoSlide = config.autoSlide;
1537
- }
1538
-
1539
1751
  }
1540
1752
  else {
1541
1753
  // Since there are no slides we can't be anywhere beyond the
@@ -1547,6 +1759,61 @@ var Reveal = (function(){
1547
1759
 
1548
1760
  }
1549
1761
 
1762
+ /**
1763
+ * Optimization method; hide all slides that are far away
1764
+ * from the present slide.
1765
+ */
1766
+ function updateSlidesVisibility() {
1767
+
1768
+ // Select all slides and convert the NodeList result to
1769
+ // an array
1770
+ var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
+ horizontalSlidesLength = horizontalSlides.length,
1772
+ distanceX,
1773
+ distanceY;
1774
+
1775
+ if( horizontalSlidesLength ) {
1776
+
1777
+ // The number of steps away from the present slide that will
1778
+ // be visible
1779
+ var viewDistance = isOverview() ? 10 : config.viewDistance;
1780
+
1781
+ // Limit view distance on weaker devices
1782
+ if( isMobileDevice ) {
1783
+ viewDistance = isOverview() ? 6 : 1;
1784
+ }
1785
+
1786
+ for( var x = 0; x < horizontalSlidesLength; x++ ) {
1787
+ var horizontalSlide = horizontalSlides[x];
1788
+
1789
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
1790
+ verticalSlidesLength = verticalSlides.length;
1791
+
1792
+ // Loops so that it measures 1 between the first and last slides
1793
+ distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
1794
+
1795
+ // Show the horizontal slide if it's within the view distance
1796
+ horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
1797
+
1798
+ if( verticalSlidesLength ) {
1799
+
1800
+ var oy = getPreviousVerticalIndex( horizontalSlide );
1801
+
1802
+ for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
+ var verticalSlide = verticalSlides[y];
1804
+
1805
+ distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
1806
+
1807
+ verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
1808
+ }
1809
+
1810
+ }
1811
+ }
1812
+
1813
+ }
1814
+
1815
+ }
1816
+
1550
1817
  /**
1551
1818
  * Updates the progress bar to reflect the current slide.
1552
1819
  */
@@ -1597,52 +1864,68 @@ var Reveal = (function(){
1597
1864
  }
1598
1865
 
1599
1866
  /**
1600
- * Updates the state of all control/navigation arrows.
1867
+ * Updates the slide number div to reflect the current slide.
1601
1868
  */
1602
- function updateControls() {
1869
+ function updateSlideNumber() {
1603
1870
 
1604
- if ( config.controls && dom.controls ) {
1871
+ // Update slide number if enabled
1872
+ if( config.slideNumber && dom.slideNumber) {
1605
1873
 
1606
- var routes = availableRoutes();
1607
- var fragments = availableFragments();
1874
+ // Display the number of the page using 'indexh - indexv' format
1875
+ var indexString = indexh;
1876
+ if( indexv > 0 ) {
1877
+ indexString += ' - ' + indexv;
1878
+ }
1608
1879
 
1609
- // Remove the 'enabled' class from all directions
1610
- dom.controlsLeft.concat( dom.controlsRight )
1611
- .concat( dom.controlsUp )
1612
- .concat( dom.controlsDown )
1613
- .concat( dom.controlsPrev )
1614
- .concat( dom.controlsNext ).forEach( function( node ) {
1615
- node.classList.remove( 'enabled' );
1616
- node.classList.remove( 'fragmented' );
1617
- } );
1880
+ dom.slideNumber.innerHTML = indexString;
1881
+ }
1618
1882
 
1619
- // Add the 'enabled' class to the available routes
1620
- if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1621
- if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1622
- if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1623
- if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1883
+ }
1624
1884
 
1625
- // Prev/next buttons
1626
- if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1627
- if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1885
+ /**
1886
+ * Updates the state of all control/navigation arrows.
1887
+ */
1888
+ function updateControls() {
1628
1889
 
1629
- // Highlight fragment directions
1630
- if( currentSlide ) {
1890
+ var routes = availableRoutes();
1891
+ var fragments = availableFragments();
1892
+
1893
+ // Remove the 'enabled' class from all directions
1894
+ dom.controlsLeft.concat( dom.controlsRight )
1895
+ .concat( dom.controlsUp )
1896
+ .concat( dom.controlsDown )
1897
+ .concat( dom.controlsPrev )
1898
+ .concat( dom.controlsNext ).forEach( function( node ) {
1899
+ node.classList.remove( 'enabled' );
1900
+ node.classList.remove( 'fragmented' );
1901
+ } );
1631
1902
 
1632
- // Always apply fragment decorator to prev/next buttons
1633
- if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1634
- if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1903
+ // Add the 'enabled' class to the available routes
1904
+ if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1905
+ if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1906
+ if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1907
+ if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1635
1908
 
1636
- // Apply fragment decorators to directional buttons based on
1637
- // what slide axis they are in
1638
- if( isVerticalSlide( currentSlide ) ) {
1639
- if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1640
- if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1641
- }
1642
- else {
1643
- if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1644
- if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1645
- }
1909
+ // Prev/next buttons
1910
+ if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1911
+ if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
1912
+
1913
+ // Highlight fragment directions
1914
+ if( currentSlide ) {
1915
+
1916
+ // Always apply fragment decorator to prev/next buttons
1917
+ if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1918
+ if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1919
+
1920
+ // Apply fragment decorators to directional buttons based on
1921
+ // what slide axis they are in
1922
+ if( isVerticalSlide( currentSlide ) ) {
1923
+ if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1924
+ if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1925
+ }
1926
+ else {
1927
+ if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1928
+ if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
1646
1929
  }
1647
1930
 
1648
1931
  }
@@ -1650,33 +1933,110 @@ var Reveal = (function(){
1650
1933
  }
1651
1934
 
1652
1935
  /**
1653
- * Updates the background elements to reflect the current
1936
+ * Updates the background elements to reflect the current
1654
1937
  * slide.
1938
+ *
1939
+ * @param {Boolean} includeAll If true, the backgrounds of
1940
+ * all vertical slides (not just the present) will be updated.
1655
1941
  */
1656
- function updateBackground() {
1942
+ function updateBackground( includeAll ) {
1943
+
1944
+ var currentBackground = null;
1945
+
1946
+ // Reverse past/future classes when in RTL mode
1947
+ var horizontalPast = config.rtl ? 'future' : 'past',
1948
+ horizontalFuture = config.rtl ? 'past' : 'future';
1657
1949
 
1658
- // Update the classes of all backgrounds to match the
1950
+ // Update the classes of all backgrounds to match the
1659
1951
  // states of their slides (past/present/future)
1660
1952
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1661
1953
 
1662
- // Reverse past/future classes when in RTL mode
1663
- var horizontalPast = config.rtl ? 'future' : 'past',
1664
- horizontalFuture = config.rtl ? 'past' : 'future';
1954
+ if( h < indexh ) {
1955
+ backgroundh.className = 'slide-background ' + horizontalPast;
1956
+ }
1957
+ else if ( h > indexh ) {
1958
+ backgroundh.className = 'slide-background ' + horizontalFuture;
1959
+ }
1960
+ else {
1961
+ backgroundh.className = 'slide-background present';
1962
+
1963
+ // Store a reference to the current background element
1964
+ currentBackground = backgroundh;
1965
+ }
1966
+
1967
+ if( includeAll || h === indexh ) {
1968
+ toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1969
+
1970
+ if( v < indexv ) {
1971
+ backgroundv.className = 'slide-background past';
1972
+ }
1973
+ else if ( v > indexv ) {
1974
+ backgroundv.className = 'slide-background future';
1975
+ }
1976
+ else {
1977
+ backgroundv.className = 'slide-background present';
1665
1978
 
1666
- backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
1979
+ // Only if this is the present horizontal and vertical slide
1980
+ if( h === indexh ) currentBackground = backgroundv;
1981
+ }
1667
1982
 
1668
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1983
+ } );
1984
+ }
1669
1985
 
1670
- backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
1986
+ } );
1671
1987
 
1672
- } );
1988
+ // Don't transition between identical backgrounds. This
1989
+ // prevents unwanted flicker.
1990
+ if( currentBackground ) {
1991
+ var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
+ var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
+ if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
1994
+ dom.background.classList.add( 'no-transition' );
1995
+ }
1996
+
1997
+ previousBackground = currentBackground;
1998
+ }
1999
+
2000
+ // Allow the first background to apply without transition
2001
+ setTimeout( function() {
2002
+ dom.background.classList.remove( 'no-transition' );
2003
+ }, 1 );
2004
+
2005
+ }
2006
+
2007
+ /**
2008
+ * Updates the position of the parallax background based
2009
+ * on the current slide index.
2010
+ */
2011
+ function updateParallax() {
2012
+
2013
+ if( config.parallaxBackgroundImage ) {
2014
+
2015
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
+ verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
+
2018
+ var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
+ backgroundWidth, backgroundHeight;
2020
+
2021
+ if( backgroundSize.length === 1 ) {
2022
+ backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
2023
+ }
2024
+ else {
2025
+ backgroundWidth = parseInt( backgroundSize[0], 10 );
2026
+ backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
+ }
2028
+
2029
+ var slideWidth = dom.background.offsetWidth;
2030
+ var horizontalSlideCount = horizontalSlides.length;
2031
+ var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2032
+
2033
+ var slideHeight = dom.background.offsetHeight;
2034
+ var verticalSlideCount = verticalSlides.length;
2035
+ var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
1673
2036
 
1674
- } );
2037
+ dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
1675
2038
 
1676
- // Allow the first background to apply without transition
1677
- setTimeout( function() {
1678
- dom.background.classList.remove( 'no-transition' );
1679
- }, 1 );
2039
+ }
1680
2040
 
1681
2041
  }
1682
2042
 
@@ -1737,7 +2097,7 @@ var Reveal = (function(){
1737
2097
  */
1738
2098
  function startEmbeddedContent( slide ) {
1739
2099
 
1740
- if( slide ) {
2100
+ if( slide && !isSpeakerNotes() ) {
1741
2101
  // HTML5 media elements
1742
2102
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1743
2103
  if( el.hasAttribute( 'data-autoplay' ) ) {
@@ -1745,10 +2105,15 @@ var Reveal = (function(){
1745
2105
  }
1746
2106
  } );
1747
2107
 
2108
+ // iframe embeds
2109
+ toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
+ el.contentWindow.postMessage( 'slide:start', '*' );
2111
+ });
2112
+
1748
2113
  // YouTube embeds
1749
2114
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1750
2115
  if( el.hasAttribute( 'data-autoplay' ) ) {
1751
- el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
2116
+ el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
1752
2117
  }
1753
2118
  });
1754
2119
  }
@@ -1769,16 +2134,31 @@ var Reveal = (function(){
1769
2134
  }
1770
2135
  } );
1771
2136
 
2137
+ // iframe embeds
2138
+ toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
+ el.contentWindow.postMessage( 'slide:stop', '*' );
2140
+ });
2141
+
1772
2142
  // YouTube embeds
1773
2143
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1774
2144
  if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
1775
- el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
2145
+ el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
1776
2146
  }
1777
2147
  });
1778
2148
  }
1779
2149
 
1780
2150
  }
1781
2151
 
2152
+ /**
2153
+ * Checks if this presentation is running inside of the
2154
+ * speaker notes window.
2155
+ */
2156
+ function isSpeakerNotes() {
2157
+
2158
+ return !!window.location.search.match( /receiver/gi );
2159
+
2160
+ }
2161
+
1782
2162
  /**
1783
2163
  * Reads the current URL (hash) and navigates accordingly.
1784
2164
  */
@@ -1803,7 +2183,7 @@ var Reveal = (function(){
1803
2183
  }
1804
2184
  // If the slide doesn't exist, navigate to the current slide
1805
2185
  else {
1806
- slide( indexh, indexv );
2186
+ slide( indexh || 0, indexv || 0 );
1807
2187
  }
1808
2188
  }
1809
2189
  else {
@@ -1811,7 +2191,9 @@ var Reveal = (function(){
1811
2191
  var h = parseInt( bits[0], 10 ) || 0,
1812
2192
  v = parseInt( bits[1], 10 ) || 0;
1813
2193
 
1814
- slide( h, v );
2194
+ if( h !== indexh || v !== indexv ) {
2195
+ slide( h, v );
2196
+ }
1815
2197
  }
1816
2198
 
1817
2199
  }
@@ -1888,9 +2270,10 @@ var Reveal = (function(){
1888
2270
  }
1889
2271
 
1890
2272
  if( !slide && currentSlide ) {
1891
- var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
1892
- if( visibleFragments.length ) {
1893
- f = visibleFragments.length;
2273
+ var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
+ if( hasFragments ) {
2275
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
+ f = visibleFragments.length - 1;
1894
2277
  }
1895
2278
  }
1896
2279
 
@@ -1899,83 +2282,226 @@ var Reveal = (function(){
1899
2282
  }
1900
2283
 
1901
2284
  /**
1902
- * Navigate to the next slide fragment.
2285
+ * Return a sorted fragments list, ordered by an increasing
2286
+ * "data-fragment-index" attribute.
1903
2287
  *
1904
- * @return {Boolean} true if there was a next fragment,
1905
- * false otherwise
2288
+ * Fragments will be revealed in the order that they are returned by
2289
+ * this function, so you can use the index attributes to control the
2290
+ * order of fragment appearance.
2291
+ *
2292
+ * To maintain a sensible default fragment order, fragments are presumed
2293
+ * to be passed in document order. This function adds a "fragment-index"
2294
+ * attribute to each node if such an attribute is not already present,
2295
+ * and sets that attribute to an integer value which is the position of
2296
+ * the fragment within the fragments list.
1906
2297
  */
1907
- function nextFragment() {
1908
-
1909
- if( currentSlide && config.fragments ) {
1910
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
2298
+ function sortFragments( fragments ) {
1911
2299
 
1912
- if( fragments.length ) {
1913
- // Find the index of the next fragment
1914
- var index = fragments[0].getAttribute( 'data-fragment-index' );
2300
+ fragments = toArray( fragments );
1915
2301
 
1916
- // Find all fragments with the same index
1917
- fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
2302
+ var ordered = [],
2303
+ unordered = [],
2304
+ sorted = [];
1918
2305
 
1919
- toArray( fragments ).forEach( function( element ) {
1920
- element.classList.add( 'visible' );
1921
- } );
2306
+ // Group ordered and unordered elements
2307
+ fragments.forEach( function( fragment, i ) {
2308
+ if( fragment.hasAttribute( 'data-fragment-index' ) ) {
2309
+ var index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
1922
2310
 
1923
- // Notify subscribers of the change
1924
- dispatchEvent( 'fragmentshown', { fragment: fragments[0], fragments: fragments } );
2311
+ if( !ordered[index] ) {
2312
+ ordered[index] = [];
2313
+ }
1925
2314
 
1926
- updateControls();
1927
- return true;
2315
+ ordered[index].push( fragment );
1928
2316
  }
1929
- }
2317
+ else {
2318
+ unordered.push( [ fragment ] );
2319
+ }
2320
+ } );
1930
2321
 
1931
- return false;
2322
+ // Append fragments without explicit indices in their
2323
+ // DOM order
2324
+ ordered = ordered.concat( unordered );
2325
+
2326
+ // Manually count the index up per group to ensure there
2327
+ // are no gaps
2328
+ var index = 0;
2329
+
2330
+ // Push all fragments in their sorted order to an array,
2331
+ // this flattens the groups
2332
+ ordered.forEach( function( group ) {
2333
+ group.forEach( function( fragment ) {
2334
+ sorted.push( fragment );
2335
+ fragment.setAttribute( 'data-fragment-index', index );
2336
+ } );
2337
+
2338
+ index ++;
2339
+ } );
2340
+
2341
+ return sorted;
1932
2342
 
1933
2343
  }
1934
2344
 
1935
2345
  /**
1936
- * Navigate to the previous slide fragment.
2346
+ * Navigate to the specified slide fragment.
1937
2347
  *
1938
- * @return {Boolean} true if there was a previous fragment,
1939
- * false otherwise
2348
+ * @param {Number} index The index of the fragment that
2349
+ * should be shown, -1 means all are invisible
2350
+ * @param {Number} offset Integer offset to apply to the
2351
+ * fragment index
2352
+ *
2353
+ * @return {Boolean} true if a change was made in any
2354
+ * fragments visibility as part of this call
1940
2355
  */
1941
- function previousFragment() {
2356
+ function navigateFragment( index, offset ) {
1942
2357
 
1943
2358
  if( currentSlide && config.fragments ) {
1944
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
1945
2359
 
2360
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
1946
2361
  if( fragments.length ) {
1947
- // Find the index of the previous fragment
1948
- var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
1949
2362
 
1950
- // Find all fragments with the same index
1951
- fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
2363
+ // If no index is specified, find the current
2364
+ if( typeof index !== 'number' ) {
2365
+ var lastVisibleFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
2366
+
2367
+ if( lastVisibleFragment ) {
2368
+ index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
2369
+ }
2370
+ else {
2371
+ index = -1;
2372
+ }
2373
+ }
2374
+
2375
+ // If an offset is specified, apply it to the index
2376
+ if( typeof offset === 'number' ) {
2377
+ index += offset;
2378
+ }
2379
+
2380
+ var fragmentsShown = [],
2381
+ fragmentsHidden = [];
2382
+
2383
+ toArray( fragments ).forEach( function( element, i ) {
2384
+
2385
+ if( element.hasAttribute( 'data-fragment-index' ) ) {
2386
+ i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );
2387
+ }
2388
+
2389
+ // Visible fragments
2390
+ if( i <= index ) {
2391
+ if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );
2392
+ element.classList.add( 'visible' );
2393
+ element.classList.remove( 'current-fragment' );
2394
+
2395
+ if( i === index ) {
2396
+ element.classList.add( 'current-fragment' );
2397
+ }
2398
+ }
2399
+ // Hidden fragments
2400
+ else {
2401
+ if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );
2402
+ element.classList.remove( 'visible' );
2403
+ element.classList.remove( 'current-fragment' );
2404
+ }
2405
+
1952
2406
 
1953
- toArray( fragments ).forEach( function( f ) {
1954
- f.classList.remove( 'visible' );
1955
2407
  } );
1956
2408
 
1957
- // Notify subscribers of the change
1958
- dispatchEvent( 'fragmenthidden', { fragment: fragments[0], fragments: fragments } );
2409
+ if( fragmentsHidden.length ) {
2410
+ dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );
2411
+ }
2412
+
2413
+ if( fragmentsShown.length ) {
2414
+ dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );
2415
+ }
1959
2416
 
1960
2417
  updateControls();
1961
- return true;
2418
+
2419
+ return !!( fragmentsShown.length || fragmentsHidden.length );
2420
+
1962
2421
  }
2422
+
1963
2423
  }
1964
2424
 
1965
2425
  return false;
1966
2426
 
1967
2427
  }
1968
2428
 
2429
+ /**
2430
+ * Navigate to the next slide fragment.
2431
+ *
2432
+ * @return {Boolean} true if there was a next fragment,
2433
+ * false otherwise
2434
+ */
2435
+ function nextFragment() {
2436
+
2437
+ return navigateFragment( null, 1 );
2438
+
2439
+ }
2440
+
2441
+ /**
2442
+ * Navigate to the previous slide fragment.
2443
+ *
2444
+ * @return {Boolean} true if there was a previous fragment,
2445
+ * false otherwise
2446
+ */
2447
+ function previousFragment() {
2448
+
2449
+ return navigateFragment( null, -1 );
2450
+
2451
+ }
2452
+
1969
2453
  /**
1970
2454
  * Cues a new automated slide if enabled in the config.
1971
2455
  */
1972
2456
  function cueAutoSlide() {
1973
2457
 
1974
- clearTimeout( autoSlideTimeout );
2458
+ cancelAutoSlide();
2459
+
2460
+ if( currentSlide ) {
2461
+
2462
+ var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
+ var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
+
2465
+ // Pick value in the following priority order:
2466
+ // 1. Current slide's data-autoslide
2467
+ // 2. Parent slide's data-autoslide
2468
+ // 3. Global autoSlide setting
2469
+ if( slideAutoSlide ) {
2470
+ autoSlide = parseInt( slideAutoSlide, 10 );
2471
+ }
2472
+ else if( parentAutoSlide ) {
2473
+ autoSlide = parseInt( parentAutoSlide, 10 );
2474
+ }
2475
+ else {
2476
+ autoSlide = config.autoSlide;
2477
+ }
2478
+
2479
+ // If there are media elements with data-autoplay,
2480
+ // automatically set the autoSlide duration to the
2481
+ // length of that media
2482
+ toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2483
+ if( el.hasAttribute( 'data-autoplay' ) ) {
2484
+ if( autoSlide && el.duration * 1000 > autoSlide ) {
2485
+ autoSlide = ( el.duration * 1000 ) + 1000;
2486
+ }
2487
+ }
2488
+ } );
2489
+
2490
+ // Cue the next auto-slide if:
2491
+ // - There is an autoSlide value
2492
+ // - Auto-sliding isn't paused by the user
2493
+ // - The presentation isn't paused
2494
+ // - The overview isn't active
2495
+ // - The presentation isn't over
2496
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
2497
+ autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
+ autoSlideStartTime = Date.now();
2499
+ }
2500
+
2501
+ if( autoSlidePlayer ) {
2502
+ autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );
2503
+ }
1975
2504
 
1976
- // Cue the next auto-slide if enabled
1977
- if( autoSlide && !isPaused() && !isOverview() ) {
1978
- autoSlideTimeout = setTimeout( navigateNext, autoSlide );
1979
2505
  }
1980
2506
 
1981
2507
  }
@@ -1986,6 +2512,25 @@ var Reveal = (function(){
1986
2512
  function cancelAutoSlide() {
1987
2513
 
1988
2514
  clearTimeout( autoSlideTimeout );
2515
+ autoSlideTimeout = -1;
2516
+
2517
+ }
2518
+
2519
+ function pauseAutoSlide() {
2520
+
2521
+ autoSlidePaused = true;
2522
+ clearTimeout( autoSlideTimeout );
2523
+
2524
+ if( autoSlidePlayer ) {
2525
+ autoSlidePlayer.setPlaying( false );
2526
+ }
2527
+
2528
+ }
2529
+
2530
+ function resumeAutoSlide() {
2531
+
2532
+ autoSlidePaused = false;
2533
+ cueAutoSlide();
1989
2534
 
1990
2535
  }
1991
2536
 
@@ -2085,14 +2630,25 @@ var Reveal = (function(){
2085
2630
  // ----------------------------- EVENTS -------------------------------//
2086
2631
  // --------------------------------------------------------------------//
2087
2632
 
2633
+ /**
2634
+ * Called by all event handlers that are based on user
2635
+ * input.
2636
+ */
2637
+ function onUserInput( event ) {
2638
+
2639
+ if( config.autoSlideStoppable ) {
2640
+ pauseAutoSlide();
2641
+ }
2642
+
2643
+ }
2088
2644
 
2089
2645
  /**
2090
2646
  * Handler for the document level 'keydown' event.
2091
- *
2092
- * @param {Object} event
2093
2647
  */
2094
2648
  function onDocumentKeyDown( event ) {
2095
2649
 
2650
+ onUserInput( event );
2651
+
2096
2652
  // Check if there's a focused element that could be using
2097
2653
  // the keyboard
2098
2654
  var activeElement = document.activeElement;
@@ -2119,7 +2675,7 @@ var Reveal = (function(){
2119
2675
 
2120
2676
  var value = config.keyboard[ key ];
2121
2677
 
2122
- // Calback function
2678
+ // Callback function
2123
2679
  if( typeof value === 'function' ) {
2124
2680
  value.apply( null, [ event ] );
2125
2681
  }
@@ -2178,8 +2734,14 @@ var Reveal = (function(){
2178
2734
  if( triggered ) {
2179
2735
  event.preventDefault();
2180
2736
  }
2181
- else if ( event.keyCode === 27 && supports3DTransforms ) {
2182
- toggleOverview();
2737
+ // ESC or O key
2738
+ else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
+ if( dom.preview ) {
2740
+ closePreview();
2741
+ }
2742
+ else {
2743
+ toggleOverview();
2744
+ }
2183
2745
 
2184
2746
  event.preventDefault();
2185
2747
  }
@@ -2220,11 +2782,13 @@ var Reveal = (function(){
2220
2782
  function onTouchMove( event ) {
2221
2783
 
2222
2784
  // Each touch should only trigger one action
2223
- if( !touch.handled ) {
2785
+ if( !touch.captured ) {
2786
+ onUserInput( event );
2787
+
2224
2788
  var currentX = event.touches[0].clientX;
2225
2789
  var currentY = event.touches[0].clientY;
2226
2790
 
2227
- // If the touch started off with two points and still has
2791
+ // If the touch started with two points and still has
2228
2792
  // two active touches; test for the pinch gesture
2229
2793
  if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
2230
2794
 
@@ -2240,7 +2804,7 @@ var Reveal = (function(){
2240
2804
  // If the span is larger than the desire amount we've got
2241
2805
  // ourselves a pinch
2242
2806
  if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
2243
- touch.handled = true;
2807
+ touch.captured = true;
2244
2808
 
2245
2809
  if( currentSpan < touch.startSpan ) {
2246
2810
  activateOverview();
@@ -2260,23 +2824,34 @@ var Reveal = (function(){
2260
2824
  deltaY = currentY - touch.startY;
2261
2825
 
2262
2826
  if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
2263
- touch.handled = true;
2827
+ touch.captured = true;
2264
2828
  navigateLeft();
2265
2829
  }
2266
2830
  else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
2267
- touch.handled = true;
2831
+ touch.captured = true;
2268
2832
  navigateRight();
2269
2833
  }
2270
2834
  else if( deltaY > touch.threshold ) {
2271
- touch.handled = true;
2835
+ touch.captured = true;
2272
2836
  navigateUp();
2273
2837
  }
2274
2838
  else if( deltaY < -touch.threshold ) {
2275
- touch.handled = true;
2839
+ touch.captured = true;
2276
2840
  navigateDown();
2277
2841
  }
2278
2842
 
2279
- event.preventDefault();
2843
+ // If we're embedded, only block touch events if they have
2844
+ // triggered an action
2845
+ if( config.embedded ) {
2846
+ if( touch.captured || isVerticalSlide( currentSlide ) ) {
2847
+ event.preventDefault();
2848
+ }
2849
+ }
2850
+ // Not embedded? Block them all to avoid needless tossing
2851
+ // around of the viewport in iOS
2852
+ else {
2853
+ event.preventDefault();
2854
+ }
2280
2855
 
2281
2856
  }
2282
2857
  }
@@ -2293,7 +2868,7 @@ var Reveal = (function(){
2293
2868
  */
2294
2869
  function onTouchEnd( event ) {
2295
2870
 
2296
- touch.handled = false;
2871
+ touch.captured = false;
2297
2872
 
2298
2873
  }
2299
2874
 
@@ -2363,6 +2938,8 @@ var Reveal = (function(){
2363
2938
  */
2364
2939
  function onProgressClicked( event ) {
2365
2940
 
2941
+ onUserInput( event );
2942
+
2366
2943
  event.preventDefault();
2367
2944
 
2368
2945
  var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
@@ -2375,12 +2952,12 @@ var Reveal = (function(){
2375
2952
  /**
2376
2953
  * Event handler for navigation control buttons.
2377
2954
  */
2378
- function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
2379
- function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
2380
- function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
2381
- function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
2382
- function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
2383
- function onNavigateNextClicked( event ) { event.preventDefault(); navigateNext(); }
2955
+ function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
2956
+ function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
2957
+ function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
2958
+ function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
2959
+ function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
2960
+ function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }
2384
2961
 
2385
2962
  /**
2386
2963
  * Handler for the window level 'hashchange' event.
@@ -2400,6 +2977,24 @@ var Reveal = (function(){
2400
2977
 
2401
2978
  }
2402
2979
 
2980
+ /**
2981
+ * Handle for the window level 'visibilitychange' event.
2982
+ */
2983
+ function onPageVisibilityChange( event ) {
2984
+
2985
+ var isHidden = document.webkitHidden ||
2986
+ document.msHidden ||
2987
+ document.hidden;
2988
+
2989
+ // If, after clicking a link or similar and we're coming back,
2990
+ // focus the document.body to ensure we can use keyboard shortcuts
2991
+ if( isHidden === false && document.activeElement !== document.body ) {
2992
+ document.activeElement.blur();
2993
+ document.body.focus();
2994
+ }
2995
+
2996
+ }
2997
+
2403
2998
  /**
2404
2999
  * Invoked when a slide is and we're in the overview.
2405
3000
  */
@@ -2446,6 +3041,191 @@ var Reveal = (function(){
2446
3041
 
2447
3042
  }
2448
3043
 
3044
+ /**
3045
+ * Handles click on the auto-sliding controls element.
3046
+ */
3047
+ function onAutoSlidePlayerClick( event ) {
3048
+
3049
+ // Replay
3050
+ if( Reveal.isLastSlide() && config.loop === false ) {
3051
+ slide( 0, 0 );
3052
+ resumeAutoSlide();
3053
+ }
3054
+ // Resume
3055
+ else if( autoSlidePaused ) {
3056
+ resumeAutoSlide();
3057
+ }
3058
+ // Pause
3059
+ else {
3060
+ pauseAutoSlide();
3061
+ }
3062
+
3063
+ }
3064
+
3065
+
3066
+ // --------------------------------------------------------------------//
3067
+ // ------------------------ PLAYBACK COMPONENT ------------------------//
3068
+ // --------------------------------------------------------------------//
3069
+
3070
+
3071
+ /**
3072
+ * Constructor for the playback component, which displays
3073
+ * play/pause/progress controls.
3074
+ *
3075
+ * @param {HTMLElement} container The component will append
3076
+ * itself to this
3077
+ * @param {Function} progressCheck A method which will be
3078
+ * called frequently to get the current progress on a range
3079
+ * of 0-1
3080
+ */
3081
+ function Playback( container, progressCheck ) {
3082
+
3083
+ // Cosmetics
3084
+ this.diameter = 50;
3085
+ this.thickness = 3;
3086
+
3087
+ // Flags if we are currently playing
3088
+ this.playing = false;
3089
+
3090
+ // Current progress on a 0-1 range
3091
+ this.progress = 0;
3092
+
3093
+ // Used to loop the animation smoothly
3094
+ this.progressOffset = 1;
3095
+
3096
+ this.container = container;
3097
+ this.progressCheck = progressCheck;
3098
+
3099
+ this.canvas = document.createElement( 'canvas' );
3100
+ this.canvas.className = 'playback';
3101
+ this.canvas.width = this.diameter;
3102
+ this.canvas.height = this.diameter;
3103
+ this.context = this.canvas.getContext( '2d' );
3104
+
3105
+ this.container.appendChild( this.canvas );
3106
+
3107
+ this.render();
3108
+
3109
+ }
3110
+
3111
+ Playback.prototype.setPlaying = function( value ) {
3112
+
3113
+ var wasPlaying = this.playing;
3114
+
3115
+ this.playing = value;
3116
+
3117
+ // Start repainting if we weren't already
3118
+ if( !wasPlaying && this.playing ) {
3119
+ this.animate();
3120
+ }
3121
+ else {
3122
+ this.render();
3123
+ }
3124
+
3125
+ };
3126
+
3127
+ Playback.prototype.animate = function() {
3128
+
3129
+ var progressBefore = this.progress;
3130
+
3131
+ this.progress = this.progressCheck();
3132
+
3133
+ // When we loop, offset the progress so that it eases
3134
+ // smoothly rather than immediately resetting
3135
+ if( progressBefore > 0.8 && this.progress < 0.2 ) {
3136
+ this.progressOffset = this.progress;
3137
+ }
3138
+
3139
+ this.render();
3140
+
3141
+ if( this.playing ) {
3142
+ features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );
3143
+ }
3144
+
3145
+ };
3146
+
3147
+ /**
3148
+ * Renders the current progress and playback state.
3149
+ */
3150
+ Playback.prototype.render = function() {
3151
+
3152
+ var progress = this.playing ? this.progress : 0,
3153
+ radius = ( this.diameter / 2 ) - this.thickness,
3154
+ x = this.diameter / 2,
3155
+ y = this.diameter / 2,
3156
+ iconSize = 14;
3157
+
3158
+ // Ease towards 1
3159
+ this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
3160
+
3161
+ var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
3162
+ var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
3163
+
3164
+ this.context.save();
3165
+ this.context.clearRect( 0, 0, this.diameter, this.diameter );
3166
+
3167
+ // Solid background color
3168
+ this.context.beginPath();
3169
+ this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
3170
+ this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
3171
+ this.context.fill();
3172
+
3173
+ // Draw progress track
3174
+ this.context.beginPath();
3175
+ this.context.arc( x, y, radius, 0, Math.PI * 2, false );
3176
+ this.context.lineWidth = this.thickness;
3177
+ this.context.strokeStyle = '#666';
3178
+ this.context.stroke();
3179
+
3180
+ if( this.playing ) {
3181
+ // Draw progress on top of track
3182
+ this.context.beginPath();
3183
+ this.context.arc( x, y, radius, startAngle, endAngle, false );
3184
+ this.context.lineWidth = this.thickness;
3185
+ this.context.strokeStyle = '#fff';
3186
+ this.context.stroke();
3187
+ }
3188
+
3189
+ this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
3190
+
3191
+ // Draw play/pause icons
3192
+ if( this.playing ) {
3193
+ this.context.fillStyle = '#fff';
3194
+ this.context.fillRect( 0, 0, iconSize / 2 - 2, iconSize );
3195
+ this.context.fillRect( iconSize / 2 + 2, 0, iconSize / 2 - 2, iconSize );
3196
+ }
3197
+ else {
3198
+ this.context.beginPath();
3199
+ this.context.translate( 2, 0 );
3200
+ this.context.moveTo( 0, 0 );
3201
+ this.context.lineTo( iconSize - 2, iconSize / 2 );
3202
+ this.context.lineTo( 0, iconSize );
3203
+ this.context.fillStyle = '#fff';
3204
+ this.context.fill();
3205
+ }
3206
+
3207
+ this.context.restore();
3208
+
3209
+ };
3210
+
3211
+ Playback.prototype.on = function( type, listener ) {
3212
+ this.canvas.addEventListener( type, listener, false );
3213
+ };
3214
+
3215
+ Playback.prototype.off = function( type, listener ) {
3216
+ this.canvas.removeEventListener( type, listener, false );
3217
+ };
3218
+
3219
+ Playback.prototype.destroy = function() {
3220
+
3221
+ this.playing = false;
3222
+
3223
+ if( this.canvas.parentNode ) {
3224
+ this.container.removeChild( this.canvas );
3225
+ }
3226
+
3227
+ };
3228
+
2449
3229
 
2450
3230
  // --------------------------------------------------------------------//
2451
3231
  // ------------------------------- API --------------------------------//
@@ -2465,6 +3245,9 @@ var Reveal = (function(){
2465
3245
  down: navigateDown,
2466
3246
  prev: navigatePrev,
2467
3247
  next: navigateNext,
3248
+
3249
+ // Fragment methods
3250
+ navigateFragment: navigateFragment,
2468
3251
  prevFragment: previousFragment,
2469
3252
  nextFragment: nextFragment,
2470
3253
 
@@ -2539,10 +3322,22 @@ var Reveal = (function(){
2539
3322
  getQueryHash: function() {
2540
3323
  var query = {};
2541
3324
 
2542
- location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
3325
+ location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {
2543
3326
  query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
2544
3327
  } );
2545
3328
 
3329
+ // Basic deserialization
3330
+ for( var i in query ) {
3331
+ var value = query[ i ];
3332
+
3333
+ query[ i ] = unescape( value );
3334
+
3335
+ if( value === 'null' ) query[ i ] = null;
3336
+ else if( value === 'true' ) query[ i ] = true;
3337
+ else if( value === 'false' ) query[ i ] = false;
3338
+ else if( value.match( /^\d+$/ ) ) query[ i ] = parseFloat( value );
3339
+ }
3340
+
2546
3341
  return query;
2547
3342
  },
2548
3343
 
@@ -2553,12 +3348,22 @@ var Reveal = (function(){
2553
3348
 
2554
3349
  // Returns true if we're currently on the last slide
2555
3350
  isLastSlide: function() {
2556
- if( currentSlide && currentSlide.classList.contains( '.stack' ) ) {
2557
- return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
2558
- }
2559
- else {
2560
- return document.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
3351
+ if( currentSlide ) {
3352
+ // Does this slide has next a sibling?
3353
+ if( currentSlide.nextElementSibling ) return false;
3354
+
3355
+ // If it's vertical, does its parent have a next sibling?
3356
+ if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
3357
+
3358
+ return true;
2561
3359
  }
3360
+
3361
+ return false;
3362
+ },
3363
+
3364
+ // Checks if reveal.js has been loaded and is ready for use
3365
+ isReady: function() {
3366
+ return loaded;
2562
3367
  },
2563
3368
 
2564
3369
  // Forward event binding to the reveal DOM element