reveal-ck 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +3 -11
  3. data/bin/reveal-ck +38 -3
  4. data/lib/reveal-ck.rb +2 -0
  5. data/lib/reveal-ck/markdown.rb +2 -0
  6. data/lib/reveal-ck/markdown/slide_markdown.rb +22 -0
  7. data/lib/reveal-ck/markdown/slide_markdown_template.rb +22 -0
  8. data/lib/reveal-ck/tilt/config.rb +2 -0
  9. data/lib/reveal-ck/version.rb +1 -1
  10. data/reveal.js/Gruntfile.js +4 -3
  11. data/reveal.js/README.md +135 -13
  12. data/reveal.js/css/print/pdf.css +1 -1
  13. data/reveal.js/css/reveal.css +242 -15
  14. data/reveal.js/css/reveal.min.css +1 -1
  15. data/reveal.js/css/theme/beige.css +7 -1
  16. data/reveal.js/css/theme/blood.css +175 -0
  17. data/reveal.js/css/theme/default.css +7 -1
  18. data/reveal.js/css/theme/moon.css +7 -1
  19. data/reveal.js/css/theme/night.css +7 -1
  20. data/reveal.js/css/theme/serif.css +7 -1
  21. data/reveal.js/css/theme/simple.css +7 -1
  22. data/reveal.js/css/theme/sky.css +7 -1
  23. data/reveal.js/css/theme/solarized.css +7 -1
  24. data/reveal.js/css/theme/source/blood.scss +91 -0
  25. data/reveal.js/css/theme/template/theme.scss +8 -1
  26. data/reveal.js/index.html +9 -4
  27. data/reveal.js/js/reveal.js +804 -199
  28. data/reveal.js/js/reveal.min.js +3 -2
  29. data/reveal.js/package.json +1 -1
  30. data/reveal.js/plugin/highlight/highlight.js +3 -2
  31. data/reveal.js/plugin/markdown/example.html +34 -3
  32. data/reveal.js/plugin/markdown/markdown.js +75 -3
  33. data/reveal.js/plugin/notes/notes.html +10 -6
  34. data/reveal.js/plugin/remotes/remotes.js +1 -1
  35. data/reveal.js/plugin/zoom-js/zoom.js +3 -1
  36. data/reveal.js/test/examples/barebones.html +0 -1
  37. data/reveal.js/test/examples/slide-backgrounds.html +22 -1
  38. data/reveal.js/test/test-markdown-element-attributes.html +134 -0
  39. data/reveal.js/test/test-markdown-element-attributes.js +46 -0
  40. data/reveal.js/test/test-markdown-slide-attributes.html +128 -0
  41. data/reveal.js/test/test-markdown-slide-attributes.js +47 -0
  42. data/reveal.js/test/test.html +26 -7
  43. data/reveal.js/test/test.js +95 -10
  44. data/spec/lib/reveal-ck/markdown/slide_markdown_spec.rb +76 -0
  45. data/spec/lib/reveal-ck/markdown/slide_markdown_template_spec.rb +29 -0
  46. data/spec/lib/reveal-ck/tilt/config_spec.rb +9 -0
  47. metadata +34 -4
