reveal-ck 0.1.8 → 0.2.0

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