slippery 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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