@@ -27,7 +27,7 @@ body {
27
27
  .reveal {
28
28
  font-family: "Lato", sans-serif;
29
29
  font-size: 36px;
30
- font-weight: 200;
30
+ font-weight: normal;
31
31
  letter-spacing: -0.02em;
32
32
  color: #eeeeee; }
33
33
 
@@ -140,3 +140,9 @@ body {
140
140
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
141
141
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
142
142
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
143
+
144
+ /*********************************************
145
+ * SLIDE NUMBER
146
+ *********************************************/
147
+ .reveal .slide-number {
148
+ color: #13daec; }
@@ -27,7 +27,7 @@ body {
27
27
  .reveal {
28
28
  font-family: "Lato", sans-serif;
29
29
  font-size: 36px;
30
- font-weight: 200;
30
+ font-weight: normal;
31
31
  letter-spacing: -0.02em;
32
32
  color: #93a1a1; }
33
33
 
@@ -140,3 +140,9 @@ body {
140
140
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
141
141
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
142
142
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
143
+
144
+ /*********************************************
145
+ * SLIDE NUMBER
146
+ *********************************************/
147
+ .reveal .slide-number {
148
+ color: #268bd2; }
@@ -15,7 +15,7 @@ body {
15
15
  .reveal {
16
16
  font-family: "Open Sans", sans-serif;
17
17
  font-size: 30px;
18
- font-weight: 200;
18
+ font-weight: normal;
19
19
  letter-spacing: -0.02em;
20
20
  color: #eeeeee; }
21
21
 
@@ -128,3 +128,9 @@ body {
128
128
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
129
129
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
130
130
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
131
+
132
+ /*********************************************
133
+ * SLIDE NUMBER
134
+ *********************************************/
135
+ .reveal .slide-number {
136
+ color: #e7ad52; }
@@ -17,7 +17,7 @@ body {
17
17
  .reveal {
18
18
  font-family: "Palatino Linotype", "Book Antiqua", Palatino, FreeSerif, serif;
19
19
  font-size: 36px;
20
- font-weight: 200;
20
+ font-weight: normal;
21
21
  letter-spacing: -0.02em;
22
22
  color: black; }
23
23
 
@@ -130,3 +130,9 @@ body {
130
130
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
131
131
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
132
132
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
133
+
134
+ /*********************************************
135
+ * SLIDE NUMBER
136
+ *********************************************/
137
+ .reveal .slide-number {
138
+ color: #51483d; }
@@ -17,7 +17,7 @@ body {
17
17
  .reveal {
18
18
  font-family: "Lato", sans-serif;
19
19
  font-size: 36px;
20
- font-weight: 200;
20
+ font-weight: normal;
21
21
  letter-spacing: -0.02em;
22
22
  color: black; }
23
23
 
@@ -130,3 +130,9 @@ body {
130
130
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
131
131
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
132
132
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
133
+
134
+ /*********************************************
135
+ * SLIDE NUMBER
136
+ *********************************************/
137
+ .reveal .slide-number {
138
+ color: darkblue; }
@@ -24,7 +24,7 @@ body {
24
24
  .reveal {
25
25
  font-family: "Open Sans", sans-serif;
26
26
  font-size: 36px;
27
- font-weight: 200;
27
+ font-weight: normal;
28
28
  letter-spacing: -0.02em;
29
29
  color: #333333; }
30
30
 
@@ -137,3 +137,9 @@ body {
137
137
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
138
138
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
139
139
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
140
+
141
+ /*********************************************
142
+ * SLIDE NUMBER
143
+ *********************************************/
144
+ .reveal .slide-number {
145
+ color: #3b759e; }
@@ -27,7 +27,7 @@ body {
27
27
  .reveal {
28
28
  font-family: "Lato", sans-serif;
29
29
  font-size: 36px;
30
- font-weight: 200;
30
+ font-weight: normal;
31
31
  letter-spacing: -0.02em;
32
32
  color: #657b83; }
33
33
 
@@ -140,3 +140,9 @@ body {
140
140
  -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
141
141
  -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
142
142
  transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
143
+
144
+ /*********************************************
145
+ * SLIDE NUMBER
146
+ *********************************************/
147
+ .reveal .slide-number {
148
+ color: #268bd2; }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Blood theme for reveal.js
3
+ * Author: Walther http://github.com/Walther
4
+ *
5
+ * Designed to be used with highlight.js theme
6
+ * "monokai_sublime.css" available from
7
+ * https://github.com/isagalaev/highlight.js/
8
+ *
9
+ * For other themes, change $codeBackground accordingly.
10
+ *
11
+ */
12
+
13
+ // Default mixins and settings -----------------
14
+ @import "../template/mixins";
15
+ @import "../template/settings";
16
+ // ---------------------------------------------
17
+
18
+ // Include theme-specific fonts
19
+
20
+ @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic);
21
+
22
+ // Colors used in the theme
23
+ $blood: #a23;
24
+ $coal: #222;
25
+ $codeBackground: #23241f;
26
+
27
+ // Main text
28
+ $mainFont: Ubuntu, 'sans-serif';
29
+ $mainFontSize: 36px;
30
+ $mainColor: #eee;
31
+
32
+ // Headings
33
+ $headingFont: Ubuntu, 'sans-serif';
34
+ $headingTextShadow: 2px 2px 2px $coal;
35
+
36
+ // h1 shadow, borrowed humbly from
37
+ // (c) Default theme by Hakim El Hattab
38
+ $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
39
+
40
+ // Links
41
+ $linkColor: $blood;
42
+ $linkColorHover: lighten( $linkColor, 20% );
43
+
44
+ // Text selection
45
+ $selectionBackgroundColor: $blood;
46
+ $selectionColor: #fff;
47
+
48
+ // Background generator
49
+ @mixin bodyBackground() {
50
+ @include radial-gradient( $coal, lighten( $coal, 25% ) );
51
+ }
52
+
53
+ // Theme template ------------------------------
54
+ @import "../template/theme";
55
+ // ---------------------------------------------
56
+
57
+ // some overrides after theme template import
58
+
59
+ .reveal p {
60
+ font-weight: 300;
61
+ text-shadow: 1px 1px $coal;
62
+ }
63
+
64
+ .reveal h1,
65
+ .reveal h2,
66
+ .reveal h3,
67
+ .reveal h4,
68
+ .reveal h5,
69
+ .reveal h6 {
70
+ font-weight: 700;
71
+ }
72
+
73
+ .reveal a:not(.image),
74
+ .reveal a:not(.image):hover {
75
+ text-shadow: 2px 2px 2px #000;
76
+ }
77
+
78
+ .reveal small a:not(.image),
79
+ .reveal small a:not(.image):hover {
80
+ text-shadow: 1px 1px 1px #000;
81
+ }
82
+
83
+ .reveal p code {
84
+ background-color: $codeBackground;
85
+ display: inline-block;
86
+ border-radius: 7px;
87
+ }
88
+
89
+ .reveal small code {
90
+ vertical-align: baseline;
91
+ }
@@ -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
  }
@@ -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
 
data/reveal.js/index.html CHANGED
@@ -173,9 +173,8 @@
173
173
  <a href="?theme=simple#/themes">Simple</a> -
174
174
  <a href="?theme=serif#/themes">Serif</a> -
175
175
  <a href="?theme=night#/themes">Night</a> <br>
176
- <a href="?theme=moon.css#/themes">Moon</a> -
177
- <a href="?theme=simple.css#/themes">Simple</a> -
178
- <a href="?theme=solarized.css#/themes">Solarized</a>
176
+ <a href="?theme=moon#/themes">Moon</a> -
177
+ <a href="?theme=solarized#/themes">Solarized</a>
179
178
  </p>
180
179
  <p>
181
180
  <small>
@@ -281,7 +280,7 @@ function linkify( selector ) {
281
280
  </section>
282
281
 
283
282
  <section>
284
- <section>
283
+ <section id="fragments">
285
284
  <h2>Fragmented Views</h2>
286
285
  <p>Hit the next arrow...</p>
287
286
  <p class="fragment">... to step through ...</p>
@@ -305,6 +304,8 @@ function linkify( selector ) {
305
304
  <p class="fragment highlight-red">highlight-red</p>
306
305
  <p class="fragment highlight-green">highlight-green</p>
307
306
  <p class="fragment highlight-blue">highlight-blue</p>
307
+ <p class="fragment current-visible">current-visible</p>
308
+ <p class="fragment highlight-current-blue">highlight-current-blue</p>
308
309
  </section>
309
310
  </section>
310
311
 
@@ -366,6 +367,10 @@ function linkify( selector ) {
366
367
  theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
367
368
  transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
368
369
 
370
+ // Parallax scrolling
371
+ // parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg',
372
+ // parallaxBackgroundSize: '2100px 900px',
373
+
369
374
  // Optional libraries used to extend on reveal.js
370
375
  dependencies: [
371
376
  { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
@@ -12,7 +12,7 @@ var Reveal = (function(){
12
12
  var SLIDES_SELECTOR = '.reveal .slides section',
13
13
  HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
14
  VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-child',
15
+ HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
16
16
 
17
17
  // Configurations defaults, can be overridden at initialization time
18
18
  config = {
@@ -35,6 +35,9 @@ var Reveal = (function(){
35
35
  // Display a presentation progress bar
36
36
  progress: true,
37
37
 
38
+ // Display the page number of the current slide
39
+ slideNumber: false,
40
+
38
41
  // Push each slide change to the browser history
39
42
  history: false,
40
43
 
@@ -44,7 +47,7 @@ var Reveal = (function(){
44
47
  // Enable the slide overview mode
45
48
  overview: true,
46
49
 
47
- // Vertical centring of slides
50
+ // Vertical centering of slides
48
51
  center: true,
49
52
 
50
53
  // Enables touch navigation on devices with touch input
@@ -68,6 +71,9 @@ var Reveal = (function(){
68
71
  // by using a data-autoslide attribute on your slides
69
72
  autoSlide: 0,
70
73
 
74
+ // Stop auto-sliding after user input
75
+ autoSlideStoppable: true,
76
+
71
77
  // Enable slide navigation via mouse wheel
72
78
  mouseWheel: false,
73
79
 
@@ -80,6 +86,9 @@ var Reveal = (function(){
80
86
  // Opens links in an iframe preview overlay
81
87
  previewLinks: false,
82
88
 
89
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
+ focusBodyOnPageVisiblityChange: true,
91
+
83
92
  // Theme (see /css/theme)
84
93
  theme: null,
85
94
 
@@ -92,19 +101,23 @@ var Reveal = (function(){
92
101
  // Transition style for full page slide backgrounds
93
102
  backgroundTransition: 'default', // default/linear/none
94
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
+
95
110
  // Number of slides away from the current that are visible
96
111
  viewDistance: 3,
97
112
 
98
113
  // Script dependencies to load
99
114
  dependencies: []
115
+
100
116
  },
101
117
 
102
118
  // Flags if reveal.js is loaded (has dispatched the 'ready' event)
103
119
  loaded = false,
104
120
 
105
- // The current auto-slide duration
106
- autoSlide = 0,
107
-
108
121
  // The horizontal and vertical index of the currently active slide
109
122
  indexh,
110
123
  indexv,
@@ -113,6 +126,8 @@ var Reveal = (function(){
113
126
  previousSlide,
114
127
  currentSlide,
115
128
 
129
+ previousBackground,
130
+
116
131
  // Slides may hold a data-state attribute which we pick up and apply
117
132
  // as a class to the body. This list contains the combined state of
118
133
  // all current slides.
@@ -124,11 +139,8 @@ var Reveal = (function(){
124
139
  // Cached references to DOM elements
125
140
  dom = {},
126
141
 
127
- // Client support for CSS 3D transforms, see #checkCapabilities()
128
- supports3DTransforms,
129
-
130
- // Client support for CSS 2D transforms, see #checkCapabilities()
131
- supports2DTransforms,
142
+ // Features supported by the browser, see #checkCapabilities()
143
+ features = {},
132
144
 
133
145
  // Client is a mobile device, see #checkCapabilities()
134
146
  isMobileDevice,
@@ -136,9 +148,6 @@ var Reveal = (function(){
136
148
  // Throttles mouse wheel navigation
137
149
  lastMouseWheelStep = 0,
138
150
 
139
- // An interval used to automatically move on to the next slide
140
- autoSlideTimeout = 0,
141
-
142
151
  // Delays updates to the URL due to a Chrome thumbnailer bug
143
152
  writeURLTimeout = 0,
144
153
 
@@ -151,6 +160,15 @@ var Reveal = (function(){
151
160
  // Flags if the interaction event listeners are bound
152
161
  eventsAreBound = false,
153
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
+
154
172
  // Holds information about the currently ongoing touch input
155
173
  touch = {
156
174
  startX: 0,
@@ -168,7 +186,7 @@ var Reveal = (function(){
168
186
 
169
187
  checkCapabilities();
170
188
 
171
- if( !supports2DTransforms && !supports3DTransforms ) {
189
+ if( !features.transforms2d && !features.transforms3d ) {
172
190
  document.body.setAttribute( 'class', 'no-transforms' );
173
191
 
174
192
  // If the browser doesn't support core features we won't be
@@ -179,8 +197,15 @@ var Reveal = (function(){
179
197
  // Force a layout when the whole page, incl fonts, has loaded
180
198
  window.addEventListener( 'load', layout, false );
181
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
+
182
206
  // Copy options over to our config object
183
207
  extend( config, options );
208
+ extend( config, query );
184
209
 
185
210
  // Hide the address bar in mobile browsers
186
211
  hideAddressBar();
@@ -196,33 +221,63 @@ var Reveal = (function(){
196
221
  */
197
222
  function checkCapabilities() {
198
223
 
199
- supports3DTransforms = 'WebkitPerspective' in document.body.style ||
224
+ features.transforms3d = 'WebkitPerspective' in document.body.style ||
200
225
  'MozPerspective' in document.body.style ||
201
226
  'msPerspective' in document.body.style ||
202
227
  'OPerspective' in document.body.style ||
203
228
  'perspective' in document.body.style;
204
229
 
205
- supports2DTransforms = 'WebkitTransform' in document.body.style ||
230
+ features.transforms2d = 'WebkitTransform' in document.body.style ||
206
231
  'MozTransform' in document.body.style ||
207
232
  'msTransform' in document.body.style ||
208
233
  'OTransform' in document.body.style ||
209
234
  'transform' in document.body.style;
210
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
+
211
241
  isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
212
242
 
213
243
  }
214
244
 
215
- /**
216
- * Loads the dependencies of reveal.js. Dependencies are
217
- * defined via the configuration option 'dependencies'
218
- * and will be loaded prior to starting/binding reveal.js.
219
- * Some dependencies may have an 'async' flag, if so they
220
- * will load after reveal.js has been started up.
221
- */
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
+ */
222
253
  function load() {
223
254
 
224
255
  var scripts = [],
225
- scriptsAsync = [];
256
+ scriptsAsync = [],
257
+ scriptsToPreload = 0;
258
+
259
+ // Called once synchronous scripts finish loading
260
+ function proceed() {
261
+ if( scriptsAsync.length ) {
262
+ // Load asynchronous scripts
263
+ head.js.apply( null, scriptsAsync );
264
+ }
265
+
266
+ start();
267
+ }
268
+
269
+ function loadScript( s ) {
270
+ head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
271
+ // Extension may contain callback functions
272
+ if( typeof s.callback === 'function' ) {
273
+ s.callback.apply( this );
274
+ }
275
+
276
+ if( --scriptsToPreload === 0 ) {
277
+ proceed();
278
+ }
279
+ });
280
+ }
226
281
 
227
282
  for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
228
283
  var s = config.dependencies[i];
@@ -236,25 +291,12 @@ var Reveal = (function(){
236
291
  scripts.push( s.src );
237
292
  }
238
293
 
239
- // Extension may contain callback functions
240
- if( typeof s.callback === 'function' ) {
241
- head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], s.callback );
242
- }
243
- }
244
- }
245
-
246
- // Called once synchronous scripts finish loading
247
- function proceed() {
248
- if( scriptsAsync.length ) {
249
- // Load asynchronous scripts
250
- head.js.apply( null, scriptsAsync );
294
+ loadScript( s );
251
295
  }
252
-
253
- start();
254
296
  }
255
297
 
256
298
  if( scripts.length ) {
257
- head.ready( proceed );
299
+ scriptsToPreload = scripts.length;
258
300
 
259
301
  // Load synchronous scripts
260
302
  head.js.apply( null, scripts );
@@ -274,8 +316,8 @@ var Reveal = (function(){
274
316
  // Make sure we've got all the DOM elements we need
275
317
  setupDOM();
276
318
 
277
- // Decorate the slide DOM elements with state classes (past/future)
278
- setupSlides();
319
+ // Resets all vertical slides so that only the first is visible
320
+ resetVerticalSlides();
279
321
 
280
322
  // Updates the presentation to match the current configuration values
281
323
  configure();
@@ -283,6 +325,9 @@ var Reveal = (function(){
283
325
  // Read the initial hash
284
326
  readURL();
285
327
 
328
+ // Update all backgrounds
329
+ updateBackground( true );
330
+
286
331
  // Notify listeners that the presentation is ready but use a 1ms
287
332
  // timeout to ensure it's not fired synchronously after #initialize()
288
333
  setTimeout( function() {
@@ -300,26 +345,6 @@ var Reveal = (function(){
300
345
 
301
346
  }
302
347
 
303
- /**
304
- * Iterates through and decorates slides DOM elements with
305
- * appropriate classes.
306
- */
307
- function setupSlides() {
308
-
309
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
310
- horizontalSlides.forEach( function( horizontalSlide ) {
311
-
312
- var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
313
- verticalSlides.forEach( function( verticalSlide, y ) {
314
-
315
- if( y > 0 ) verticalSlide.classList.add( 'future' );
316
-
317
- } );
318
-
319
- } );
320
-
321
- }
322
-
323
348
  /**
324
349
  * Finds and stores references to DOM elements which are
325
350
  * required by the presentation. If a required element is
@@ -349,6 +374,9 @@ var Reveal = (function(){
349
374
  '<div class="navigate-up"></div>' +
350
375
  '<div class="navigate-down"></div>' );
351
376
 
377
+ // Slide number
378
+ dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
+
352
380
  // State background element [DEPRECATED]
353
381
  createSingletonNode( dom.wrapper, 'div', 'state-background', null );
354
382
 
@@ -422,7 +450,7 @@ var Reveal = (function(){
422
450
 
423
451
  if( data.background ) {
424
452
  // Auto-wrap image urls in url(...)
425
- 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 ) ) {
426
454
  element.style.backgroundImage = 'url('+ data.background +')';
427
455
  }
428
456
  else {
@@ -430,6 +458,10 @@ var Reveal = (function(){
430
458
  }
431
459
  }
432
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
+
433
465
  // Additional and optional background properties
434
466
  if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
435
467
  if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
@@ -470,6 +502,28 @@ var Reveal = (function(){
470
502
 
471
503
  } );
472
504
 
505
+ // Add parallax background if specified
506
+ if( config.parallaxBackgroundImage ) {
507
+
508
+ dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
509
+ dom.background.style.backgroundSize = config.parallaxBackgroundSize;
510
+
511
+ // Make sure the below properties are set on the element - these properties are
512
+ // needed for proper transitions to be set on the element via CSS. To remove
513
+ // annoying background slide-in effect when the presentation starts, apply
514
+ // these properties after short time delay
515
+ setTimeout( function() {
516
+ dom.wrapper.classList.add( 'has-parallax-background' );
517
+ }, 1 );
518
+
519
+ }
520
+ else {
521
+
522
+ dom.background.style.backgroundImage = '';
523
+ dom.wrapper.classList.remove( 'has-parallax-background' );
524
+
525
+ }
526
+
473
527
  }
474
528
 
475
529
  /**
@@ -478,6 +532,8 @@ var Reveal = (function(){
478
532
  */
479
533
  function configure( options ) {
480
534
 
535
+ var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
536
+
481
537
  dom.wrapper.classList.remove( config.transition );
482
538
 
483
539
  // New config options may be passed when this method
@@ -485,7 +541,7 @@ var Reveal = (function(){
485
541
  if( typeof options === 'object' ) extend( config, options );
486
542
 
487
543
  // Force linear transition based on browser capabilities
488
- if( supports3DTransforms === false ) config.transition = 'linear';
544
+ if( features.transforms3d === false ) config.transition = 'linear';
489
545
 
490
546
  dom.wrapper.classList.add( config.transition );
491
547
 
@@ -535,6 +591,20 @@ var Reveal = (function(){
535
591
  enablePreviewLinks( '[data-preview-link]' );
536
592
  }
537
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
+
538
608
  // Load the theme in the config, if it's not already loaded
539
609
  if( config.theme && dom.theme ) {
540
610
  var themeURL = dom.theme.getAttribute( 'href' );
@@ -578,10 +648,28 @@ var Reveal = (function(){
578
648
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
579
649
  }
580
650
 
581
- if ( config.progress && dom.progress ) {
651
+ if( config.progress && dom.progress ) {
582
652
  dom.progress.addEventListener( 'click', onProgressClicked, false );
583
653
  }
584
654
 
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
+ }
671
+ }
672
+
585
673
  [ 'touchstart', 'click' ].forEach( function( eventName ) {
586
674
  dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
587
675
  dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
@@ -784,16 +872,6 @@ var Reveal = (function(){
784
872
  */
785
873
  function removeAddressBar() {
786
874
 
787
- // Portrait and not Chrome for iOS
788
- if( window.orientation === 0 && !/crios/gi.test( navigator.userAgent ) ) {
789
- document.documentElement.style.overflow = 'scroll';
790
- document.body.style.height = '120%';
791
- }
792
- else {
793
- document.documentElement.style.overflow = '';
794
- document.body.style.height = '100%';
795
- }
796
-
797
875
  setTimeout( function() {
798
876
  window.scrollTo( 0, 1 );
799
877
  }, 10 );
@@ -818,7 +896,7 @@ var Reveal = (function(){
818
896
  */
819
897
  function enableRollingLinks() {
820
898
 
821
- if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
899
+ if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
822
900
  var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
823
901
 
824
902
  for( var i = 0, len = anchors.length; i < len; i++ ) {
@@ -941,38 +1019,6 @@ var Reveal = (function(){
941
1019
 
942
1020
  }
943
1021
 
944
- /**
945
- * Return a sorted fragments list, ordered by an increasing
946
- * "data-fragment-index" attribute.
947
- *
948
- * Fragments will be revealed in the order that they are returned by
949
- * this function, so you can use the index attributes to control the
950
- * order of fragment appearance.
951
- *
952
- * To maintain a sensible default fragment order, fragments are presumed
953
- * to be passed in document order. This function adds a "fragment-index"
954
- * attribute to each node if such an attribute is not already present,
955
- * and sets that attribute to an integer value which is the position of
956
- * the fragment within the fragments list.
957
- */
958
- function sortFragments( fragments ) {
959
-
960
- var a = toArray( fragments );
961
-
962
- a.forEach( function( el, idx ) {
963
- if( !el.hasAttribute( 'data-fragment-index' ) ) {
964
- el.setAttribute( 'data-fragment-index', idx );
965
- }
966
- } );
967
-
968
- a.sort( function( l, r ) {
969
- return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
970
- } );
971
-
972
- return a;
973
-
974
- }
975
-
976
1022
  /**
977
1023
  * Applies JavaScript-controlled layout rules to the
978
1024
  * presentation.
@@ -1038,7 +1084,7 @@ var Reveal = (function(){
1038
1084
  continue;
1039
1085
  }
1040
1086
 
1041
- if( config.center ) {
1087
+ if( config.center || slide.classList.contains( 'center' ) ) {
1042
1088
  // Vertical stacks are not centred since their section
1043
1089
  // children will be
1044
1090
  if( slide.classList.contains( 'stack' ) ) {
@@ -1055,6 +1101,7 @@ var Reveal = (function(){
1055
1101
  }
1056
1102
 
1057
1103
  updateProgress();
1104
+ updateParallax();
1058
1105
 
1059
1106
  }
1060
1107
 
@@ -1471,19 +1518,9 @@ var Reveal = (function(){
1471
1518
  // Store references to the previous and current slides
1472
1519
  currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
1473
1520
 
1474
-
1475
1521
  // Show fragment, if specified
1476
1522
  if( typeof f !== 'undefined' ) {
1477
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
1478
-
1479
- toArray( fragments ).forEach( function( fragment, indexf ) {
1480
- if( indexf < f ) {
1481
- fragment.classList.add( 'visible' );
1482
- }
1483
- else {
1484
- fragment.classList.remove( 'visible' );
1485
- }
1486
- } );
1523
+ navigateFragment( f );
1487
1524
  }
1488
1525
 
1489
1526
  // Dispatch an event if the slide changed
@@ -1533,10 +1570,14 @@ var Reveal = (function(){
1533
1570
  updateControls();
1534
1571
  updateProgress();
1535
1572
  updateBackground();
1573
+ updateParallax();
1574
+ updateSlideNumber();
1536
1575
 
1537
1576
  // Update the URL hash
1538
1577
  writeURL();
1539
1578
 
1579
+ cueAutoSlide();
1580
+
1540
1581
  }
1541
1582
 
1542
1583
  /**
@@ -1562,9 +1603,58 @@ var Reveal = (function(){
1562
1603
  // Re-create the slide backgrounds
1563
1604
  createBackgrounds();
1564
1605
 
1606
+ sortAllFragments();
1607
+
1565
1608
  updateControls();
1566
1609
  updateProgress();
1567
- 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
+ } );
1568
1658
 
1569
1659
  }
1570
1660
 
@@ -1617,16 +1707,27 @@ var Reveal = (function(){
1617
1707
  if( i < index ) {
1618
1708
  // Any element previous to index is given the 'past' class
1619
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
+ }
1620
1719
  }
1621
1720
  else if( i > index ) {
1622
1721
  // Any element subsequent to index is given the 'future' class
1623
1722
  element.classList.add( reverse ? 'past' : 'future' );
1624
1723
 
1625
- var fragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1724
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1626
1725
 
1627
1726
  // No fragments in future slides should be visible ahead of time
1628
- while( fragments.length ) {
1629
- fragments.pop().classList.remove( 'visible' );
1727
+ while( futureFragments.length ) {
1728
+ var futureFragment = futureFragments.pop();
1729
+ futureFragment.classList.remove( 'visible' );
1730
+ futureFragment.classList.remove( 'current-fragment' );
1630
1731
  }
1631
1732
  }
1632
1733
 
@@ -1647,18 +1748,6 @@ var Reveal = (function(){
1647
1748
  state = state.concat( slideState.split( ' ' ) );
1648
1749
  }
1649
1750
 
1650
- // If this slide has a data-autoslide attribute associated use this as
1651
- // autoSlide value otherwise use the global configured time
1652
- var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
1653
- if( slideAutoSlide ) {
1654
- autoSlide = parseInt( slideAutoSlide, 10 );
1655
- }
1656
- else {
1657
- autoSlide = config.autoSlide;
1658
- }
1659
-
1660
- cueAutoSlide();
1661
-
1662
1751
  }
1663
1752
  else {
1664
1753
  // Since there are no slides we can't be anywhere beyond the
@@ -1774,6 +1863,25 @@ var Reveal = (function(){
1774
1863
 
1775
1864
  }
1776
1865
 
1866
+ /**
1867
+ * Updates the slide number div to reflect the current slide.
1868
+ */
1869
+ function updateSlideNumber() {
1870
+
1871
+ // Update slide number if enabled
1872
+ if( config.slideNumber && dom.slideNumber) {
1873
+
1874
+ // Display the number of the page using 'indexh - indexv' format
1875
+ var indexString = indexh;
1876
+ if( indexv > 0 ) {
1877
+ indexString += ' - ' + indexv;
1878
+ }
1879
+
1880
+ dom.slideNumber.innerHTML = indexString;
1881
+ }
1882
+
1883
+ }
1884
+
1777
1885
  /**
1778
1886
  * Updates the state of all control/navigation arrows.
1779
1887
  */
@@ -1825,29 +1933,70 @@ var Reveal = (function(){
1825
1933
  }
1826
1934
 
1827
1935
  /**
1828
- * Updates the background elements to reflect the current
1936
+ * Updates the background elements to reflect the current
1829
1937
  * slide.
1938
+ *
1939
+ * @param {Boolean} includeAll If true, the backgrounds of
1940
+ * all vertical slides (not just the present) will be updated.
1830
1941
  */
1831
- function updateBackground() {
1942
+ function updateBackground( includeAll ) {
1943
+
1944
+ var currentBackground = null;
1832
1945
 
1833
- // Update the classes of all backgrounds to match the
1946
+ // Reverse past/future classes when in RTL mode
1947
+ var horizontalPast = config.rtl ? 'future' : 'past',
1948
+ horizontalFuture = config.rtl ? 'past' : 'future';
1949
+
1950
+ // Update the classes of all backgrounds to match the
1834
1951
  // states of their slides (past/present/future)
1835
1952
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1836
1953
 
1837
- // Reverse past/future classes when in RTL mode
1838
- var horizontalPast = config.rtl ? 'future' : 'past',
1839
- 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
+ }
1840
1966
 
1841
- backgroundh.className = 'slide-background ' + ( h < indexh ? horizontalPast : h > indexh ? horizontalFuture : 'present' );
1967
+ if( includeAll || h === indexh ) {
1968
+ toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1842
1969
 
1843
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
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';
1844
1978
 
1845
- backgroundv.className = 'slide-background ' + ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
1979
+ // Only if this is the present horizontal and vertical slide
1980
+ if( h === indexh ) currentBackground = backgroundv;
1981
+ }
1846
1982
 
1847
- } );
1983
+ } );
1984
+ }
1848
1985
 
1849
1986
  } );
1850
1987
 
1988
+ // Don't transition between identical backgrounds. This
1989
+ // prevents unwanted flicker.
1990
+ if( currentBackground ) {
1991
+ var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
+ var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
+ if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
1994
+ dom.background.classList.add( 'no-transition' );
1995
+ }
1996
+
1997
+ previousBackground = currentBackground;
1998
+ }
1999
+
1851
2000
  // Allow the first background to apply without transition
1852
2001
  setTimeout( function() {
1853
2002
  dom.background.classList.remove( 'no-transition' );
@@ -1855,6 +2004,42 @@ var Reveal = (function(){
1855
2004
 
1856
2005
  }
1857
2006
 
2007
+ /**
2008
+ * Updates the position of the parallax background based
2009
+ * on the current slide index.
2010
+ */
2011
+ function updateParallax() {
2012
+
2013
+ if( config.parallaxBackgroundImage ) {
2014
+
2015
+ var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
+ verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
+
2018
+ var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
+ backgroundWidth, backgroundHeight;
2020
+
2021
+ if( backgroundSize.length === 1 ) {
2022
+ backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
2023
+ }
2024
+ else {
2025
+ backgroundWidth = parseInt( backgroundSize[0], 10 );
2026
+ backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
+ }
2028
+
2029
+ var slideWidth = dom.background.offsetWidth;
2030
+ var horizontalSlideCount = horizontalSlides.length;
2031
+ var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2032
+
2033
+ var slideHeight = dom.background.offsetHeight;
2034
+ var verticalSlideCount = verticalSlides.length;
2035
+ var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2036
+
2037
+ dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2038
+
2039
+ }
2040
+
2041
+ }
2042
+
1858
2043
  /**
1859
2044
  * Determine what available routes there are for navigation.
1860
2045
  *
@@ -1912,7 +2097,7 @@ var Reveal = (function(){
1912
2097
  */
1913
2098
  function startEmbeddedContent( slide ) {
1914
2099
 
1915
- if( slide ) {
2100
+ if( slide && !isSpeakerNotes() ) {
1916
2101
  // HTML5 media elements
1917
2102
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
1918
2103
  if( el.hasAttribute( 'data-autoplay' ) ) {
@@ -1920,10 +2105,15 @@ var Reveal = (function(){
1920
2105
  }
1921
2106
  } );
1922
2107
 
2108
+ // iframe embeds
2109
+ toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
+ el.contentWindow.postMessage( 'slide:start', '*' );
2111
+ });
2112
+
1923
2113
  // YouTube embeds
1924
2114
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1925
2115
  if( el.hasAttribute( 'data-autoplay' ) ) {
1926
- el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
2116
+ el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
1927
2117
  }
1928
2118
  });
1929
2119
  }
@@ -1944,16 +2134,31 @@ var Reveal = (function(){
1944
2134
  }
1945
2135
  } );
1946
2136
 
2137
+ // iframe embeds
2138
+ toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
+ el.contentWindow.postMessage( 'slide:stop', '*' );
2140
+ });
2141
+
1947
2142
  // YouTube embeds
1948
2143
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
1949
2144
  if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
1950
- el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
2145
+ el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
1951
2146
  }
1952
2147
  });
1953
2148
  }
1954
2149
 
1955
2150
  }
1956
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
+
1957
2162
  /**
1958
2163
  * Reads the current URL (hash) and navigates accordingly.
1959
2164
  */
@@ -2068,7 +2273,7 @@ var Reveal = (function(){
2068
2273
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2069
2274
  if( hasFragments ) {
2070
2275
  var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2071
- f = visibleFragments.length;
2276
+ f = visibleFragments.length - 1;
2072
2277
  }
2073
2278
  }
2074
2279
 
@@ -2077,83 +2282,226 @@ var Reveal = (function(){
2077
2282
  }
2078
2283
 
2079
2284
  /**
2080
- * Navigate to the next slide fragment.
2285
+ * Return a sorted fragments list, ordered by an increasing
2286
+ * "data-fragment-index" attribute.
2081
2287
  *
2082
- * @return {Boolean} true if there was a next fragment,
2083
- * 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.
2084
2297
  */
2085
- function nextFragment() {
2298
+ function sortFragments( fragments ) {
2086
2299
 
2087
- if( currentSlide && config.fragments ) {
2088
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );
2300
+ fragments = toArray( fragments );
2089
2301
 
2090
- if( fragments.length ) {
2091
- // Find the index of the next fragment
2092
- var index = fragments[0].getAttribute( 'data-fragment-index' );
2302
+ var ordered = [],
2303
+ unordered = [],
2304
+ sorted = [];
2093
2305
 
2094
- // Find all fragments with the same index
2095
- fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
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 );
2096
2310
 
2097
- toArray( fragments ).forEach( function( element ) {
2098
- element.classList.add( 'visible' );
2099
- } );
2100
-
2101
- // Notify subscribers of the change
2102
- dispatchEvent( 'fragmentshown', { fragment: fragments[0], fragments: fragments } );
2311
+ if( !ordered[index] ) {
2312
+ ordered[index] = [];
2313
+ }
2103
2314
 
2104
- updateControls();
2105
- return true;
2315
+ ordered[index].push( fragment );
2106
2316
  }
2107
- }
2317
+ else {
2318
+ unordered.push( [ fragment ] );
2319
+ }
2320
+ } );
2108
2321
 
2109
- 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;
2110
2342
 
2111
2343
  }
2112
2344
 
2113
2345
  /**
2114
- * Navigate to the previous slide fragment.
2346
+ * Navigate to the specified slide fragment.
2115
2347
  *
2116
- * @return {Boolean} true if there was a previous fragment,
2117
- * 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
2118
2355
  */
2119
- function previousFragment() {
2356
+ function navigateFragment( index, offset ) {
2120
2357
 
2121
2358
  if( currentSlide && config.fragments ) {
2122
- var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );
2123
2359
 
2360
+ var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
2124
2361
  if( fragments.length ) {
2125
- // Find the index of the previous fragment
2126
- var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
2127
2362
 
2128
- // Find all fragments with the same index
2129
- 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
+
2130
2406
 
2131
- toArray( fragments ).forEach( function( f ) {
2132
- f.classList.remove( 'visible' );
2133
2407
  } );
2134
2408
 
2135
- // Notify subscribers of the change
2136
- 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
+ }
2137
2416
 
2138
2417
  updateControls();
2139
- return true;
2418
+
2419
+ return !!( fragmentsShown.length || fragmentsHidden.length );
2420
+
2140
2421
  }
2422
+
2141
2423
  }
2142
2424
 
2143
2425
  return false;
2144
2426
 
2145
2427
  }
2146
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
+
2147
2453
  /**
2148
2454
  * Cues a new automated slide if enabled in the config.
2149
2455
  */
2150
2456
  function cueAutoSlide() {
2151
2457
 
2152
- 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
+ }
2153
2504
 
2154
- // Cue the next auto-slide if enabled
2155
- if( autoSlide && !isPaused() && !isOverview() ) {
2156
- autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2157
2505
  }
2158
2506
 
2159
2507
  }
@@ -2164,6 +2512,25 @@ var Reveal = (function(){
2164
2512
  function cancelAutoSlide() {
2165
2513
 
2166
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();
2167
2534
 
2168
2535
  }
2169
2536
 
@@ -2263,14 +2630,25 @@ var Reveal = (function(){
2263
2630
  // ----------------------------- EVENTS -------------------------------//
2264
2631
  // --------------------------------------------------------------------//
2265
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
+ }
2266
2644
 
2267
2645
  /**
2268
2646
  * Handler for the document level 'keydown' event.
2269
- *
2270
- * @param {Object} event
2271
2647
  */
2272
2648
  function onDocumentKeyDown( event ) {
2273
2649
 
2650
+ onUserInput( event );
2651
+
2274
2652
  // Check if there's a focused element that could be using
2275
2653
  // the keyboard
2276
2654
  var activeElement = document.activeElement;
@@ -2357,8 +2735,13 @@ var Reveal = (function(){
2357
2735
  event.preventDefault();
2358
2736
  }
2359
2737
  // ESC or O key
2360
- else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && supports3DTransforms ) {
2361
- toggleOverview();
2738
+ else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
+ if( dom.preview ) {
2740
+ closePreview();
2741
+ }
2742
+ else {
2743
+ toggleOverview();
2744
+ }
2362
2745
 
2363
2746
  event.preventDefault();
2364
2747
  }
@@ -2400,6 +2783,8 @@ var Reveal = (function(){
2400
2783
 
2401
2784
  // Each touch should only trigger one action
2402
2785
  if( !touch.captured ) {
2786
+ onUserInput( event );
2787
+
2403
2788
  var currentX = event.touches[0].clientX;
2404
2789
  var currentY = event.touches[0].clientY;
2405
2790
 
@@ -2553,6 +2938,8 @@ var Reveal = (function(){
2553
2938
  */
2554
2939
  function onProgressClicked( event ) {
2555
2940
 
2941
+ onUserInput( event );
2942
+
2556
2943
  event.preventDefault();
2557
2944
 
2558
2945
  var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
@@ -2565,12 +2952,12 @@ var Reveal = (function(){
2565
2952
  /**
2566
2953
  * Event handler for navigation control buttons.
2567
2954
  */
2568
- function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
2569
- function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
2570
- function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
2571
- function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
2572
- function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
2573
- 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(); }
2574
2961
 
2575
2962
  /**
2576
2963
  * Handler for the window level 'hashchange' event.
@@ -2590,6 +2977,24 @@ var Reveal = (function(){
2590
2977
 
2591
2978
  }
2592
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
+
2593
2998
  /**
2594
2999
  * Invoked when a slide is and we're in the overview.
2595
3000
  */
@@ -2636,6 +3041,191 @@ var Reveal = (function(){
2636
3041
 
2637
3042
  }
2638
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
+
2639
3229
 
2640
3230
  // --------------------------------------------------------------------//
2641
3231
  // ------------------------------- API --------------------------------//
@@ -2655,6 +3245,9 @@ var Reveal = (function(){
2655
3245
  down: navigateDown,
2656
3246
  prev: navigatePrev,
2657
3247
  next: navigateNext,
3248
+
3249
+ // Fragment methods
3250
+ navigateFragment: navigateFragment,
2658
3251
  prevFragment: previousFragment,
2659
3252
  nextFragment: nextFragment,
2660
3253
 
@@ -2729,10 +3322,22 @@ var Reveal = (function(){
2729
3322
  getQueryHash: function() {
2730
3323
  var query = {};
2731
3324
 
2732
- location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
3325
+ location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {
2733
3326
  query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
2734
3327
  } );
2735
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
+
2736
3341
  return query;
2737
3342
  },
2738
3343