slippery 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +2 -2
  3. data/assets/LICENSE.md +21 -0
  4. data/assets/README.md +48 -0
  5. data/assets/highlight.js/CHANGES.md +693 -0
  6. data/assets/highlight.js/README.md +86 -37
  7. data/assets/highlight.js/README.ru.md +65 -24
  8. data/assets/highlight.js/highlight.pack.js +2 -1
  9. data/assets/highlight.js/styles/agate.css +108 -0
  10. data/assets/highlight.js/styles/androidstudio.css +66 -0
  11. data/assets/highlight.js/styles/arduino-light.css +88 -0
  12. data/assets/highlight.js/styles/arta.css +25 -93
  13. data/assets/highlight.js/styles/ascetic.css +20 -28
  14. data/assets/highlight.js/styles/atelier-cave-dark.css +83 -0
  15. data/assets/highlight.js/styles/atelier-cave-light.css +85 -0
  16. data/assets/highlight.js/styles/atelier-dune-dark.css +69 -0
  17. data/assets/highlight.js/styles/atelier-dune-light.css +69 -0
  18. data/assets/highlight.js/styles/atelier-estuary-dark.css +84 -0
  19. data/assets/highlight.js/styles/atelier-estuary-light.css +84 -0
  20. data/assets/highlight.js/styles/atelier-forest-dark.css +69 -0
  21. data/assets/highlight.js/styles/atelier-forest-light.css +69 -0
  22. data/assets/highlight.js/styles/atelier-heath-dark.css +69 -0
  23. data/assets/highlight.js/styles/atelier-heath-light.css +69 -0
  24. data/assets/highlight.js/styles/atelier-lakeside-dark.css +69 -0
  25. data/assets/highlight.js/styles/atelier-lakeside-light.css +69 -0
  26. data/assets/highlight.js/styles/atelier-plateau-dark.css +84 -0
  27. data/assets/highlight.js/styles/atelier-plateau-light.css +84 -0
  28. data/assets/highlight.js/styles/atelier-savanna-dark.css +84 -0
  29. data/assets/highlight.js/styles/atelier-savanna-light.css +84 -0
  30. data/assets/highlight.js/styles/atelier-seaside-dark.css +69 -0
  31. data/assets/highlight.js/styles/atelier-seaside-light.css +69 -0
  32. data/assets/highlight.js/styles/atelier-sulphurpool-dark.css +69 -0
  33. data/assets/highlight.js/styles/atelier-sulphurpool-light.css +69 -0
  34. data/assets/highlight.js/styles/atom-one-dark.css +96 -0
  35. data/assets/highlight.js/styles/atom-one-light.css +96 -0
  36. data/assets/highlight.js/styles/brown-paper.css +64 -0
  37. data/assets/highlight.js/styles/codepen-embed.css +22 -70
  38. data/assets/highlight.js/styles/color-brewer.css +20 -118
  39. data/assets/highlight.js/styles/darcula.css +77 -0
  40. data/assets/highlight.js/styles/dark.css +20 -62
  41. data/assets/highlight.js/styles/darkula.css +6 -0
  42. data/assets/highlight.js/styles/default.css +67 -121
  43. data/assets/highlight.js/styles/docco.css +22 -61
  44. data/assets/highlight.js/styles/dracula.css +76 -0
  45. data/assets/highlight.js/styles/far.css +26 -67
  46. data/assets/highlight.js/styles/foundation.css +22 -70
  47. data/assets/highlight.js/styles/github-gist.css +71 -0
  48. data/assets/highlight.js/styles/github.css +23 -51
  49. data/assets/highlight.js/styles/googlecode.css +29 -88
  50. data/assets/highlight.js/styles/grayscale.css +101 -0
  51. data/assets/highlight.js/styles/gruvbox-dark.css +108 -0
  52. data/assets/highlight.js/styles/gruvbox-light.css +108 -0
  53. data/assets/highlight.js/styles/hopscotch.css +83 -0
  54. data/assets/highlight.js/styles/hybrid.css +22 -91
  55. data/assets/highlight.js/styles/idea.css +24 -53
  56. data/assets/highlight.js/styles/ir-black.css +73 -0
  57. data/assets/highlight.js/styles/kimbie.dark.css +24 -46
  58. data/assets/highlight.js/styles/kimbie.light.css +24 -46
  59. data/assets/highlight.js/styles/magula.css +19 -71
  60. data/assets/highlight.js/styles/mono-blue.css +22 -33
  61. data/assets/highlight.js/styles/monokai-sublime.css +83 -0
  62. data/assets/highlight.js/styles/monokai.css +23 -80
  63. data/assets/highlight.js/styles/obsidian.css +26 -92
  64. data/assets/highlight.js/styles/ocean.css +74 -0
  65. data/assets/highlight.js/styles/paraiso-dark.css +72 -0
  66. data/assets/highlight.js/styles/paraiso-light.css +72 -0
  67. data/assets/highlight.js/styles/pojoaque.css +29 -54
  68. data/assets/highlight.js/styles/purebasic.css +96 -0
  69. data/assets/highlight.js/styles/qtcreator_dark.css +83 -0
  70. data/assets/highlight.js/styles/qtcreator_light.css +83 -0
  71. data/assets/highlight.js/styles/railscasts.css +32 -111
  72. data/assets/highlight.js/styles/rainbow.css +34 -58
  73. data/assets/highlight.js/styles/routeros.css +108 -0
  74. data/assets/highlight.js/styles/school-book.css +72 -0
  75. data/assets/highlight.js/styles/solarized-dark.css +84 -0
  76. data/assets/highlight.js/styles/solarized-light.css +84 -0
  77. data/assets/highlight.js/styles/sunburst.css +28 -91
  78. data/assets/highlight.js/styles/tomorrow-night-blue.css +23 -43
  79. data/assets/highlight.js/styles/tomorrow-night-bright.css +23 -43
  80. data/assets/highlight.js/styles/tomorrow-night-eighties.css +23 -43
  81. data/assets/highlight.js/styles/tomorrow-night.css +23 -43
  82. data/assets/highlight.js/styles/tomorrow.css +23 -43
  83. data/assets/highlight.js/styles/vs.css +33 -59
  84. data/assets/highlight.js/styles/vs2015.css +115 -0
  85. data/assets/highlight.js/styles/xcode.css +28 -94
  86. data/assets/highlight.js/styles/xt256.css +92 -0
  87. data/assets/highlight.js/styles/zenburn.css +26 -65
  88. data/assets/index.html +186 -0
  89. data/assets/pictures/cover.jpg +0 -0
  90. data/assets/pictures/logo.svg +3 -0
  91. data/assets/pictures/picture.jpg +0 -0
  92. data/assets/reveal.js/css/print/paper.css +193 -167
  93. data/assets/reveal.js/css/print/pdf.css +20 -53
  94. data/assets/reveal.js/css/reveal.css +953 -1663
  95. data/assets/reveal.js/css/reveal.min.css +3 -3
  96. data/assets/reveal.js/css/reveal.scss +1319 -0
  97. data/assets/reveal.js/css/theme/README.md +2 -4
  98. data/assets/reveal.js/css/theme/beige.css +183 -60
  99. data/assets/reveal.js/css/theme/black.css +267 -0
  100. data/assets/reveal.js/css/theme/blood.css +190 -80
  101. data/assets/reveal.js/css/theme/league.css +273 -0
  102. data/assets/reveal.js/css/theme/moon.css +174 -51
  103. data/assets/reveal.js/css/theme/night.css +171 -42
  104. data/assets/reveal.js/css/theme/serif.css +187 -58
  105. data/assets/reveal.js/css/theme/simple.css +179 -50
  106. data/assets/reveal.js/css/theme/sky.css +176 -47
  107. data/assets/reveal.js/css/theme/solarized.css +174 -51
  108. data/assets/reveal.js/css/theme/source/beige.scss +1 -12
  109. data/assets/reveal.js/css/theme/source/black.scss +49 -0
  110. data/assets/reveal.js/css/theme/source/blood.scss +3 -15
  111. data/assets/reveal.js/css/theme/source/{default.scss → league.scss} +5 -13
  112. data/assets/reveal.js/css/theme/source/moon.scss +1 -12
  113. data/assets/reveal.js/css/theme/source/serif.scss +1 -1
  114. data/assets/reveal.js/css/theme/source/sky.scss +1 -1
  115. data/assets/reveal.js/css/theme/source/solarized.scss +1 -12
  116. data/assets/reveal.js/css/theme/source/white.scss +49 -0
  117. data/assets/reveal.js/css/theme/template/settings.scss +13 -4
  118. data/assets/reveal.js/css/theme/template/theme.scss +193 -14
  119. data/assets/reveal.js/css/theme/white.css +267 -0
  120. data/assets/reveal.js/js/reveal.js +1572 -446
  121. data/assets/reveal.js/js/reveal.min.js +4 -4
  122. data/assets/reveal.js/lib/css/zenburn.css +74 -71
  123. data/assets/reveal.js/lib/font/league-gothic/league-gothic.css +10 -0
  124. data/assets/reveal.js/lib/font/league-gothic/league-gothic.eot +0 -0
  125. data/assets/reveal.js/lib/font/league-gothic/league-gothic.ttf +0 -0
  126. data/assets/reveal.js/lib/font/league-gothic/league-gothic.woff +0 -0
  127. data/assets/reveal.js/lib/font/source-sans-pro/LICENSE +45 -0
  128. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.eot +0 -0
  129. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.ttf +0 -0
  130. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-italic.woff +0 -0
  131. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.eot +0 -0
  132. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.ttf +0 -0
  133. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-regular.woff +0 -0
  134. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.eot +0 -0
  135. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.ttf +0 -0
  136. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibold.woff +0 -0
  137. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot +0 -0
  138. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf +0 -0
  139. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff +0 -0
  140. data/assets/reveal.js/lib/font/source-sans-pro/source-sans-pro.css +39 -0
  141. data/assets/reveal.js/plugin/highlight/highlight.js +2 -4
  142. data/assets/reveal.js/plugin/leap/leap.js +4 -2
  143. data/assets/reveal.js/plugin/markdown/example.html +3 -3
  144. data/assets/reveal.js/plugin/markdown/markdown.js +10 -9
  145. data/assets/reveal.js/plugin/markdown/marked.js +2 -33
  146. data/assets/reveal.js/plugin/math/math.js +1 -1
  147. data/assets/reveal.js/plugin/notes/notes.html +321 -182
  148. data/assets/reveal.js/plugin/notes/notes.js +89 -45
  149. data/assets/reveal.js/plugin/notes-server/client.js +49 -46
  150. data/assets/reveal.js/plugin/notes-server/index.js +28 -21
  151. data/assets/reveal.js/plugin/notes-server/notes.html +351 -97
  152. data/assets/reveal.js/plugin/print-pdf/print-pdf.js +24 -20
  153. data/assets/reveal.js/plugin/zoom-js/zoom.js +78 -58
  154. data/assets/shower/shower.min.js +8 -0
  155. data/assets/shower/themes/material/LICENSE.md +21 -0
  156. data/assets/shower/themes/material/README.md +45 -0
  157. data/assets/shower/themes/material/fonts/COPYRIGHT.txt +1 -0
  158. data/assets/shower/themes/material/fonts/LICENSE.txt +202 -0
  159. data/assets/shower/themes/material/fonts/roboto-bold-italic.woff2 +0 -0
  160. data/assets/shower/themes/material/fonts/roboto-bold.woff2 +0 -0
  161. data/assets/shower/themes/material/fonts/roboto-italic.woff2 +0 -0
  162. data/assets/shower/themes/material/fonts/roboto-light.woff2 +0 -0
  163. data/assets/shower/themes/material/fonts/roboto-mono-regular.woff2 +0 -0
  164. data/assets/shower/themes/material/fonts/roboto-regular.woff2 +0 -0
  165. data/assets/shower/themes/material/index.html +488 -0
  166. data/assets/shower/themes/material/pictures/canvas.png +0 -0
  167. data/assets/shower/themes/material/pictures/exact.svg +3 -0
  168. data/assets/shower/themes/material/pictures/picture-1.svg +3 -0
  169. data/assets/shower/themes/material/pictures/picture-2.svg +3 -0
  170. data/assets/shower/themes/material/pictures/picture-3.svg +3 -0
  171. data/assets/shower/themes/material/pictures/square.svg +3 -0
  172. data/assets/shower/themes/material/pictures/tall.svg +3 -0
  173. data/assets/shower/themes/material/pictures/wide.svg +3 -0
  174. data/assets/shower/themes/material/styles/blocks/badge.css +65 -0
  175. data/assets/shower/themes/material/styles/blocks/caption.css +67 -0
  176. data/assets/shower/themes/material/styles/blocks/progress.css +37 -0
  177. data/assets/shower/themes/material/styles/blocks/region.css +29 -0
  178. data/assets/shower/themes/material/styles/fonts.css +64 -0
  179. data/assets/shower/themes/material/styles/shower/grid.svg +50 -0
  180. data/assets/shower/themes/material/styles/shower/shower-full.css +13 -0
  181. data/assets/shower/themes/material/styles/shower/shower-grid.css +13 -0
  182. data/assets/shower/themes/material/styles/shower/shower-list.css +30 -0
  183. data/assets/shower/themes/material/styles/shower/shower-print.css +11 -0
  184. data/assets/shower/themes/material/styles/shower/shower.css +39 -0
  185. data/assets/shower/themes/material/styles/slide/content/basic.css +23 -0
  186. data/assets/shower/themes/material/styles/slide/content/code.css +80 -0
  187. data/assets/shower/themes/material/styles/slide/content/footer.css +29 -0
  188. data/assets/shower/themes/material/styles/slide/content/inline.css +49 -0
  189. data/assets/shower/themes/material/styles/slide/content/lists.css +54 -0
  190. data/assets/shower/themes/material/styles/slide/content/quote.css +24 -0
  191. data/assets/shower/themes/material/styles/slide/content/table.css +53 -0
  192. data/assets/shower/themes/material/styles/slide/elements/columns.css +19 -0
  193. data/assets/shower/themes/material/styles/slide/elements/copyright.css +38 -0
  194. data/assets/shower/themes/material/styles/slide/elements/cover.css +25 -0
  195. data/assets/shower/themes/material/styles/slide/elements/next.css +9 -0
  196. data/assets/shower/themes/material/styles/slide/elements/place.css +63 -0
  197. data/assets/shower/themes/material/styles/slide/elements/shout.css +53 -0
  198. data/assets/shower/themes/material/styles/slide/modifiers/black.css +5 -0
  199. data/assets/shower/themes/material/styles/slide/modifiers/clear.css +11 -0
  200. data/assets/shower/themes/material/styles/slide/modifiers/white.css +5 -0
  201. data/assets/shower/themes/material/styles/slide/slide-full.css +16 -0
  202. data/assets/shower/themes/material/styles/slide/slide-list.css +68 -0
  203. data/assets/shower/themes/material/styles/slide/slide.css +56 -0
  204. data/assets/shower/themes/material/styles/styles.css +9 -0
  205. data/assets/shower/themes/ribbon/LICENSE.md +21 -0
  206. data/assets/shower/themes/ribbon/README.md +41 -0
  207. data/assets/shower/themes/ribbon/fonts/LICENSE.txt +44 -0
  208. data/assets/shower/themes/ribbon/fonts/pt-mono-regular.woff2 +0 -0
  209. data/assets/shower/themes/ribbon/fonts/pt-sans-bold-italic.woff2 +0 -0
  210. data/assets/shower/themes/ribbon/fonts/pt-sans-bold.woff2 +0 -0
  211. data/assets/shower/themes/ribbon/fonts/pt-sans-italic.woff2 +0 -0
  212. data/assets/shower/themes/ribbon/fonts/pt-sans-narrow-bold.woff2 +0 -0
  213. data/assets/shower/themes/ribbon/fonts/pt-sans-regular.woff2 +0 -0
  214. data/assets/shower/themes/ribbon/index.html +490 -0
  215. data/assets/shower/themes/ribbon/pictures/canvas.png +0 -0
  216. data/assets/shower/themes/ribbon/pictures/exact.svg +3 -0
  217. data/assets/shower/themes/ribbon/pictures/picture-1.svg +3 -0
  218. data/assets/shower/themes/ribbon/pictures/picture-2.svg +3 -0
  219. data/assets/shower/themes/ribbon/pictures/picture-3.svg +3 -0
  220. data/assets/shower/themes/ribbon/pictures/square.svg +3 -0
  221. data/assets/shower/themes/ribbon/pictures/tall.svg +3 -0
  222. data/assets/shower/themes/ribbon/pictures/wide.svg +3 -0
  223. data/assets/shower/themes/ribbon/styles/blocks/badge.css +63 -0
  224. data/assets/shower/themes/ribbon/styles/blocks/caption.css +63 -0
  225. data/assets/shower/themes/ribbon/styles/blocks/progress.css +46 -0
  226. data/assets/shower/themes/ribbon/styles/blocks/region.css +29 -0
  227. data/assets/shower/themes/ribbon/styles/fonts.css +64 -0
  228. data/assets/shower/themes/ribbon/styles/shower/grid.svg +40 -0
  229. data/assets/shower/themes/ribbon/styles/shower/shower-full.css +13 -0
  230. data/assets/shower/themes/ribbon/styles/shower/shower-grid.css +13 -0
  231. data/assets/shower/themes/ribbon/styles/shower/shower-list.css +30 -0
  232. data/assets/shower/themes/ribbon/styles/shower/shower-print.css +11 -0
  233. data/assets/shower/themes/ribbon/styles/shower/shower.css +32 -0
  234. data/assets/shower/themes/ribbon/styles/slide/content/basic.css +21 -0
  235. data/assets/shower/themes/ribbon/styles/slide/content/code.css +82 -0
  236. data/assets/shower/themes/ribbon/styles/slide/content/footer.css +29 -0
  237. data/assets/shower/themes/ribbon/styles/slide/content/inline.css +50 -0
  238. data/assets/shower/themes/ribbon/styles/slide/content/lists.css +55 -0
  239. data/assets/shower/themes/ribbon/styles/slide/content/quote.css +23 -0
  240. data/assets/shower/themes/ribbon/styles/slide/content/table.css +53 -0
  241. data/assets/shower/themes/ribbon/styles/slide/elements/columns.css +19 -0
  242. data/assets/shower/themes/ribbon/styles/slide/elements/copyright.css +38 -0
  243. data/assets/shower/themes/ribbon/styles/slide/elements/cover.css +25 -0
  244. data/assets/shower/themes/ribbon/styles/slide/elements/next.css +9 -0
  245. data/assets/shower/themes/ribbon/styles/slide/elements/place.css +63 -0
  246. data/assets/shower/themes/ribbon/styles/slide/elements/shout.css +54 -0
  247. data/assets/shower/themes/ribbon/styles/slide/modifiers/black.css +5 -0
  248. data/assets/shower/themes/ribbon/styles/slide/modifiers/clear.css +11 -0
  249. data/assets/shower/themes/ribbon/styles/slide/modifiers/white.css +5 -0
  250. data/assets/shower/themes/ribbon/styles/slide/ribbon.svg +1 -0
  251. data/assets/shower/themes/ribbon/styles/slide/slide-full.css +16 -0
  252. data/assets/shower/themes/ribbon/styles/slide/slide-list.css +68 -0
  253. data/assets/shower/themes/ribbon/styles/slide/slide.css +60 -0
  254. data/assets/shower/themes/ribbon/styles/styles.css +9 -0
  255. data/lib/slippery/presentation.rb +4 -0
  256. data/lib/slippery/processors/add_highlight.rb +3 -8
  257. data/lib/slippery/processors/reveal_js/add_reveal_js.rb +1 -1
  258. data/lib/slippery/processors/shower.rb +76 -0
  259. data/lib/slippery/rake_tasks.rb +8 -3
  260. data/lib/slippery/version.rb +1 -1
  261. data/lib/slippery.rb +1 -0
  262. data/slippery.gemspec +3 -3
  263. metadata +194 -56
  264. data/assets/highlight.js/highlight-0.8.default.min.css +0 -1
  265. data/assets/highlight.js/highlight-0.8.min.js +0 -1
  266. data/assets/highlight.js/styles/atelier-dune.dark.css +0 -95
  267. data/assets/highlight.js/styles/atelier-dune.light.css +0 -95
  268. data/assets/highlight.js/styles/atelier-forest.dark.css +0 -95
  269. data/assets/highlight.js/styles/atelier-forest.light.css +0 -95
  270. data/assets/highlight.js/styles/atelier-heath.dark.css +0 -95
  271. data/assets/highlight.js/styles/atelier-heath.light.css +0 -95
  272. data/assets/highlight.js/styles/atelier-lakeside.dark.css +0 -95
  273. data/assets/highlight.js/styles/atelier-lakeside.light.css +0 -95
  274. data/assets/highlight.js/styles/atelier-seaside.dark.css +0 -95
  275. data/assets/highlight.js/styles/atelier-seaside.light.css +0 -95
  276. data/assets/highlight.js/styles/brown_paper.css +0 -105
  277. data/assets/highlight.js/styles/ir_black.css +0 -110
  278. data/assets/highlight.js/styles/monokai_sublime.css +0 -148
  279. data/assets/highlight.js/styles/paraiso.dark.css +0 -95
  280. data/assets/highlight.js/styles/paraiso.light.css +0 -95
  281. data/assets/highlight.js/styles/school_book.css +0 -113
  282. data/assets/highlight.js/styles/solarized_dark.css +0 -109
  283. data/assets/highlight.js/styles/solarized_light.css +0 -109
  284. data/assets/reveal.js/css/theme/default.css +0 -148
  285. data/assets/reveal.js/lib/font/league_gothic-webfont.eot +0 -0
  286. data/assets/reveal.js/lib/font/league_gothic-webfont.svg +0 -230
  287. data/assets/reveal.js/lib/font/league_gothic-webfont.ttf +0 -0
  288. data/assets/reveal.js/lib/font/league_gothic-webfont.woff +0 -0
  289. data/assets/reveal.js/plugin/postmessage/example.html +0 -39
  290. data/assets/reveal.js/plugin/postmessage/postmessage.js +0 -42
  291. /data/assets/highlight.js/styles/{brown_papersq.png → brown-papersq.png} +0 -0
  292. /data/assets/highlight.js/styles/{school_book.png → school-book.png} +0 -0
  293. /data/assets/reveal.js/lib/font/{league_gothic_license → league-gothic/LICENSE} +0 -0
@@ -3,18 +3,34 @@
3
3
  * http://lab.hakim.se/reveal-js
4
4
  * MIT licensed
5
5
  *
6
- * Copyright (C) 2014 Hakim El Hattab, http://hakim.se
6
+ * Copyright (C) 2015 Hakim El Hattab, http://hakim.se
7
7
  */
8
- var Reveal = (function(){
8
+ (function( root, factory ) {
9
+ if( typeof define === 'function' && define.amd ) {
10
+ // AMD. Register as an anonymous module.
11
+ define( function() {
12
+ root.Reveal = factory();
13
+ return root.Reveal;
14
+ } );
15
+ } else if( typeof exports === 'object' ) {
16
+ // Node. Does not work with strict CommonJS.
17
+ module.exports = factory();
18
+ } else {
19
+ // Browser globals.
20
+ root.Reveal = factory();
21
+ }
22
+ }( this, function() {
9
23
 
10
24
  'use strict';
11
25
 
12
- var SLIDES_SELECTOR = '.reveal .slides section',
13
- HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
- VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
- HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
26
+ var Reveal;
16
27
 
17
- // Configurations defaults, can be overridden at initialization time
28
+ var SLIDES_SELECTOR = '.slides section',
29
+ HORIZONTAL_SLIDES_SELECTOR = '.slides>section',
30
+ VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section',
31
+ HOME_SLIDE_SELECTOR = '.slides>section:first-of-type',
32
+
33
+ // Configuration defaults, can be overridden at initialization time
18
34
  config = {
19
35
 
20
36
  // The "normal" size of the presentation, aspect ratio will be preserved
@@ -27,7 +43,7 @@ var Reveal = (function(){
27
43
 
28
44
  // Bounds for smallest/largest possible scale to apply to content
29
45
  minScale: 0.2,
30
- maxScale: 1.0,
46
+ maxScale: 1.5,
31
47
 
32
48
  // Display controls in the bottom right corner
33
49
  controls: true,
@@ -44,6 +60,9 @@ var Reveal = (function(){
44
60
  // Enable keyboard shortcuts for navigation
45
61
  keyboard: true,
46
62
 
63
+ // Optional function that blocks keyboard events when retuning false
64
+ keyboardCondition: null,
65
+
47
66
  // Enable the slide overview mode
48
67
  overview: true,
49
68
 
@@ -66,6 +85,13 @@ var Reveal = (function(){
66
85
  // i.e. contained within a limited portion of the screen
67
86
  embedded: false,
68
87
 
88
+ // Flags if we should show a help overlay when the questionmark
89
+ // key is pressed
90
+ help: true,
91
+
92
+ // Flags if it should be possible to pause the presentation (blackout)
93
+ pause: true,
94
+
69
95
  // Number of milliseconds between automatically proceeding to the
70
96
  // next slide, disabled when set to 0, this value can be overwritten
71
97
  // by using a data-autoslide attribute on your slides
@@ -86,20 +112,23 @@ var Reveal = (function(){
86
112
  // Opens links in an iframe preview overlay
87
113
  previewLinks: false,
88
114
 
89
- // Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
- focusBodyOnPageVisiblityChange: true,
115
+ // Exposes the reveal.js API through window.postMessage
116
+ postMessage: true,
91
117
 
92
- // Theme (see /css/theme)
93
- theme: null,
118
+ // Dispatches all reveal.js events to the parent window through postMessage
119
+ postMessageEvents: false,
120
+
121
+ // Focuses body when page changes visiblity to ensure keyboard shortcuts work
122
+ focusBodyOnPageVisibilityChange: true,
94
123
 
95
124
  // Transition style
96
- transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
125
+ transition: 'slide', // none/fade/slide/convex/concave/zoom
97
126
 
98
127
  // Transition speed
99
128
  transitionSpeed: 'default', // default/fast/slow
100
129
 
101
130
  // Transition style for full page slide backgrounds
102
- backgroundTransition: 'default', // default/linear/none
131
+ backgroundTransition: 'fade', // none/fade/slide/convex/concave/zoom
103
132
 
104
133
  // Parallax background image
105
134
  parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
@@ -107,6 +136,10 @@ var Reveal = (function(){
107
136
  // Parallax background size
108
137
  parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
109
138
 
139
+ // Amount of pixels to move the parallax background per slide step
140
+ parallaxBackgroundHorizontal: null,
141
+ parallaxBackgroundVertical: null,
142
+
110
143
  // Number of slides away from the current that are visible
111
144
  viewDistance: 3,
112
145
 
@@ -118,6 +151,9 @@ var Reveal = (function(){
118
151
  // Flags if reveal.js is loaded (has dispatched the 'ready' event)
119
152
  loaded = false,
120
153
 
154
+ // Flags if the overview mode is currently active
155
+ overview = false,
156
+
121
157
  // The horizontal and vertical index of the currently active slide
122
158
  indexh,
123
159
  indexv,
@@ -136,6 +172,10 @@ var Reveal = (function(){
136
172
  // The current scale of the presentation (see width/height config)
137
173
  scale = 1,
138
174
 
175
+ // CSS transform that is currently applied to the slides container,
176
+ // split into two groups
177
+ slidesTransform = { layout: '', overview: '' },
178
+
139
179
  // Cached references to DOM elements
140
180
  dom = {},
141
181
 
@@ -151,12 +191,6 @@ var Reveal = (function(){
151
191
  // Delays updates to the URL due to a Chrome thumbnailer bug
152
192
  writeURLTimeout = 0,
153
193
 
154
- // A delay used to activate the overview mode
155
- activateOverviewTimeout = 0,
156
-
157
- // A delay used to deactivate the overview mode
158
- deactivateOverviewTimeout = 0,
159
-
160
194
  // Flags if the interaction event listeners are bound
161
195
  eventsAreBound = false,
162
196
 
@@ -177,6 +211,21 @@ var Reveal = (function(){
177
211
  startCount: 0,
178
212
  captured: false,
179
213
  threshold: 40
214
+ },
215
+
216
+ // Holds information about the keyboard shortcuts
217
+ keyboardShortcuts = {
218
+ 'N , SPACE': 'Next slide',
219
+ 'P': 'Previous slide',
220
+ '← , H': 'Navigate left',
221
+ '→ , L': 'Navigate right',
222
+ '↑ , K': 'Navigate up',
223
+ '↓ , J': 'Navigate down',
224
+ 'Home': 'First slide',
225
+ 'End': 'Last slide',
226
+ 'B , .': 'Pause',
227
+ 'F': 'Fullscreen',
228
+ 'ESC, O': 'Slide overview'
180
229
  };
181
230
 
182
231
  /**
@@ -189,11 +238,30 @@ var Reveal = (function(){
189
238
  if( !features.transforms2d && !features.transforms3d ) {
190
239
  document.body.setAttribute( 'class', 'no-transforms' );
191
240
 
241
+ // Since JS won't be running any further, we load all lazy
242
+ // loading elements upfront
243
+ var images = toArray( document.getElementsByTagName( 'img' ) ),
244
+ iframes = toArray( document.getElementsByTagName( 'iframe' ) );
245
+
246
+ var lazyLoadable = images.concat( iframes );
247
+
248
+ for( var i = 0, len = lazyLoadable.length; i < len; i++ ) {
249
+ var element = lazyLoadable[i];
250
+ if( element.getAttribute( 'data-src' ) ) {
251
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
252
+ element.removeAttribute( 'data-src' );
253
+ }
254
+ }
255
+
192
256
  // If the browser doesn't support core features we won't be
193
257
  // using JavaScript to control the presentation
194
258
  return;
195
259
  }
196
260
 
261
+ // Cache references to key DOM elements
262
+ dom.wrapper = document.querySelector( '.reveal' );
263
+ dom.slides = document.querySelector( '.reveal .slides' );
264
+
197
265
  // Force a layout when the whole page, incl fonts, has loaded
198
266
  window.addEventListener( 'load', layout, false );
199
267
 
@@ -238,10 +306,15 @@ var Reveal = (function(){
238
306
 
239
307
  features.canvas = !!document.createElement( 'canvas' ).getContext;
240
308
 
241
- isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
309
+ features.touch = !!( 'ontouchstart' in window );
242
310
 
243
- }
311
+ // Transitions in the overview are disabled in desktop and
312
+ // mobile Safari due to lag
313
+ features.overviewTransitions = !/Version\/[\d\.]+.*Safari/.test( navigator.userAgent );
244
314
 
315
+ isMobileDevice = /(iphone|ipod|ipad|android)/gi.test( navigator.userAgent );
316
+
317
+ }
245
318
 
246
319
  /**
247
320
  * Loads the dependencies of reveal.js. Dependencies are
@@ -316,6 +389,12 @@ var Reveal = (function(){
316
389
  // Make sure we've got all the DOM elements we need
317
390
  setupDOM();
318
391
 
392
+ // Listen to messages posted to this window
393
+ setupPostMessage();
394
+
395
+ // Prevent iframes from scrolling the slides out of view
396
+ setupIframeScrollPrevention();
397
+
319
398
  // Resets all vertical slides so that only the first is visible
320
399
  resetVerticalSlides();
321
400
 
@@ -343,6 +422,20 @@ var Reveal = (function(){
343
422
  } );
344
423
  }, 1 );
345
424
 
425
+ // Special setup and config is required when printing to PDF
426
+ if( isPrintingPDF() ) {
427
+ removeEventListeners();
428
+
429
+ // The document needs to have loaded for the PDF layout
430
+ // measurements to be accurate
431
+ if( document.readyState === 'complete' ) {
432
+ setupPDF();
433
+ }
434
+ else {
435
+ window.addEventListener( 'load', setupPDF );
436
+ }
437
+ }
438
+
346
439
  }
347
440
 
348
441
  /**
@@ -352,11 +445,6 @@ var Reveal = (function(){
352
445
  */
353
446
  function setupDOM() {
354
447
 
355
- // Cache references to key DOM elements
356
- dom.theme = document.querySelector( '#theme' );
357
- dom.wrapper = document.querySelector( '.reveal' );
358
- dom.slides = document.querySelector( '.reveal .slides' );
359
-
360
448
  // Prevent transitions while we're loading
361
449
  dom.slides.classList.add( 'no-transition' );
362
450
 
@@ -377,14 +465,14 @@ var Reveal = (function(){
377
465
  // Slide number
378
466
  dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
467
 
380
- // State background element [DEPRECATED]
381
- createSingletonNode( dom.wrapper, 'div', 'state-background', null );
382
-
383
468
  // Overlay graphic which is displayed during the paused mode
384
469
  createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
385
470
 
386
471
  // Cache references to elements
387
472
  dom.controls = document.querySelector( '.reveal .controls' );
473
+ dom.theme = document.querySelector( '#theme' );
474
+
475
+ dom.wrapper.setAttribute( 'role', 'application' );
388
476
 
389
477
  // There can be multiple instances of controls throughout the page
390
478
  dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
@@ -394,6 +482,120 @@ var Reveal = (function(){
394
482
  dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
483
  dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
484
 
485
+ dom.statusDiv = createStatusDiv();
486
+ }
487
+
488
+ /**
489
+ * Creates a hidden div with role aria-live to announce the
490
+ * current slide content. Hide the div off-screen to make it
491
+ * available only to Assistive Technologies.
492
+ */
493
+ function createStatusDiv() {
494
+
495
+ var statusDiv = document.getElementById( 'aria-status-div' );
496
+ if( !statusDiv ) {
497
+ statusDiv = document.createElement( 'div' );
498
+ statusDiv.style.position = 'absolute';
499
+ statusDiv.style.height = '1px';
500
+ statusDiv.style.width = '1px';
501
+ statusDiv.style.overflow ='hidden';
502
+ statusDiv.style.clip = 'rect( 1px, 1px, 1px, 1px )';
503
+ statusDiv.setAttribute( 'id', 'aria-status-div' );
504
+ statusDiv.setAttribute( 'aria-live', 'polite' );
505
+ statusDiv.setAttribute( 'aria-atomic','true' );
506
+ dom.wrapper.appendChild( statusDiv );
507
+ }
508
+ return statusDiv;
509
+
510
+ }
511
+
512
+ /**
513
+ * Configures the presentation for printing to a static
514
+ * PDF.
515
+ */
516
+ function setupPDF() {
517
+
518
+ var slideSize = getComputedSlideSize( window.innerWidth, window.innerHeight );
519
+
520
+ // Dimensions of the PDF pages
521
+ var pageWidth = Math.floor( slideSize.width * ( 1 + config.margin ) ),
522
+ pageHeight = Math.floor( slideSize.height * ( 1 + config.margin ) );
523
+
524
+ // Dimensions of slides within the pages
525
+ var slideWidth = slideSize.width,
526
+ slideHeight = slideSize.height;
527
+
528
+ // Let the browser know what page size we want to print
529
+ injectStyleSheet( '@page{size:'+ pageWidth +'px '+ pageHeight +'px; margin: 0;}' );
530
+
531
+ // Limit the size of certain elements to the dimensions of the slide
532
+ injectStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
533
+
534
+ document.body.classList.add( 'print-pdf' );
535
+ document.body.style.width = pageWidth + 'px';
536
+ document.body.style.height = pageHeight + 'px';
537
+
538
+ // Slide and slide background layout
539
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
540
+
541
+ // Vertical stacks are not centred since their section
542
+ // children will be
543
+ if( slide.classList.contains( 'stack' ) === false ) {
544
+ // Center the slide inside of the page, giving the slide some margin
545
+ var left = ( pageWidth - slideWidth ) / 2,
546
+ top = ( pageHeight - slideHeight ) / 2;
547
+
548
+ var contentHeight = getAbsoluteHeight( slide );
549
+ var numberOfPages = Math.max( Math.ceil( contentHeight / pageHeight ), 1 );
550
+
551
+ // Center slides vertically
552
+ if( numberOfPages === 1 && config.center || slide.classList.contains( 'center' ) ) {
553
+ top = Math.max( ( pageHeight - contentHeight ) / 2, 0 );
554
+ }
555
+
556
+ // Position the slide inside of the page
557
+ slide.style.left = left + 'px';
558
+ slide.style.top = top + 'px';
559
+ slide.style.width = slideWidth + 'px';
560
+
561
+ // TODO Backgrounds need to be multiplied when the slide
562
+ // stretches over multiple pages
563
+ var background = slide.querySelector( '.slide-background' );
564
+ if( background ) {
565
+ background.style.width = pageWidth + 'px';
566
+ background.style.height = ( pageHeight * numberOfPages ) + 'px';
567
+ background.style.top = -top + 'px';
568
+ background.style.left = -left + 'px';
569
+ }
570
+ }
571
+
572
+ } );
573
+
574
+ // Show all fragments
575
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' .fragment' ) ).forEach( function( fragment ) {
576
+ fragment.classList.add( 'visible' );
577
+ } );
578
+
579
+ }
580
+
581
+ /**
582
+ * This is an unfortunate necessity. Iframes can trigger the
583
+ * parent window to scroll, for example by focusing an input.
584
+ * This scrolling can not be prevented by hiding overflow in
585
+ * CSS so we have to resort to repeatedly checking if the
586
+ * browser has decided to offset our slides :(
587
+ */
588
+ function setupIframeScrollPrevention() {
589
+
590
+ if( dom.slides.querySelector( 'iframe' ) ) {
591
+ setInterval( function() {
592
+ if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
593
+ dom.wrapper.scrollTop = 0;
594
+ dom.wrapper.scrollLeft = 0;
595
+ }
596
+ }, 500 );
597
+ }
598
+
397
599
  }
398
600
 
399
601
  /**
@@ -403,15 +605,26 @@ var Reveal = (function(){
403
605
  */
404
606
  function createSingletonNode( container, tagname, classname, innerHTML ) {
405
607
 
406
- var node = container.querySelector( '.' + classname );
407
- if( !node ) {
408
- node = document.createElement( tagname );
409
- node.classList.add( classname );
410
- if( innerHTML !== null ) {
411
- node.innerHTML = innerHTML;
608
+ // Find all nodes matching the description
609
+ var nodes = container.querySelectorAll( '.' + classname );
610
+
611
+ // Check all matches to find one which is a direct child of
612
+ // the specified container
613
+ for( var i = 0; i < nodes.length; i++ ) {
614
+ var testNode = nodes[i];
615
+ if( testNode.parentNode === container ) {
616
+ return testNode;
412
617
  }
413
- container.appendChild( node );
414
618
  }
619
+
620
+ // If no node was found, create it now
621
+ var node = document.createElement( tagname );
622
+ node.classList.add( classname );
623
+ if( typeof innerHTML === 'string' ) {
624
+ node.innerHTML = innerHTML;
625
+ }
626
+ container.appendChild( node );
627
+
415
628
  return node;
416
629
 
417
630
  }
@@ -423,81 +636,36 @@ var Reveal = (function(){
423
636
  */
424
637
  function createBackgrounds() {
425
638
 
426
- if( isPrintingPDF() ) {
427
- document.body.classList.add( 'print-pdf' );
428
- }
639
+ var printMode = isPrintingPDF();
429
640
 
430
641
  // Clear prior backgrounds
431
642
  dom.background.innerHTML = '';
432
643
  dom.background.classList.add( 'no-transition' );
433
644
 
434
- // Helper method for creating a background element for the
435
- // given slide
436
- function _createBackground( slide, container ) {
437
-
438
- var data = {
439
- background: slide.getAttribute( 'data-background' ),
440
- backgroundSize: slide.getAttribute( 'data-background-size' ),
441
- backgroundImage: slide.getAttribute( 'data-background-image' ),
442
- backgroundColor: slide.getAttribute( 'data-background-color' ),
443
- backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
444
- backgroundPosition: slide.getAttribute( 'data-background-position' ),
445
- backgroundTransition: slide.getAttribute( 'data-background-transition' )
446
- };
447
-
448
- var element = document.createElement( 'div' );
449
- element.className = 'slide-background';
450
-
451
- if( data.background ) {
452
- // Auto-wrap image urls in url(...)
453
- if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
454
- element.style.backgroundImage = 'url('+ data.background +')';
455
- }
456
- else {
457
- element.style.background = data.background;
458
- }
459
- }
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
-
465
- // Additional and optional background properties
466
- if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
467
- if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
468
- if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
469
- if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
470
- if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
471
- if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
472
-
473
- container.appendChild( element );
474
-
475
- return element;
476
-
477
- }
478
-
479
645
  // Iterate over all horizontal slides
480
- toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
646
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
481
647
 
482
648
  var backgroundStack;
483
649
 
484
- if( isPrintingPDF() ) {
485
- backgroundStack = _createBackground( slideh, slideh );
650
+ if( printMode ) {
651
+ backgroundStack = createBackground( slideh, slideh );
486
652
  }
487
653
  else {
488
- backgroundStack = _createBackground( slideh, dom.background );
654
+ backgroundStack = createBackground( slideh, dom.background );
489
655
  }
490
656
 
491
657
  // Iterate over all vertical slides
492
658
  toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
493
659
 
494
- if( isPrintingPDF() ) {
495
- _createBackground( slidev, slidev );
660
+ if( printMode ) {
661
+ createBackground( slidev, slidev );
496
662
  }
497
663
  else {
498
- _createBackground( slidev, backgroundStack );
664
+ createBackground( slidev, backgroundStack );
499
665
  }
500
666
 
667
+ backgroundStack.classList.add( 'stack' );
668
+
501
669
  } );
502
670
 
503
671
  } );
@@ -526,13 +694,131 @@ var Reveal = (function(){
526
694
 
527
695
  }
528
696
 
697
+ /**
698
+ * Creates a background for the given slide.
699
+ *
700
+ * @param {HTMLElement} slide
701
+ * @param {HTMLElement} container The element that the background
702
+ * should be appended to
703
+ */
704
+ function createBackground( slide, container ) {
705
+
706
+ var data = {
707
+ background: slide.getAttribute( 'data-background' ),
708
+ backgroundSize: slide.getAttribute( 'data-background-size' ),
709
+ backgroundImage: slide.getAttribute( 'data-background-image' ),
710
+ backgroundVideo: slide.getAttribute( 'data-background-video' ),
711
+ backgroundIframe: slide.getAttribute( 'data-background-iframe' ),
712
+ backgroundColor: slide.getAttribute( 'data-background-color' ),
713
+ backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
714
+ backgroundPosition: slide.getAttribute( 'data-background-position' ),
715
+ backgroundTransition: slide.getAttribute( 'data-background-transition' )
716
+ };
717
+
718
+ var element = document.createElement( 'div' );
719
+
720
+ // Carry over custom classes from the slide to the background
721
+ element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' );
722
+
723
+ if( data.background ) {
724
+ // Auto-wrap image urls in url(...)
725
+ if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
726
+ slide.setAttribute( 'data-background-image', data.background );
727
+ }
728
+ else {
729
+ element.style.background = data.background;
730
+ }
731
+ }
732
+
733
+ // Create a hash for this combination of background settings.
734
+ // This is used to determine when two slide backgrounds are
735
+ // the same.
736
+ if( data.background || data.backgroundColor || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) {
737
+ element.setAttribute( 'data-background-hash', data.background +
738
+ data.backgroundSize +
739
+ data.backgroundImage +
740
+ data.backgroundVideo +
741
+ data.backgroundIframe +
742
+ data.backgroundColor +
743
+ data.backgroundRepeat +
744
+ data.backgroundPosition +
745
+ data.backgroundTransition );
746
+ }
747
+
748
+ // Additional and optional background properties
749
+ if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
750
+ if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
751
+ if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
752
+ if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
753
+ if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
754
+
755
+ container.appendChild( element );
756
+
757
+ // If backgrounds are being recreated, clear old classes
758
+ slide.classList.remove( 'has-dark-background' );
759
+ slide.classList.remove( 'has-light-background' );
760
+
761
+ // If this slide has a background color, add a class that
762
+ // signals if it is light or dark. If the slide has no background
763
+ // color, no class will be set
764
+ var computedBackgroundColor = window.getComputedStyle( element ).backgroundColor;
765
+ if( computedBackgroundColor ) {
766
+ var rgb = colorToRgb( computedBackgroundColor );
767
+
768
+ // Ignore fully transparent backgrounds. Some browsers return
769
+ // rgba(0,0,0,0) when reading the computed background color of
770
+ // an element with no background
771
+ if( rgb && rgb.a !== 0 ) {
772
+ if( colorBrightness( computedBackgroundColor ) < 128 ) {
773
+ slide.classList.add( 'has-dark-background' );
774
+ }
775
+ else {
776
+ slide.classList.add( 'has-light-background' );
777
+ }
778
+ }
779
+ }
780
+
781
+ return element;
782
+
783
+ }
784
+
785
+ /**
786
+ * Registers a listener to postMessage events, this makes it
787
+ * possible to call all reveal.js API methods from another
788
+ * window. For example:
789
+ *
790
+ * revealWindow.postMessage( JSON.stringify({
791
+ * method: 'slide',
792
+ * args: [ 2 ]
793
+ * }), '*' );
794
+ */
795
+ function setupPostMessage() {
796
+
797
+ if( config.postMessage ) {
798
+ window.addEventListener( 'message', function ( event ) {
799
+ var data = event.data;
800
+
801
+ // Make sure we're dealing with JSON
802
+ if( typeof data === 'string' && data.charAt( 0 ) === '{' && data.charAt( data.length - 1 ) === '}' ) {
803
+ data = JSON.parse( data );
804
+
805
+ // Check if the requested method can be found
806
+ if( data.method && typeof Reveal[data.method] === 'function' ) {
807
+ Reveal[data.method].apply( Reveal, data.args );
808
+ }
809
+ }
810
+ }, false );
811
+ }
812
+
813
+ }
814
+
529
815
  /**
530
816
  * Applies the configuration settings from the config
531
817
  * object. May be called multiple times.
532
818
  */
533
819
  function configure( options ) {
534
820
 
535
- var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
821
+ var numberOfSlides = dom.wrapper.querySelectorAll( SLIDES_SELECTOR ).length;
536
822
 
537
823
  dom.wrapper.classList.remove( config.transition );
538
824
 
@@ -565,6 +851,11 @@ var Reveal = (function(){
565
851
  dom.wrapper.classList.remove( 'center' );
566
852
  }
567
853
 
854
+ // Exit the paused mode if it was configured off
855
+ if( config.pause === false ) {
856
+ resume();
857
+ }
858
+
568
859
  if( config.mouseWheel ) {
569
860
  document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
570
861
  document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
@@ -591,7 +882,13 @@ var Reveal = (function(){
591
882
  enablePreviewLinks( '[data-preview-link]' );
592
883
  }
593
884
 
594
- // Auto-slide playback controls
885
+ // Remove existing auto-slide controls
886
+ if( autoSlidePlayer ) {
887
+ autoSlidePlayer.destroy();
888
+ autoSlidePlayer = null;
889
+ }
890
+
891
+ // Generate auto-slide controls if needed
595
892
  if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
893
  autoSlidePlayer = new Playback( dom.wrapper, function() {
597
894
  return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
@@ -600,21 +897,13 @@ var Reveal = (function(){
600
897
  autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
898
  autoSlidePaused = false;
602
899
  }
603
- else if( autoSlidePlayer ) {
604
- autoSlidePlayer.destroy();
605
- autoSlidePlayer = null;
606
- }
607
900
 
608
- // Load the theme in the config, if it's not already loaded
609
- if( config.theme && dom.theme ) {
610
- var themeURL = dom.theme.getAttribute( 'href' );
611
- var themeFinder = /[^\/]*?(?=\.css)/;
612
- var themeName = themeURL.match(themeFinder)[0];
613
-
614
- if( config.theme !== themeName ) {
615
- themeURL = themeURL.replace(themeFinder, config.theme);
616
- dom.theme.setAttribute( 'href', themeURL );
617
- }
901
+ // When fragments are turned off they should be visible
902
+ if( config.fragments === false ) {
903
+ toArray( dom.slides.querySelectorAll( '.fragment' ) ).forEach( function( element ) {
904
+ element.classList.add( 'visible' );
905
+ element.classList.remove( 'current-fragment' );
906
+ } );
618
907
  }
619
908
 
620
909
  sync();
@@ -637,7 +926,14 @@ var Reveal = (function(){
637
926
  dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
638
927
 
639
928
  // Support pointer-style touch interaction as well
640
- if( window.navigator.msPointerEnabled ) {
929
+ if( window.navigator.pointerEnabled ) {
930
+ // IE 11 uses un-prefixed version of pointer events
931
+ dom.wrapper.addEventListener( 'pointerdown', onPointerDown, false );
932
+ dom.wrapper.addEventListener( 'pointermove', onPointerMove, false );
933
+ dom.wrapper.addEventListener( 'pointerup', onPointerUp, false );
934
+ }
935
+ else if( window.navigator.msPointerEnabled ) {
936
+ // IE 10 uses prefixed version of pointer events
641
937
  dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
642
938
  dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
643
939
  dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
@@ -646,13 +942,14 @@ var Reveal = (function(){
646
942
 
647
943
  if( config.keyboard ) {
648
944
  document.addEventListener( 'keydown', onDocumentKeyDown, false );
945
+ document.addEventListener( 'keypress', onDocumentKeyPress, false );
649
946
  }
650
947
 
651
948
  if( config.progress && dom.progress ) {
652
949
  dom.progress.addEventListener( 'click', onProgressClicked, false );
653
950
  }
654
951
 
655
- if( config.focusBodyOnPageVisiblityChange ) {
952
+ if( config.focusBodyOnPageVisibilityChange ) {
656
953
  var visibilityChange;
657
954
 
658
955
  if( 'hidden' in document ) {
@@ -670,7 +967,17 @@ var Reveal = (function(){
670
967
  }
671
968
  }
672
969
 
673
- [ 'touchstart', 'click' ].forEach( function( eventName ) {
970
+ // Listen to both touch and click events, in case the device
971
+ // supports both
972
+ var pointerEvents = [ 'touchstart', 'click' ];
973
+
974
+ // Only support touch for Android, fixes double navigations in
975
+ // stock browser
976
+ if( navigator.userAgent.match( /android/gi ) ) {
977
+ pointerEvents = [ 'touchstart' ];
978
+ }
979
+
980
+ pointerEvents.forEach( function( eventName ) {
674
981
  dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
982
  dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
983
  dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
@@ -689,6 +996,7 @@ var Reveal = (function(){
689
996
  eventsAreBound = false;
690
997
 
691
998
  document.removeEventListener( 'keydown', onDocumentKeyDown, false );
999
+ document.removeEventListener( 'keypress', onDocumentKeyPress, false );
692
1000
  window.removeEventListener( 'hashchange', onWindowHashChange, false );
693
1001
  window.removeEventListener( 'resize', onWindowResize, false );
694
1002
 
@@ -696,7 +1004,14 @@ var Reveal = (function(){
696
1004
  dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
697
1005
  dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
698
1006
 
699
- if( window.navigator.msPointerEnabled ) {
1007
+ // IE11
1008
+ if( window.navigator.pointerEnabled ) {
1009
+ dom.wrapper.removeEventListener( 'pointerdown', onPointerDown, false );
1010
+ dom.wrapper.removeEventListener( 'pointermove', onPointerMove, false );
1011
+ dom.wrapper.removeEventListener( 'pointerup', onPointerUp, false );
1012
+ }
1013
+ // IE10
1014
+ else if( window.navigator.msPointerEnabled ) {
700
1015
  dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
701
1016
  dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
702
1017
  dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
@@ -738,6 +1053,22 @@ var Reveal = (function(){
738
1053
 
739
1054
  }
740
1055
 
1056
+ /**
1057
+ * Utility for deserializing a value.
1058
+ */
1059
+ function deserialize( value ) {
1060
+
1061
+ if( typeof value === 'string' ) {
1062
+ if( value === 'null' ) return null;
1063
+ else if( value === 'true' ) return true;
1064
+ else if( value === 'false' ) return false;
1065
+ else if( value.match( /^\d+$/ ) ) return parseFloat( value );
1066
+ }
1067
+
1068
+ return value;
1069
+
1070
+ }
1071
+
741
1072
  /**
742
1073
  * Measures the distance in pixels between point a
743
1074
  * and point b.
@@ -762,11 +1093,119 @@ var Reveal = (function(){
762
1093
  element.style.WebkitTransform = transform;
763
1094
  element.style.MozTransform = transform;
764
1095
  element.style.msTransform = transform;
765
- element.style.OTransform = transform;
766
1096
  element.style.transform = transform;
767
1097
 
768
1098
  }
769
1099
 
1100
+ /**
1101
+ * Applies CSS transforms to the slides container. The container
1102
+ * is transformed from two separate sources: layout and the overview
1103
+ * mode.
1104
+ */
1105
+ function transformSlides( transforms ) {
1106
+
1107
+ // Pick up new transforms from arguments
1108
+ if( typeof transforms.layout === 'string' ) slidesTransform.layout = transforms.layout;
1109
+ if( typeof transforms.overview === 'string' ) slidesTransform.overview = transforms.overview;
1110
+
1111
+ // Apply the transforms to the slides container
1112
+ if( slidesTransform.layout ) {
1113
+ transformElement( dom.slides, slidesTransform.layout + ' ' + slidesTransform.overview );
1114
+ }
1115
+ else {
1116
+ transformElement( dom.slides, slidesTransform.overview );
1117
+ }
1118
+
1119
+ }
1120
+
1121
+ /**
1122
+ * Injects the given CSS styles into the DOM.
1123
+ */
1124
+ function injectStyleSheet( value ) {
1125
+
1126
+ var tag = document.createElement( 'style' );
1127
+ tag.type = 'text/css';
1128
+ if( tag.styleSheet ) {
1129
+ tag.styleSheet.cssText = value;
1130
+ }
1131
+ else {
1132
+ tag.appendChild( document.createTextNode( value ) );
1133
+ }
1134
+ document.getElementsByTagName( 'head' )[0].appendChild( tag );
1135
+
1136
+ }
1137
+
1138
+ /**
1139
+ * Converts various color input formats to an {r:0,g:0,b:0} object.
1140
+ *
1141
+ * @param {String} color The string representation of a color,
1142
+ * the following formats are supported:
1143
+ * - #000
1144
+ * - #000000
1145
+ * - rgb(0,0,0)
1146
+ */
1147
+ function colorToRgb( color ) {
1148
+
1149
+ var hex3 = color.match( /^#([0-9a-f]{3})$/i );
1150
+ if( hex3 && hex3[1] ) {
1151
+ hex3 = hex3[1];
1152
+ return {
1153
+ r: parseInt( hex3.charAt( 0 ), 16 ) * 0x11,
1154
+ g: parseInt( hex3.charAt( 1 ), 16 ) * 0x11,
1155
+ b: parseInt( hex3.charAt( 2 ), 16 ) * 0x11
1156
+ };
1157
+ }
1158
+
1159
+ var hex6 = color.match( /^#([0-9a-f]{6})$/i );
1160
+ if( hex6 && hex6[1] ) {
1161
+ hex6 = hex6[1];
1162
+ return {
1163
+ r: parseInt( hex6.substr( 0, 2 ), 16 ),
1164
+ g: parseInt( hex6.substr( 2, 2 ), 16 ),
1165
+ b: parseInt( hex6.substr( 4, 2 ), 16 )
1166
+ };
1167
+ }
1168
+
1169
+ var rgb = color.match( /^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i );
1170
+ if( rgb ) {
1171
+ return {
1172
+ r: parseInt( rgb[1], 10 ),
1173
+ g: parseInt( rgb[2], 10 ),
1174
+ b: parseInt( rgb[3], 10 )
1175
+ };
1176
+ }
1177
+
1178
+ var rgba = color.match( /^rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\,\s*([\d]+|[\d]*.[\d]+)\s*\)$/i );
1179
+ if( rgba ) {
1180
+ return {
1181
+ r: parseInt( rgba[1], 10 ),
1182
+ g: parseInt( rgba[2], 10 ),
1183
+ b: parseInt( rgba[3], 10 ),
1184
+ a: parseFloat( rgba[4] )
1185
+ };
1186
+ }
1187
+
1188
+ return null;
1189
+
1190
+ }
1191
+
1192
+ /**
1193
+ * Calculates brightness on a scale of 0-255.
1194
+ *
1195
+ * @param color See colorStringToRgb for supported formats.
1196
+ */
1197
+ function colorBrightness( color ) {
1198
+
1199
+ if( typeof color === 'string' ) color = colorToRgb( color );
1200
+
1201
+ if( color ) {
1202
+ return ( color.r * 299 + color.g * 587 + color.b * 114 ) / 1000;
1203
+ }
1204
+
1205
+ return null;
1206
+
1207
+ }
1208
+
770
1209
  /**
771
1210
  * Retrieves the height of the given element by looking
772
1211
  * at the position and height of its immediate children.
@@ -782,7 +1221,7 @@ var Reveal = (function(){
782
1221
 
783
1222
  if( typeof child.offsetTop === 'number' && child.style ) {
784
1223
  // Count # of abs children
785
- if( child.style.position === 'absolute' ) {
1224
+ if( window.getComputedStyle( child ).position === 'absolute' ) {
786
1225
  absoluteChildren += 1;
787
1226
  }
788
1227
 
@@ -804,40 +1243,26 @@ var Reveal = (function(){
804
1243
 
805
1244
  /**
806
1245
  * Returns the remaining height within the parent of the
807
- * target element after subtracting the height of all
808
- * siblings.
1246
+ * target element.
809
1247
  *
810
- * remaining height = [parent height] - [ siblings height]
1248
+ * remaining height = [ configured parent height ] - [ current parent height ]
811
1249
  */
812
1250
  function getRemainingHeight( element, height ) {
813
1251
 
814
1252
  height = height || 0;
815
1253
 
816
1254
  if( element ) {
817
- var parent = element.parentNode;
818
- var siblings = parent.childNodes;
819
-
820
- // Subtract the height of each sibling
821
- toArray( siblings ).forEach( function( sibling ) {
822
-
823
- if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
1255
+ var newHeight, oldHeight = element.style.height;
824
1256
 
825
- var styles = window.getComputedStyle( sibling ),
826
- marginTop = parseInt( styles.marginTop, 10 ),
827
- marginBottom = parseInt( styles.marginBottom, 10 );
1257
+ // Change the .stretch element height to 0 in order find the height of all
1258
+ // the other elements
1259
+ element.style.height = '0px';
1260
+ newHeight = height - element.parentNode.offsetHeight;
828
1261
 
829
- height -= sibling.offsetHeight + marginTop + marginBottom;
830
-
831
- }
832
-
833
- } );
834
-
835
- var elementStyles = window.getComputedStyle( element );
836
-
837
- // Subtract the margins of the target element
838
- height -= parseInt( elementStyles.marginTop, 10 ) +
839
- parseInt( elementStyles.marginBottom, 10 );
1262
+ // Restore the old height, just in case
1263
+ element.style.height = oldHeight + 'px';
840
1264
 
1265
+ return newHeight;
841
1266
  }
842
1267
 
843
1268
  return height;
@@ -882,13 +1307,19 @@ var Reveal = (function(){
882
1307
  * Dispatches an event of the specified type from the
883
1308
  * reveal DOM element.
884
1309
  */
885
- function dispatchEvent( type, properties ) {
1310
+ function dispatchEvent( type, args ) {
886
1311
 
887
- var event = document.createEvent( "HTMLEvents", 1, 2 );
1312
+ var event = document.createEvent( 'HTMLEvents', 1, 2 );
888
1313
  event.initEvent( type, true, true );
889
- extend( event, properties );
1314
+ extend( event, args );
890
1315
  dom.wrapper.dispatchEvent( event );
891
1316
 
1317
+ // If we're in an iframe, post each reveal.js event to the
1318
+ // parent window. Used by the notes plugin
1319
+ if( config.postMessageEvents && window.parent !== window.self ) {
1320
+ window.parent.postMessage( JSON.stringify({ namespace: 'reveal', eventName: type, state: getState() }), '*' );
1321
+ }
1322
+
892
1323
  }
893
1324
 
894
1325
  /**
@@ -897,7 +1328,7 @@ var Reveal = (function(){
897
1328
  function enableRollingLinks() {
898
1329
 
899
1330
  if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
900
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
1331
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a' );
901
1332
 
902
1333
  for( var i = 0, len = anchors.length; i < len; i++ ) {
903
1334
  var anchor = anchors[i];
@@ -921,7 +1352,7 @@ var Reveal = (function(){
921
1352
  */
922
1353
  function disableRollingLinks() {
923
1354
 
924
- var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
1355
+ var anchors = dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
925
1356
 
926
1357
  for( var i = 0, len = anchors.length; i < len; i++ ) {
927
1358
  var anchor = anchors[i];
@@ -968,15 +1399,16 @@ var Reveal = (function(){
968
1399
  /**
969
1400
  * Opens a preview window for the target URL.
970
1401
  */
971
- function openPreview( url ) {
1402
+ function showPreview( url ) {
972
1403
 
973
- closePreview();
1404
+ closeOverlay();
974
1405
 
975
- dom.preview = document.createElement( 'div' );
976
- dom.preview.classList.add( 'preview-link-overlay' );
977
- dom.wrapper.appendChild( dom.preview );
1406
+ dom.overlay = document.createElement( 'div' );
1407
+ dom.overlay.classList.add( 'overlay' );
1408
+ dom.overlay.classList.add( 'overlay-preview' );
1409
+ dom.wrapper.appendChild( dom.overlay );
978
1410
 
979
- dom.preview.innerHTML = [
1411
+ dom.overlay.innerHTML = [
980
1412
  '<header>',
981
1413
  '<a class="close" href="#"><span class="icon"></span></a>',
982
1414
  '<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
@@ -987,34 +1419,78 @@ var Reveal = (function(){
987
1419
  '</div>'
988
1420
  ].join('');
989
1421
 
990
- dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
991
- dom.preview.classList.add( 'loaded' );
1422
+ dom.overlay.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
1423
+ dom.overlay.classList.add( 'loaded' );
992
1424
  }, false );
993
1425
 
994
- dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
995
- closePreview();
1426
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1427
+ closeOverlay();
996
1428
  event.preventDefault();
997
1429
  }, false );
998
1430
 
999
- dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1000
- closePreview();
1431
+ dom.overlay.querySelector( '.external' ).addEventListener( 'click', function( event ) {
1432
+ closeOverlay();
1001
1433
  }, false );
1002
1434
 
1003
1435
  setTimeout( function() {
1004
- dom.preview.classList.add( 'visible' );
1436
+ dom.overlay.classList.add( 'visible' );
1005
1437
  }, 1 );
1006
1438
 
1007
1439
  }
1008
1440
 
1009
1441
  /**
1010
- * Closes the iframe preview window.
1442
+ * Opens a overlay window with help material.
1011
1443
  */
1012
- function closePreview() {
1444
+ function showHelp() {
1445
+
1446
+ if( config.help ) {
1447
+
1448
+ closeOverlay();
1449
+
1450
+ dom.overlay = document.createElement( 'div' );
1451
+ dom.overlay.classList.add( 'overlay' );
1452
+ dom.overlay.classList.add( 'overlay-help' );
1453
+ dom.wrapper.appendChild( dom.overlay );
1454
+
1455
+ var html = '<p class="title">Keyboard Shortcuts</p><br/>';
1456
+
1457
+ html += '<table><th>KEY</th><th>ACTION</th>';
1458
+ for( var key in keyboardShortcuts ) {
1459
+ html += '<tr><td>' + key + '</td><td>' + keyboardShortcuts[ key ] + '</td></tr>';
1460
+ }
1461
+
1462
+ html += '</table>';
1463
+
1464
+ dom.overlay.innerHTML = [
1465
+ '<header>',
1466
+ '<a class="close" href="#"><span class="icon"></span></a>',
1467
+ '</header>',
1468
+ '<div class="viewport">',
1469
+ '<div class="viewport-inner">'+ html +'</div>',
1470
+ '</div>'
1471
+ ].join('');
1472
+
1473
+ dom.overlay.querySelector( '.close' ).addEventListener( 'click', function( event ) {
1474
+ closeOverlay();
1475
+ event.preventDefault();
1476
+ }, false );
1477
+
1478
+ setTimeout( function() {
1479
+ dom.overlay.classList.add( 'visible' );
1480
+ }, 1 );
1013
1481
 
1014
- if( dom.preview ) {
1015
- dom.preview.setAttribute( 'src', '' );
1016
- dom.preview.parentNode.removeChild( dom.preview );
1017
- dom.preview = null;
1482
+ }
1483
+
1484
+ }
1485
+
1486
+ /**
1487
+ * Closes any currently open overlay.
1488
+ */
1489
+ function closeOverlay() {
1490
+
1491
+ if( dom.overlay ) {
1492
+ dom.overlay.parentNode.removeChild( dom.overlay );
1493
+ dom.overlay = null;
1018
1494
  }
1019
1495
 
1020
1496
  }
@@ -1027,54 +1503,50 @@ var Reveal = (function(){
1027
1503
 
1028
1504
  if( dom.wrapper && !isPrintingPDF() ) {
1029
1505
 
1030
- // Available space to scale within
1031
- var availableWidth = dom.wrapper.offsetWidth,
1032
- availableHeight = dom.wrapper.offsetHeight;
1506
+ var size = getComputedSlideSize();
1033
1507
 
1034
- // Reduce available space by margin
1035
- availableWidth -= ( availableHeight * config.margin );
1036
- availableHeight -= ( availableHeight * config.margin );
1037
-
1038
- // Dimensions of the content
1039
- var slideWidth = config.width,
1040
- slideHeight = config.height,
1041
- slidePadding = 20; // TODO Dig this out of DOM
1508
+ var slidePadding = 20; // TODO Dig this out of DOM
1042
1509
 
1043
1510
  // Layout the contents of the slides
1044
1511
  layoutSlideContents( config.width, config.height, slidePadding );
1045
1512
 
1046
- // Slide width may be a percentage of available width
1047
- if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
1048
- slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
1049
- }
1050
-
1051
- // Slide height may be a percentage of available height
1052
- if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
1053
- slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
1054
- }
1055
-
1056
- dom.slides.style.width = slideWidth + 'px';
1057
- dom.slides.style.height = slideHeight + 'px';
1513
+ dom.slides.style.width = size.width + 'px';
1514
+ dom.slides.style.height = size.height + 'px';
1058
1515
 
1059
1516
  // Determine scale of content to fit within available space
1060
- scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1517
+ scale = Math.min( size.presentationWidth / size.width, size.presentationHeight / size.height );
1061
1518
 
1062
1519
  // Respect max/min scale settings
1063
1520
  scale = Math.max( scale, config.minScale );
1064
1521
  scale = Math.min( scale, config.maxScale );
1065
1522
 
1066
- // Prefer applying scale via zoom since Chrome blurs scaled content
1067
- // with nested transforms
1068
- if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
1069
- dom.slides.style.zoom = scale;
1523
+ // Don't apply any scaling styles if scale is 1
1524
+ if( scale === 1 ) {
1525
+ dom.slides.style.zoom = '';
1526
+ dom.slides.style.left = '';
1527
+ dom.slides.style.top = '';
1528
+ dom.slides.style.bottom = '';
1529
+ dom.slides.style.right = '';
1530
+ transformSlides( { layout: '' } );
1070
1531
  }
1071
- // Apply scale transform as a fallback
1072
1532
  else {
1073
- transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1533
+ // Prefer zooming in desktop Chrome so that content remains crisp
1534
+ if( !isMobileDevice && /chrome/i.test( navigator.userAgent ) && typeof dom.slides.style.zoom !== 'undefined' ) {
1535
+ dom.slides.style.zoom = scale;
1536
+ transformSlides( { layout: '' } );
1537
+ }
1538
+ // Apply scale transform as a fallback
1539
+ else {
1540
+ dom.slides.style.left = '50%';
1541
+ dom.slides.style.top = '50%';
1542
+ dom.slides.style.bottom = 'auto';
1543
+ dom.slides.style.right = 'auto';
1544
+ transformSlides( { layout: 'translate(-50%, -50%) scale('+ scale +')' } );
1545
+ }
1074
1546
  }
1075
1547
 
1076
1548
  // Select all slides, vertical and horizontal
1077
- var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1549
+ var slides = toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) );
1078
1550
 
1079
1551
  for( var i = 0, len = slides.length; i < len; i++ ) {
1080
1552
  var slide = slides[ i ];
@@ -1091,7 +1563,7 @@ var Reveal = (function(){
1091
1563
  slide.style.top = 0;
1092
1564
  }
1093
1565
  else {
1094
- slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1566
+ slide.style.top = Math.max( ( ( size.height - getAbsoluteHeight( slide ) ) / 2 ) - slidePadding, 0 ) + 'px';
1095
1567
  }
1096
1568
  }
1097
1569
  else {
@@ -1117,7 +1589,7 @@ var Reveal = (function(){
1117
1589
  toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
1590
 
1119
1591
  // Determine how much vertical space we can use
1120
- var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1592
+ var remainingHeight = getRemainingHeight( element, height );
1121
1593
 
1122
1594
  // Consider the aspect ratio of media elements
1123
1595
  if( /(img|video)/gi.test( element.nodeName ) ) {
@@ -1139,6 +1611,41 @@ var Reveal = (function(){
1139
1611
 
1140
1612
  }
1141
1613
 
1614
+ /**
1615
+ * Calculates the computed pixel size of our slides. These
1616
+ * values are based on the width and height configuration
1617
+ * options.
1618
+ */
1619
+ function getComputedSlideSize( presentationWidth, presentationHeight ) {
1620
+
1621
+ var size = {
1622
+ // Slide size
1623
+ width: config.width,
1624
+ height: config.height,
1625
+
1626
+ // Presentation size
1627
+ presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
1628
+ presentationHeight: presentationHeight || dom.wrapper.offsetHeight
1629
+ };
1630
+
1631
+ // Reduce available space by margin
1632
+ size.presentationWidth -= ( size.presentationWidth * config.margin );
1633
+ size.presentationHeight -= ( size.presentationHeight * config.margin );
1634
+
1635
+ // Slide width may be a percentage of available width
1636
+ if( typeof size.width === 'string' && /%$/.test( size.width ) ) {
1637
+ size.width = parseInt( size.width, 10 ) / 100 * size.presentationWidth;
1638
+ }
1639
+
1640
+ // Slide height may be a percentage of available height
1641
+ if( typeof size.height === 'string' && /%$/.test( size.height ) ) {
1642
+ size.height = parseInt( size.height, 10 ) / 100 * size.presentationHeight;
1643
+ }
1644
+
1645
+ return size;
1646
+
1647
+ }
1648
+
1142
1649
  /**
1143
1650
  * Stores the vertical index of a stack so that the same
1144
1651
  * vertical slide can be selected when navigating to and
@@ -1176,92 +1683,122 @@ var Reveal = (function(){
1176
1683
  }
1177
1684
 
1178
1685
  /**
1179
- * Displays the overview of slides (quick nav) by
1180
- * scaling down and arranging all slide elements.
1181
- *
1182
- * Experimental feature, might be dropped if perf
1183
- * can't be improved.
1686
+ * Displays the overview of slides (quick nav) by scaling
1687
+ * down and arranging all slide elements.
1184
1688
  */
1185
1689
  function activateOverview() {
1186
1690
 
1187
1691
  // Only proceed if enabled in config
1188
- if( config.overview ) {
1692
+ if( config.overview && !isOverview() ) {
1189
1693
 
1190
- // Don't auto-slide while in overview mode
1191
- cancelAutoSlide();
1192
-
1193
- var wasActive = dom.wrapper.classList.contains( 'overview' );
1194
-
1195
- // Vary the depth of the overview based on screen size
1196
- var depth = window.innerWidth < 400 ? 1000 : 2500;
1694
+ overview = true;
1197
1695
 
1198
1696
  dom.wrapper.classList.add( 'overview' );
1199
1697
  dom.wrapper.classList.remove( 'overview-deactivating' );
1200
1698
 
1201
- clearTimeout( activateOverviewTimeout );
1202
- clearTimeout( deactivateOverviewTimeout );
1699
+ if( features.overviewTransitions ) {
1700
+ setTimeout( function() {
1701
+ dom.wrapper.classList.add( 'overview-animated' );
1702
+ }, 1 );
1703
+ }
1203
1704
 
1204
- // Not the pretties solution, but need to let the overview
1205
- // class apply first so that slides are measured accurately
1206
- // before we can position them
1207
- activateOverviewTimeout = setTimeout( function() {
1705
+ // Don't auto-slide while in overview mode
1706
+ cancelAutoSlide();
1208
1707
 
1209
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1708
+ // Move the backgrounds element into the slide container to
1709
+ // that the same scaling is applied
1710
+ dom.slides.appendChild( dom.background );
1210
1711
 
1211
- for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1212
- var hslide = horizontalSlides[i],
1213
- hoffset = config.rtl ? -105 : 105;
1712
+ // Clicking on an overview slide navigates to it
1713
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1714
+ if( !slide.classList.contains( 'stack' ) ) {
1715
+ slide.addEventListener( 'click', onOverviewSlideClicked, true );
1716
+ }
1717
+ } );
1214
1718
 
1215
- hslide.setAttribute( 'data-index-h', i );
1719
+ updateSlidesVisibility();
1720
+ layoutOverview();
1721
+ updateOverview();
1216
1722
 
1217
- // Apply CSS transform
1218
- transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1723
+ layout();
1219
1724
 
1220
- if( hslide.classList.contains( 'stack' ) ) {
1725
+ // Notify observers of the overview showing
1726
+ dispatchEvent( 'overviewshown', {
1727
+ 'indexh': indexh,
1728
+ 'indexv': indexv,
1729
+ 'currentSlide': currentSlide
1730
+ } );
1221
1731
 
1222
- var verticalSlides = hslide.querySelectorAll( 'section' );
1732
+ }
1223
1733
 
1224
- for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1225
- var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1734
+ }
1226
1735
 
1227
- var vslide = verticalSlides[j];
1736
+ /**
1737
+ * Uses CSS transforms to position all slides in a grid for
1738
+ * display inside of the overview mode.
1739
+ */
1740
+ function layoutOverview() {
1228
1741
 
1229
- vslide.setAttribute( 'data-index-h', i );
1230
- vslide.setAttribute( 'data-index-v', j );
1742
+ var margin = 70;
1743
+ var slideWidth = config.width + margin,
1744
+ slideHeight = config.height + margin;
1231
1745
 
1232
- // Apply CSS transform
1233
- transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1746
+ // Reverse in RTL mode
1747
+ if( config.rtl ) {
1748
+ slideWidth = -slideWidth;
1749
+ }
1234
1750
 
1235
- // Navigate to this slide on click
1236
- vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1237
- }
1751
+ // Layout slides
1752
+ toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( hslide, h ) {
1753
+ hslide.setAttribute( 'data-index-h', h );
1754
+ transformElement( hslide, 'translate3d(' + ( h * slideWidth ) + 'px, 0, 0)' );
1238
1755
 
1239
- }
1240
- else {
1756
+ if( hslide.classList.contains( 'stack' ) ) {
1241
1757
 
1242
- // Navigate to this slide on click
1243
- hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1758
+ toArray( hslide.querySelectorAll( 'section' ) ).forEach( function( vslide, v ) {
1759
+ vslide.setAttribute( 'data-index-h', h );
1760
+ vslide.setAttribute( 'data-index-v', v );
1244
1761
 
1245
- }
1246
- }
1762
+ transformElement( vslide, 'translate3d(0, ' + ( v * slideHeight ) + 'px, 0)' );
1763
+ } );
1764
+
1765
+ }
1766
+ } );
1247
1767
 
1248
- updateSlidesVisibility();
1768
+ // Layout slide backgrounds
1769
+ toArray( dom.background.childNodes ).forEach( function( hbackground, h ) {
1770
+ transformElement( hbackground, 'translate3d(' + ( h * slideWidth ) + 'px, 0, 0)' );
1249
1771
 
1250
- layout();
1772
+ toArray( hbackground.querySelectorAll( '.slide-background' ) ).forEach( function( vbackground, v ) {
1773
+ transformElement( vbackground, 'translate3d(0, ' + ( v * slideHeight ) + 'px, 0)' );
1774
+ } );
1775
+ } );
1251
1776
 
1252
- if( !wasActive ) {
1253
- // Notify observers of the overview showing
1254
- dispatchEvent( 'overviewshown', {
1255
- 'indexh': indexh,
1256
- 'indexv': indexv,
1257
- 'currentSlide': currentSlide
1258
- } );
1259
- }
1777
+ }
1260
1778
 
1261
- }, 10 );
1779
+ /**
1780
+ * Moves the overview viewport to the current slides.
1781
+ * Called each time the current slide changes.
1782
+ */
1783
+ function updateOverview() {
1262
1784
 
1785
+ var margin = 70;
1786
+ var slideWidth = config.width + margin,
1787
+ slideHeight = config.height + margin;
1788
+
1789
+ // Reverse in RTL mode
1790
+ if( config.rtl ) {
1791
+ slideWidth = -slideWidth;
1263
1792
  }
1264
1793
 
1794
+ transformSlides( {
1795
+ overview: [
1796
+ 'translateX('+ ( -indexh * slideWidth ) +'px)',
1797
+ 'translateY('+ ( -indexv * slideHeight ) +'px)',
1798
+ 'translateZ('+ ( window.innerWidth < 400 ? -1000 : -2500 ) +'px)'
1799
+ ].join( ' ' )
1800
+ } );
1801
+
1265
1802
  }
1266
1803
 
1267
1804
  /**
@@ -1273,30 +1810,41 @@ var Reveal = (function(){
1273
1810
  // Only proceed if enabled in config
1274
1811
  if( config.overview ) {
1275
1812
 
1276
- clearTimeout( activateOverviewTimeout );
1277
- clearTimeout( deactivateOverviewTimeout );
1813
+ overview = false;
1278
1814
 
1279
1815
  dom.wrapper.classList.remove( 'overview' );
1816
+ dom.wrapper.classList.remove( 'overview-animated' );
1280
1817
 
1281
1818
  // Temporarily add a class so that transitions can do different things
1282
1819
  // depending on whether they are exiting/entering overview, or just
1283
1820
  // moving from slide to slide
1284
1821
  dom.wrapper.classList.add( 'overview-deactivating' );
1285
1822
 
1286
- deactivateOverviewTimeout = setTimeout( function () {
1823
+ setTimeout( function () {
1287
1824
  dom.wrapper.classList.remove( 'overview-deactivating' );
1288
1825
  }, 1 );
1289
1826
 
1290
- // Select all slides
1291
- toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1292
- // Resets all transforms to use the external styles
1827
+ // Move the background element back out
1828
+ dom.wrapper.appendChild( dom.background );
1829
+
1830
+ // Clean up changes made to slides
1831
+ toArray( dom.wrapper.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1293
1832
  transformElement( slide, '' );
1294
1833
 
1295
1834
  slide.removeEventListener( 'click', onOverviewSlideClicked, true );
1296
1835
  } );
1297
1836
 
1837
+ // Clean up changes made to backgrounds
1838
+ toArray( dom.background.querySelectorAll( '.slide-background' ) ).forEach( function( background ) {
1839
+ transformElement( background, '' );
1840
+ } );
1841
+
1842
+ transformSlides( { overview: '' } );
1843
+
1298
1844
  slide( indexh, indexv );
1299
1845
 
1846
+ layout();
1847
+
1300
1848
  cueAutoSlide();
1301
1849
 
1302
1850
  // Notify observers of the overview hiding
@@ -1335,7 +1883,7 @@ var Reveal = (function(){
1335
1883
  */
1336
1884
  function isOverview() {
1337
1885
 
1338
- return dom.wrapper.classList.contains( 'overview' );
1886
+ return overview;
1339
1887
 
1340
1888
  }
1341
1889
 
@@ -1370,7 +1918,7 @@ var Reveal = (function(){
1370
1918
  element.webkitRequestFullscreen ||
1371
1919
  element.webkitRequestFullScreen ||
1372
1920
  element.mozRequestFullScreen ||
1373
- element.msRequestFullScreen;
1921
+ element.msRequestFullscreen;
1374
1922
 
1375
1923
  if( requestMethod ) {
1376
1924
  requestMethod.apply( element );
@@ -1384,13 +1932,15 @@ var Reveal = (function(){
1384
1932
  */
1385
1933
  function pause() {
1386
1934
 
1387
- var wasPaused = dom.wrapper.classList.contains( 'paused' );
1935
+ if( config.pause ) {
1936
+ var wasPaused = dom.wrapper.classList.contains( 'paused' );
1388
1937
 
1389
- cancelAutoSlide();
1390
- dom.wrapper.classList.add( 'paused' );
1938
+ cancelAutoSlide();
1939
+ dom.wrapper.classList.add( 'paused' );
1391
1940
 
1392
- if( wasPaused === false ) {
1393
- dispatchEvent( 'paused' );
1941
+ if( wasPaused === false ) {
1942
+ dispatchEvent( 'paused' );
1943
+ }
1394
1944
  }
1395
1945
 
1396
1946
  }
@@ -1414,13 +1964,13 @@ var Reveal = (function(){
1414
1964
  /**
1415
1965
  * Toggles the paused mode on and off.
1416
1966
  */
1417
- function togglePause() {
1967
+ function togglePause( override ) {
1418
1968
 
1419
- if( isPaused() ) {
1420
- resume();
1969
+ if( typeof override === 'boolean' ) {
1970
+ override ? pause() : resume();
1421
1971
  }
1422
1972
  else {
1423
- pause();
1973
+ isPaused() ? resume() : pause();
1424
1974
  }
1425
1975
 
1426
1976
  }
@@ -1434,6 +1984,34 @@ var Reveal = (function(){
1434
1984
 
1435
1985
  }
1436
1986
 
1987
+ /**
1988
+ * Toggles the auto slide mode on and off.
1989
+ *
1990
+ * @param {Boolean} override Optional flag which sets the desired state.
1991
+ * True means autoplay starts, false means it stops.
1992
+ */
1993
+
1994
+ function toggleAutoSlide( override ) {
1995
+
1996
+ if( typeof override === 'boolean' ) {
1997
+ override ? resumeAutoSlide() : pauseAutoSlide();
1998
+ }
1999
+
2000
+ else {
2001
+ autoSlidePaused ? resumeAutoSlide() : pauseAutoSlide();
2002
+ }
2003
+
2004
+ }
2005
+
2006
+ /**
2007
+ * Checks if the auto slide mode is currently on.
2008
+ */
2009
+ function isAutoSliding() {
2010
+
2011
+ return !!( autoSlide && !autoSlidePaused );
2012
+
2013
+ }
2014
+
1437
2015
  /**
1438
2016
  * Steps from the current point in the presentation to the
1439
2017
  * slide which matches the specified horizontal and vertical
@@ -1451,11 +2029,11 @@ var Reveal = (function(){
1451
2029
  previousSlide = currentSlide;
1452
2030
 
1453
2031
  // Query all horizontal slides in the deck
1454
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
2032
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1455
2033
 
1456
2034
  // If no vertical index is specified and the upcoming slide is a
1457
2035
  // stack, resume at its previous vertical index
1458
- if( v === undefined ) {
2036
+ if( v === undefined && !isOverview() ) {
1459
2037
  v = getPreviousVerticalIndex( horizontalSlides[ h ] );
1460
2038
  }
1461
2039
 
@@ -1505,9 +2083,9 @@ var Reveal = (function(){
1505
2083
  document.documentElement.classList.remove( stateBefore.pop() );
1506
2084
  }
1507
2085
 
1508
- // If the overview is active, re-activate it to update positions
2086
+ // Update the overview if it's currently active
1509
2087
  if( isOverview() ) {
1510
- activateOverview();
2088
+ updateOverview();
1511
2089
  }
1512
2090
 
1513
2091
  // Find the current horizontal slide and any possible vertical slides
@@ -1544,13 +2122,14 @@ var Reveal = (function(){
1544
2122
  // stacks
1545
2123
  if( previousSlide ) {
1546
2124
  previousSlide.classList.remove( 'present' );
2125
+ previousSlide.setAttribute( 'aria-hidden', 'true' );
1547
2126
 
1548
2127
  // Reset all slides upon navigate to home
1549
2128
  // Issue: #285
1550
- if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
2129
+ if ( dom.wrapper.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1551
2130
  // Launch async task
1552
2131
  setTimeout( function () {
1553
- var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
2132
+ var slides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1554
2133
  for( i in slides ) {
1555
2134
  if( slides[i] ) {
1556
2135
  // Reset stack
@@ -1562,11 +2141,14 @@ var Reveal = (function(){
1562
2141
  }
1563
2142
 
1564
2143
  // Handle embedded content
1565
- if( slideChanged ) {
2144
+ if( slideChanged || !previousSlide ) {
1566
2145
  stopEmbeddedContent( previousSlide );
1567
2146
  startEmbeddedContent( currentSlide );
1568
2147
  }
1569
2148
 
2149
+ // Announce the current slide contents, for screen readers
2150
+ dom.statusDiv.textContent = currentSlide.textContent;
2151
+
1570
2152
  updateControls();
1571
2153
  updateProgress();
1572
2154
  updateBackground();
@@ -1603,12 +2185,23 @@ var Reveal = (function(){
1603
2185
  // Re-create the slide backgrounds
1604
2186
  createBackgrounds();
1605
2187
 
2188
+ // Write the current hash to the URL
2189
+ writeURL();
2190
+
1606
2191
  sortAllFragments();
1607
2192
 
1608
2193
  updateControls();
1609
2194
  updateProgress();
1610
2195
  updateBackground( true );
1611
2196
  updateSlideNumber();
2197
+ updateSlidesVisibility();
2198
+
2199
+ formatEmbeddedContent();
2200
+ startEmbeddedContent( currentSlide );
2201
+
2202
+ if( isOverview() ) {
2203
+ layoutOverview();
2204
+ }
1612
2205
 
1613
2206
  }
1614
2207
 
@@ -1618,7 +2211,7 @@ var Reveal = (function(){
1618
2211
  */
1619
2212
  function resetVerticalSlides() {
1620
2213
 
1621
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2214
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
2215
  horizontalSlides.forEach( function( horizontalSlide ) {
1623
2216
 
1624
2217
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1628,6 +2221,7 @@ var Reveal = (function(){
1628
2221
  verticalSlide.classList.remove( 'present' );
1629
2222
  verticalSlide.classList.remove( 'past' );
1630
2223
  verticalSlide.classList.add( 'future' );
2224
+ verticalSlide.setAttribute( 'aria-hidden', 'true' );
1631
2225
  }
1632
2226
 
1633
2227
  } );
@@ -1642,7 +2236,7 @@ var Reveal = (function(){
1642
2236
  */
1643
2237
  function sortAllFragments() {
1644
2238
 
1645
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2239
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
2240
  horizontalSlides.forEach( function( horizontalSlide ) {
1647
2241
 
1648
2242
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
@@ -1675,9 +2269,11 @@ var Reveal = (function(){
1675
2269
 
1676
2270
  // Select all slides and convert the NodeList result to
1677
2271
  // an array
1678
- var slides = toArray( document.querySelectorAll( selector ) ),
2272
+ var slides = toArray( dom.wrapper.querySelectorAll( selector ) ),
1679
2273
  slidesLength = slides.length;
1680
2274
 
2275
+ var printMode = isPrintingPDF();
2276
+
1681
2277
  if( slidesLength ) {
1682
2278
 
1683
2279
  // Should the index loop?
@@ -1703,43 +2299,55 @@ var Reveal = (function(){
1703
2299
 
1704
2300
  // http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1705
2301
  element.setAttribute( 'hidden', '' );
2302
+ element.setAttribute( 'aria-hidden', 'true' );
2303
+
2304
+ // If this element contains vertical slides
2305
+ if( element.querySelector( 'section' ) ) {
2306
+ element.classList.add( 'stack' );
2307
+ }
2308
+
2309
+ // If we're printing static slides, all slides are "present"
2310
+ if( printMode ) {
2311
+ element.classList.add( 'present' );
2312
+ continue;
2313
+ }
1706
2314
 
1707
2315
  if( i < index ) {
1708
2316
  // Any element previous to index is given the 'past' class
1709
2317
  element.classList.add( reverse ? 'future' : 'past' );
1710
2318
 
1711
- var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
2319
+ if( config.fragments ) {
2320
+ var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
2321
 
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' );
2322
+ // Show all fragments on prior slides
2323
+ while( pastFragments.length ) {
2324
+ var pastFragment = pastFragments.pop();
2325
+ pastFragment.classList.add( 'visible' );
2326
+ pastFragment.classList.remove( 'current-fragment' );
2327
+ }
1718
2328
  }
1719
2329
  }
1720
2330
  else if( i > index ) {
1721
2331
  // Any element subsequent to index is given the 'future' class
1722
2332
  element.classList.add( reverse ? 'past' : 'future' );
1723
2333
 
1724
- var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
2334
+ if( config.fragments ) {
2335
+ var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
2336
 
1726
- // No fragments in future slides should be visible ahead of time
1727
- while( futureFragments.length ) {
1728
- var futureFragment = futureFragments.pop();
1729
- futureFragment.classList.remove( 'visible' );
1730
- futureFragment.classList.remove( 'current-fragment' );
2337
+ // No fragments in future slides should be visible ahead of time
2338
+ while( futureFragments.length ) {
2339
+ var futureFragment = futureFragments.pop();
2340
+ futureFragment.classList.remove( 'visible' );
2341
+ futureFragment.classList.remove( 'current-fragment' );
2342
+ }
1731
2343
  }
1732
2344
  }
1733
-
1734
- // If this element contains vertical slides
1735
- if( element.querySelector( 'section' ) ) {
1736
- element.classList.add( 'stack' );
1737
- }
1738
2345
  }
1739
2346
 
1740
2347
  // Mark the current slide as present
1741
2348
  slides[index].classList.add( 'present' );
1742
2349
  slides[index].removeAttribute( 'hidden' );
2350
+ slides[index].removeAttribute( 'aria-hidden' );
1743
2351
 
1744
2352
  // If this slide has a state associated with it, add it
1745
2353
  // onto the current state of the deck
@@ -1767,12 +2375,12 @@ var Reveal = (function(){
1767
2375
 
1768
2376
  // Select all slides and convert the NodeList result to
1769
2377
  // an array
1770
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
2378
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
2379
  horizontalSlidesLength = horizontalSlides.length,
1772
2380
  distanceX,
1773
2381
  distanceY;
1774
2382
 
1775
- if( horizontalSlidesLength ) {
2383
+ if( horizontalSlidesLength && typeof indexh !== 'undefined' ) {
1776
2384
 
1777
2385
  // The number of steps away from the present slide that will
1778
2386
  // be visible
@@ -1780,7 +2388,12 @@ var Reveal = (function(){
1780
2388
 
1781
2389
  // Limit view distance on weaker devices
1782
2390
  if( isMobileDevice ) {
1783
- viewDistance = isOverview() ? 6 : 1;
2391
+ viewDistance = isOverview() ? 6 : 2;
2392
+ }
2393
+
2394
+ // All slides need to be visible when exporting to PDF
2395
+ if( isPrintingPDF() ) {
2396
+ viewDistance = Number.MAX_VALUE;
1784
2397
  }
1785
2398
 
1786
2399
  for( var x = 0; x < horizontalSlidesLength; x++ ) {
@@ -1789,11 +2402,22 @@ var Reveal = (function(){
1789
2402
  var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
1790
2403
  verticalSlidesLength = verticalSlides.length;
1791
2404
 
1792
- // Loops so that it measures 1 between the first and last slides
1793
- distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2405
+ // Determine how far away this slide is from the present
2406
+ distanceX = Math.abs( ( indexh || 0 ) - x ) || 0;
2407
+
2408
+ // If the presentation is looped, distance should measure
2409
+ // 1 between the first and last slides
2410
+ if( config.loop ) {
2411
+ distanceX = Math.abs( ( ( indexh || 0 ) - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
2412
+ }
1794
2413
 
1795
2414
  // Show the horizontal slide if it's within the view distance
1796
- horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
2415
+ if( distanceX < viewDistance ) {
2416
+ showSlide( horizontalSlide );
2417
+ }
2418
+ else {
2419
+ hideSlide( horizontalSlide );
2420
+ }
1797
2421
 
1798
2422
  if( verticalSlidesLength ) {
1799
2423
 
@@ -1802,9 +2426,14 @@ var Reveal = (function(){
1802
2426
  for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
2427
  var verticalSlide = verticalSlides[y];
1804
2428
 
1805
- distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
2429
+ distanceY = x === ( indexh || 0 ) ? Math.abs( ( indexv || 0 ) - y ) : Math.abs( y - oy );
1806
2430
 
1807
- verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
2431
+ if( distanceX + distanceY < viewDistance ) {
2432
+ showSlide( verticalSlide );
2433
+ }
2434
+ else {
2435
+ hideSlide( verticalSlide );
2436
+ }
1808
2437
  }
1809
2438
 
1810
2439
  }
@@ -1820,44 +2449,9 @@ var Reveal = (function(){
1820
2449
  function updateProgress() {
1821
2450
 
1822
2451
  // Update progress if enabled
1823
- if( config.progress && dom.progress ) {
1824
-
1825
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1826
-
1827
- // The number of past and total slides
1828
- var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
1829
- var pastCount = 0;
2452
+ if( config.progress && dom.progressbar ) {
1830
2453
 
1831
- // Step through all slides and count the past ones
1832
- mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
1833
-
1834
- var horizontalSlide = horizontalSlides[i];
1835
- var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1836
-
1837
- for( var j = 0; j < verticalSlides.length; j++ ) {
1838
-
1839
- // Stop as soon as we arrive at the present
1840
- if( verticalSlides[j].classList.contains( 'present' ) ) {
1841
- break mainLoop;
1842
- }
1843
-
1844
- pastCount++;
1845
-
1846
- }
1847
-
1848
- // Stop as soon as we arrive at the present
1849
- if( horizontalSlide.classList.contains( 'present' ) ) {
1850
- break;
1851
- }
1852
-
1853
- // Don't count the wrapping section for vertical slides
1854
- if( horizontalSlide.classList.contains( 'stack' ) === false ) {
1855
- pastCount++;
1856
- }
1857
-
1858
- }
1859
-
1860
- dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
2454
+ dom.progressbar.style.width = getProgress() * dom.wrapper.offsetWidth + 'px';
1861
2455
 
1862
2456
  }
1863
2457
 
@@ -1865,19 +2459,31 @@ var Reveal = (function(){
1865
2459
 
1866
2460
  /**
1867
2461
  * Updates the slide number div to reflect the current slide.
2462
+ *
2463
+ * Slide number format can be defined as a string using the
2464
+ * following variables:
2465
+ * h: current slide's horizontal index
2466
+ * v: current slide's vertical index
2467
+ * c: current slide index (flattened)
2468
+ * t: total number of slides (flattened)
1868
2469
  */
1869
2470
  function updateSlideNumber() {
1870
2471
 
1871
2472
  // Update slide number if enabled
1872
2473
  if( config.slideNumber && dom.slideNumber) {
1873
2474
 
1874
- // Display the number of the page using 'indexh - indexv' format
1875
- var indexString = indexh;
1876
- if( indexv > 0 ) {
1877
- indexString += ' - ' + indexv;
2475
+ // Default to only showing the current slide number
2476
+ var format = 'c';
2477
+
2478
+ // Check if a custom slide number format is available
2479
+ if( typeof config.slideNumber === 'string' ) {
2480
+ format = config.slideNumber;
1878
2481
  }
1879
2482
 
1880
- dom.slideNumber.innerHTML = indexString;
2483
+ dom.slideNumber.innerHTML = format.replace( /h/g, indexh )
2484
+ .replace( /v/g, indexv )
2485
+ .replace( /c/g, getSlidePastCount() + 1 )
2486
+ .replace( /t/g, getTotalSlides() );
1881
2487
  }
1882
2488
 
1883
2489
  }
@@ -1951,43 +2557,76 @@ var Reveal = (function(){
1951
2557
  // states of their slides (past/present/future)
1952
2558
  toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1953
2559
 
2560
+ backgroundh.classList.remove( 'past' );
2561
+ backgroundh.classList.remove( 'present' );
2562
+ backgroundh.classList.remove( 'future' );
2563
+
1954
2564
  if( h < indexh ) {
1955
- backgroundh.className = 'slide-background ' + horizontalPast;
2565
+ backgroundh.classList.add( horizontalPast );
1956
2566
  }
1957
2567
  else if ( h > indexh ) {
1958
- backgroundh.className = 'slide-background ' + horizontalFuture;
2568
+ backgroundh.classList.add( horizontalFuture );
1959
2569
  }
1960
2570
  else {
1961
- backgroundh.className = 'slide-background present';
2571
+ backgroundh.classList.add( 'present' );
1962
2572
 
1963
2573
  // Store a reference to the current background element
1964
2574
  currentBackground = backgroundh;
1965
2575
  }
1966
2576
 
1967
2577
  if( includeAll || h === indexh ) {
1968
- toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
2578
+ toArray( backgroundh.querySelectorAll( '.slide-background' ) ).forEach( function( backgroundv, v ) {
2579
+
2580
+ backgroundv.classList.remove( 'past' );
2581
+ backgroundv.classList.remove( 'present' );
2582
+ backgroundv.classList.remove( 'future' );
1969
2583
 
1970
2584
  if( v < indexv ) {
1971
- backgroundv.className = 'slide-background past';
2585
+ backgroundv.classList.add( 'past' );
1972
2586
  }
1973
2587
  else if ( v > indexv ) {
1974
- backgroundv.className = 'slide-background future';
2588
+ backgroundv.classList.add( 'future' );
1975
2589
  }
1976
2590
  else {
1977
- backgroundv.className = 'slide-background present';
2591
+ backgroundv.classList.add( 'present' );
1978
2592
 
1979
2593
  // Only if this is the present horizontal and vertical slide
1980
2594
  if( h === indexh ) currentBackground = backgroundv;
1981
2595
  }
1982
2596
 
1983
- } );
2597
+ } );
2598
+ }
2599
+
2600
+ } );
2601
+
2602
+ // Stop any currently playing video background
2603
+ if( previousBackground ) {
2604
+
2605
+ var previousVideo = previousBackground.querySelector( 'video' );
2606
+ if( previousVideo ) previousVideo.pause();
2607
+
2608
+ }
2609
+
2610
+ if( currentBackground ) {
2611
+
2612
+ // Start video playback
2613
+ var currentVideo = currentBackground.querySelector( 'video' );
2614
+ if( currentVideo ) {
2615
+ currentVideo.currentTime = 0;
2616
+ currentVideo.play();
1984
2617
  }
1985
2618
 
1986
- } );
2619
+ var backgroundImageURL = currentBackground.style.backgroundImage || '';
1987
2620
 
1988
- // Don't transition between identical backgrounds. This
1989
- // prevents unwanted flicker.
1990
- if( currentBackground ) {
2621
+ // Restart GIFs (doesn't work in Firefox)
2622
+ if( /\.gif/i.test( backgroundImageURL ) ) {
2623
+ currentBackground.style.backgroundImage = '';
2624
+ window.getComputedStyle( currentBackground ).opacity;
2625
+ currentBackground.style.backgroundImage = backgroundImageURL;
2626
+ }
2627
+
2628
+ // Don't transition between identical backgrounds. This
2629
+ // prevents unwanted flicker.
1991
2630
  var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
2631
  var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
2632
  if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
@@ -1995,6 +2634,20 @@ var Reveal = (function(){
1995
2634
  }
1996
2635
 
1997
2636
  previousBackground = currentBackground;
2637
+
2638
+ }
2639
+
2640
+ // If there's a background brightness flag for this slide,
2641
+ // bubble it to the .reveal container
2642
+ if( currentSlide ) {
2643
+ [ 'has-light-background', 'has-dark-background' ].forEach( function( classToBubble ) {
2644
+ if( currentSlide.classList.contains( classToBubble ) ) {
2645
+ dom.wrapper.classList.add( classToBubble );
2646
+ }
2647
+ else {
2648
+ dom.wrapper.classList.remove( classToBubble );
2649
+ }
2650
+ } );
1998
2651
  }
1999
2652
 
2000
2653
  // Allow the first background to apply without transition
@@ -2012,8 +2665,8 @@ var Reveal = (function(){
2012
2665
 
2013
2666
  if( config.parallaxBackgroundImage ) {
2014
2667
 
2015
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2668
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2669
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
2670
 
2018
2671
  var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
2672
  backgroundWidth, backgroundHeight;
@@ -2026,16 +2679,138 @@ var Reveal = (function(){
2026
2679
  backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
2680
  }
2028
2681
 
2029
- var slideWidth = dom.background.offsetWidth;
2030
- var horizontalSlideCount = horizontalSlides.length;
2031
- var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2682
+ var slideWidth = dom.background.offsetWidth,
2683
+ horizontalSlideCount = horizontalSlides.length,
2684
+ horizontalOffsetMultiplier,
2685
+ horizontalOffset;
2686
+
2687
+ if( typeof config.parallaxBackgroundHorizontal === 'number' ) {
2688
+ horizontalOffsetMultiplier = config.parallaxBackgroundHorizontal;
2689
+ }
2690
+ else {
2691
+ horizontalOffsetMultiplier = ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 );
2692
+ }
2693
+
2694
+ horizontalOffset = horizontalOffsetMultiplier * indexh * -1;
2695
+
2696
+ var slideHeight = dom.background.offsetHeight,
2697
+ verticalSlideCount = verticalSlides.length,
2698
+ verticalOffsetMultiplier,
2699
+ verticalOffset;
2700
+
2701
+ if( typeof config.parallaxBackgroundVertical === 'number' ) {
2702
+ verticalOffsetMultiplier = config.parallaxBackgroundVertical;
2703
+ }
2704
+ else {
2705
+ verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 );
2706
+ }
2707
+
2708
+ verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indexv * 1 : 0;
2709
+
2710
+ dom.background.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px';
2711
+
2712
+ }
2713
+
2714
+ }
2715
+
2716
+ /**
2717
+ * Called when the given slide is within the configured view
2718
+ * distance. Shows the slide element and loads any content
2719
+ * that is set to load lazily (data-src).
2720
+ */
2721
+ function showSlide( slide ) {
2722
+
2723
+ // Show the slide element
2724
+ slide.style.display = 'block';
2725
+
2726
+ // Media elements with data-src attributes
2727
+ toArray( slide.querySelectorAll( 'img[data-src], video[data-src], audio[data-src]' ) ).forEach( function( element ) {
2728
+ element.setAttribute( 'src', element.getAttribute( 'data-src' ) );
2729
+ element.removeAttribute( 'data-src' );
2730
+ } );
2731
+
2732
+ // Media elements with <source> children
2733
+ toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( media ) {
2734
+ var sources = 0;
2735
+
2736
+ toArray( media.querySelectorAll( 'source[data-src]' ) ).forEach( function( source ) {
2737
+ source.setAttribute( 'src', source.getAttribute( 'data-src' ) );
2738
+ source.removeAttribute( 'data-src' );
2739
+ sources += 1;
2740
+ } );
2741
+
2742
+ // If we rewrote sources for this video/audio element, we need
2743
+ // to manually tell it to load from its new origin
2744
+ if( sources > 0 ) {
2745
+ media.load();
2746
+ }
2747
+ } );
2748
+
2749
+
2750
+ // Show the corresponding background element
2751
+ var indices = getIndices( slide );
2752
+ var background = getSlideBackground( indices.h, indices.v );
2753
+ if( background ) {
2754
+ background.style.display = 'block';
2755
+
2756
+ // If the background contains media, load it
2757
+ if( background.hasAttribute( 'data-loaded' ) === false ) {
2758
+ background.setAttribute( 'data-loaded', 'true' );
2759
+
2760
+ var backgroundImage = slide.getAttribute( 'data-background-image' ),
2761
+ backgroundVideo = slide.getAttribute( 'data-background-video' ),
2762
+ backgroundVideoLoop = slide.hasAttribute( 'data-background-video-loop' ),
2763
+ backgroundIframe = slide.getAttribute( 'data-background-iframe' );
2764
+
2765
+ // Images
2766
+ if( backgroundImage ) {
2767
+ background.style.backgroundImage = 'url('+ backgroundImage +')';
2768
+ }
2769
+ // Videos
2770
+ else if ( backgroundVideo && !isSpeakerNotes() ) {
2771
+ var video = document.createElement( 'video' );
2772
+
2773
+ if( backgroundVideoLoop ) {
2774
+ video.setAttribute( 'loop', '' );
2775
+ }
2776
+
2777
+ // Support comma separated lists of video sources
2778
+ backgroundVideo.split( ',' ).forEach( function( source ) {
2779
+ video.innerHTML += '<source src="'+ source +'">';
2780
+ } );
2781
+
2782
+ background.appendChild( video );
2783
+ }
2784
+ // Iframes
2785
+ else if( backgroundIframe ) {
2786
+ var iframe = document.createElement( 'iframe' );
2787
+ iframe.setAttribute( 'src', backgroundIframe );
2788
+ iframe.style.width = '100%';
2789
+ iframe.style.height = '100%';
2790
+ iframe.style.maxHeight = '100%';
2791
+ iframe.style.maxWidth = '100%';
2792
+
2793
+ background.appendChild( iframe );
2794
+ }
2795
+ }
2796
+ }
2797
+
2798
+ }
2032
2799
 
2033
- var slideHeight = dom.background.offsetHeight;
2034
- var verticalSlideCount = verticalSlides.length;
2035
- var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2800
+ /**
2801
+ * Called when the given slide is moved outside of the
2802
+ * configured view distance.
2803
+ */
2804
+ function hideSlide( slide ) {
2036
2805
 
2037
- dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2806
+ // Hide the slide element
2807
+ slide.style.display = 'none';
2038
2808
 
2809
+ // Hide the corresponding background element
2810
+ var indices = getIndices( slide );
2811
+ var background = getSlideBackground( indices.h, indices.v );
2812
+ if( background ) {
2813
+ background.style.display = 'none';
2039
2814
  }
2040
2815
 
2041
2816
  }
@@ -2047,8 +2822,8 @@ var Reveal = (function(){
2047
2822
  */
2048
2823
  function availableRoutes() {
2049
2824
 
2050
- var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2051
- verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2825
+ var horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2826
+ verticalSlides = dom.wrapper.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2052
2827
 
2053
2828
  var routes = {
2054
2829
  left: indexh > 0 || config.loop,
@@ -2091,6 +2866,30 @@ var Reveal = (function(){
2091
2866
 
2092
2867
  }
2093
2868
 
2869
+ /**
2870
+ * Enforces origin-specific format rules for embedded media.
2871
+ */
2872
+ function formatEmbeddedContent() {
2873
+
2874
+ var _appendParamToIframeSource = function( sourceAttribute, sourceURL, param ) {
2875
+ toArray( dom.slides.querySelectorAll( 'iframe['+ sourceAttribute +'*="'+ sourceURL +'"]' ) ).forEach( function( el ) {
2876
+ var src = el.getAttribute( sourceAttribute );
2877
+ if( src && src.indexOf( param ) === -1 ) {
2878
+ el.setAttribute( sourceAttribute, src + ( !/\?/.test( src ) ? '?' : '&' ) + param );
2879
+ }
2880
+ });
2881
+ };
2882
+
2883
+ // YouTube frames must include "?enablejsapi=1"
2884
+ _appendParamToIframeSource( 'src', 'youtube.com/embed/', 'enablejsapi=1' );
2885
+ _appendParamToIframeSource( 'data-src', 'youtube.com/embed/', 'enablejsapi=1' );
2886
+
2887
+ // Vimeo frames must include "?api=1"
2888
+ _appendParamToIframeSource( 'src', 'player.vimeo.com/', 'api=1' );
2889
+ _appendParamToIframeSource( 'data-src', 'player.vimeo.com/', 'api=1' );
2890
+
2891
+ }
2892
+
2094
2893
  /**
2095
2894
  * Start playback of any embedded content inside of
2096
2895
  * the targeted slide.
@@ -2098,24 +2897,56 @@ var Reveal = (function(){
2098
2897
  function startEmbeddedContent( slide ) {
2099
2898
 
2100
2899
  if( slide && !isSpeakerNotes() ) {
2900
+ // Restart GIFs
2901
+ toArray( slide.querySelectorAll( 'img[src$=".gif"]' ) ).forEach( function( el ) {
2902
+ // Setting the same unchanged source like this was confirmed
2903
+ // to work in Chrome, FF & Safari
2904
+ el.setAttribute( 'src', el.getAttribute( 'src' ) );
2905
+ } );
2906
+
2101
2907
  // HTML5 media elements
2102
2908
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2103
- if( el.hasAttribute( 'data-autoplay' ) ) {
2909
+ if( el.hasAttribute( 'data-autoplay' ) && typeof el.play === 'function' ) {
2104
2910
  el.play();
2105
2911
  }
2106
2912
  } );
2107
2913
 
2108
- // iframe embeds
2109
- toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
- el.contentWindow.postMessage( 'slide:start', '*' );
2111
- });
2914
+ // Normal iframes
2915
+ toArray( slide.querySelectorAll( 'iframe[src]' ) ).forEach( function( el ) {
2916
+ startEmbeddedIframe( { target: el } );
2917
+ } );
2112
2918
 
2113
- // YouTube embeds
2114
- toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2115
- if( el.hasAttribute( 'data-autoplay' ) ) {
2116
- el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2919
+ // Lazy loading iframes
2920
+ toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
2921
+ if( el.getAttribute( 'src' ) !== el.getAttribute( 'data-src' ) ) {
2922
+ el.removeEventListener( 'load', startEmbeddedIframe ); // remove first to avoid dupes
2923
+ el.addEventListener( 'load', startEmbeddedIframe );
2924
+ el.setAttribute( 'src', el.getAttribute( 'data-src' ) );
2117
2925
  }
2118
- });
2926
+ } );
2927
+ }
2928
+
2929
+ }
2930
+
2931
+ /**
2932
+ * "Starts" the content of an embedded iframe using the
2933
+ * postmessage API.
2934
+ */
2935
+ function startEmbeddedIframe( event ) {
2936
+
2937
+ var iframe = event.target;
2938
+
2939
+ // YouTube postMessage API
2940
+ if( /youtube\.com\/embed\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
2941
+ iframe.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2942
+ }
2943
+ // Vimeo postMessage API
2944
+ else if( /player\.vimeo\.com\//.test( iframe.getAttribute( 'src' ) ) && iframe.hasAttribute( 'data-autoplay' ) ) {
2945
+ iframe.contentWindow.postMessage( '{"method":"play"}', '*' );
2946
+ }
2947
+ // Generic postMessage API
2948
+ else {
2949
+ iframe.contentWindow.postMessage( 'slide:start', '*' );
2119
2950
  }
2120
2951
 
2121
2952
  }
@@ -2126,27 +2957,120 @@ var Reveal = (function(){
2126
2957
  */
2127
2958
  function stopEmbeddedContent( slide ) {
2128
2959
 
2129
- if( slide ) {
2960
+ if( slide && slide.parentNode ) {
2130
2961
  // HTML5 media elements
2131
2962
  toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2132
- if( !el.hasAttribute( 'data-ignore' ) ) {
2963
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.pause === 'function' ) {
2133
2964
  el.pause();
2134
2965
  }
2135
2966
  } );
2136
2967
 
2137
- // iframe embeds
2968
+ // Generic postMessage API for non-lazy loaded iframes
2138
2969
  toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
2970
  el.contentWindow.postMessage( 'slide:stop', '*' );
2971
+ el.removeEventListener( 'load', startEmbeddedIframe );
2140
2972
  });
2141
2973
 
2142
- // YouTube embeds
2974
+ // YouTube postMessage API
2143
2975
  toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2144
2976
  if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2145
2977
  el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
2146
2978
  }
2147
2979
  });
2980
+
2981
+ // Vimeo postMessage API
2982
+ toArray( slide.querySelectorAll( 'iframe[src*="player.vimeo.com/"]' ) ).forEach( function( el ) {
2983
+ if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2984
+ el.contentWindow.postMessage( '{"method":"pause"}', '*' );
2985
+ }
2986
+ });
2987
+
2988
+ // Lazy loading iframes
2989
+ toArray( slide.querySelectorAll( 'iframe[data-src]' ) ).forEach( function( el ) {
2990
+ // Only removing the src doesn't actually unload the frame
2991
+ // in all browsers (Firefox) so we set it to blank first
2992
+ el.setAttribute( 'src', 'about:blank' );
2993
+ el.removeAttribute( 'src' );
2994
+ } );
2995
+ }
2996
+
2997
+ }
2998
+
2999
+ /**
3000
+ * Returns the number of past slides. This can be used as a global
3001
+ * flattened index for slides.
3002
+ */
3003
+ function getSlidePastCount() {
3004
+
3005
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3006
+
3007
+ // The number of past slides
3008
+ var pastCount = 0;
3009
+
3010
+ // Step through all slides and count the past ones
3011
+ mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
3012
+
3013
+ var horizontalSlide = horizontalSlides[i];
3014
+ var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
3015
+
3016
+ for( var j = 0; j < verticalSlides.length; j++ ) {
3017
+
3018
+ // Stop as soon as we arrive at the present
3019
+ if( verticalSlides[j].classList.contains( 'present' ) ) {
3020
+ break mainLoop;
3021
+ }
3022
+
3023
+ pastCount++;
3024
+
3025
+ }
3026
+
3027
+ // Stop as soon as we arrive at the present
3028
+ if( horizontalSlide.classList.contains( 'present' ) ) {
3029
+ break;
3030
+ }
3031
+
3032
+ // Don't count the wrapping section for vertical slides
3033
+ if( horizontalSlide.classList.contains( 'stack' ) === false ) {
3034
+ pastCount++;
3035
+ }
3036
+
3037
+ }
3038
+
3039
+ return pastCount;
3040
+
3041
+ }
3042
+
3043
+ /**
3044
+ * Returns a value ranging from 0-1 that represents
3045
+ * how far into the presentation we have navigated.
3046
+ */
3047
+ function getProgress() {
3048
+
3049
+ // The number of past and total slides
3050
+ var totalCount = getTotalSlides();
3051
+ var pastCount = getSlidePastCount();
3052
+
3053
+ if( currentSlide ) {
3054
+
3055
+ var allFragments = currentSlide.querySelectorAll( '.fragment' );
3056
+
3057
+ // If there are fragments in the current slide those should be
3058
+ // accounted for in the progress.
3059
+ if( allFragments.length > 0 ) {
3060
+ var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
3061
+
3062
+ // This value represents how big a portion of the slide progress
3063
+ // that is made up by its fragments (0-1)
3064
+ var fragmentWeight = 0.9;
3065
+
3066
+ // Add fragment progress to the past slide count
3067
+ pastCount += ( visibleFragments.length / allFragments.length ) * fragmentWeight;
3068
+ }
3069
+
2148
3070
  }
2149
3071
 
3072
+ return pastCount / ( totalCount - 1 );
3073
+
2150
3074
  }
2151
3075
 
2152
3076
  /**
@@ -2173,8 +3097,13 @@ var Reveal = (function(){
2173
3097
  // If the first bit is invalid and there is a name we can
2174
3098
  // assume that this is a named link
2175
3099
  if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
2176
- // Find the slide with the specified name
2177
- var element = document.querySelector( '#' + name );
3100
+ var element;
3101
+
3102
+ // Ensure the named link is a valid HTML ID attribute
3103
+ if( /^[a-zA-Z][\w:.-]*$/.test( name ) ) {
3104
+ // Find the slide with the specified ID
3105
+ element = document.getElementById( name );
3106
+ }
2178
3107
 
2179
3108
  if( element ) {
2180
3109
  // Find the position of the named slide and navigate to it
@@ -2216,12 +3145,19 @@ var Reveal = (function(){
2216
3145
  if( typeof delay === 'number' ) {
2217
3146
  writeURLTimeout = setTimeout( writeURL, delay );
2218
3147
  }
2219
- else {
3148
+ else if( currentSlide ) {
2220
3149
  var url = '/';
2221
3150
 
3151
+ // Attempt to create a named link based on the slide's ID
3152
+ var id = currentSlide.getAttribute( 'id' );
3153
+ if( id ) {
3154
+ id = id.toLowerCase();
3155
+ id = id.replace( /[^a-zA-Z0-9\-\_\:\.]/g, '' );
3156
+ }
3157
+
2222
3158
  // If the current slide has an ID, use that as a named link
2223
- if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
2224
- url = '/' + currentSlide.getAttribute( 'id' );
3159
+ if( typeof id === 'string' && id.length ) {
3160
+ url = '/' + id;
2225
3161
  }
2226
3162
  // Otherwise use the /h/v index
2227
3163
  else {
@@ -2258,11 +3194,14 @@ var Reveal = (function(){
2258
3194
  var slideh = isVertical ? slide.parentNode : slide;
2259
3195
 
2260
3196
  // Select all horizontal slides
2261
- var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
3197
+ var horizontalSlides = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2262
3198
 
2263
3199
  // Now that we know which the horizontal slide is, get its index
2264
3200
  h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2265
3201
 
3202
+ // Assume we're not vertical
3203
+ v = undefined;
3204
+
2266
3205
  // If this is a vertical slide, grab the vertical index
2267
3206
  if( isVertical ) {
2268
3207
  v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
@@ -2272,8 +3211,13 @@ var Reveal = (function(){
2272
3211
  if( !slide && currentSlide ) {
2273
3212
  var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
3213
  if( hasFragments ) {
2275
- var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
- f = visibleFragments.length - 1;
3214
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3215
+ if( currentFragment && currentFragment.hasAttribute( 'data-fragment-index' ) ) {
3216
+ f = parseInt( currentFragment.getAttribute( 'data-fragment-index' ), 10 );
3217
+ }
3218
+ else {
3219
+ f = currentSlide.querySelectorAll( '.fragment.visible' ).length - 1;
3220
+ }
2277
3221
  }
2278
3222
  }
2279
3223
 
@@ -2281,6 +3225,107 @@ var Reveal = (function(){
2281
3225
 
2282
3226
  }
2283
3227
 
3228
+ /**
3229
+ * Retrieves the total number of slides in this presentation.
3230
+ */
3231
+ function getTotalSlides() {
3232
+
3233
+ return dom.wrapper.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
3234
+
3235
+ }
3236
+
3237
+ /**
3238
+ * Returns the slide element matching the specified index.
3239
+ */
3240
+ function getSlide( x, y ) {
3241
+
3242
+ var horizontalSlide = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3243
+ var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
3244
+
3245
+ if( verticalSlides && verticalSlides.length && typeof y === 'number' ) {
3246
+ return verticalSlides ? verticalSlides[ y ] : undefined;
3247
+ }
3248
+
3249
+ return horizontalSlide;
3250
+
3251
+ }
3252
+
3253
+ /**
3254
+ * Returns the background element for the given slide.
3255
+ * All slides, even the ones with no background properties
3256
+ * defined, have a background element so as long as the
3257
+ * index is valid an element will be returned.
3258
+ */
3259
+ function getSlideBackground( x, y ) {
3260
+
3261
+ // When printing to PDF the slide backgrounds are nested
3262
+ // inside of the slides
3263
+ if( isPrintingPDF() ) {
3264
+ var slide = getSlide( x, y );
3265
+ if( slide ) {
3266
+ var background = slide.querySelector( '.slide-background' );
3267
+ if( background && background.parentNode === slide ) {
3268
+ return background;
3269
+ }
3270
+ }
3271
+
3272
+ return undefined;
3273
+ }
3274
+
3275
+ var horizontalBackground = dom.wrapper.querySelectorAll( '.backgrounds>.slide-background' )[ x ];
3276
+ var verticalBackgrounds = horizontalBackground && horizontalBackground.querySelectorAll( '.slide-background' );
3277
+
3278
+ if( verticalBackgrounds && verticalBackgrounds.length && typeof y === 'number' ) {
3279
+ return verticalBackgrounds ? verticalBackgrounds[ y ] : undefined;
3280
+ }
3281
+
3282
+ return horizontalBackground;
3283
+
3284
+ }
3285
+
3286
+ /**
3287
+ * Retrieves the current state of the presentation as
3288
+ * an object. This state can then be restored at any
3289
+ * time.
3290
+ */
3291
+ function getState() {
3292
+
3293
+ var indices = getIndices();
3294
+
3295
+ return {
3296
+ indexh: indices.h,
3297
+ indexv: indices.v,
3298
+ indexf: indices.f,
3299
+ paused: isPaused(),
3300
+ overview: isOverview()
3301
+ };
3302
+
3303
+ }
3304
+
3305
+ /**
3306
+ * Restores the presentation to the given state.
3307
+ *
3308
+ * @param {Object} state As generated by getState()
3309
+ */
3310
+ function setState( state ) {
3311
+
3312
+ if( typeof state === 'object' ) {
3313
+ slide( deserialize( state.indexh ), deserialize( state.indexv ), deserialize( state.indexf ) );
3314
+
3315
+ var pausedFlag = deserialize( state.paused ),
3316
+ overviewFlag = deserialize( state.overview );
3317
+
3318
+ if( typeof pausedFlag === 'boolean' && pausedFlag !== isPaused() ) {
3319
+ togglePause( pausedFlag );
3320
+ }
3321
+
3322
+ if( typeof overviewFlag === 'boolean' && overviewFlag !== isOverview() ) {
3323
+ toggleOverview( overviewFlag );
3324
+ }
3325
+ }
3326
+
3327
+ }
3328
+
2284
3329
  /**
2285
3330
  * Return a sorted fragments list, ordered by an increasing
2286
3331
  * "data-fragment-index" attribute.
@@ -2392,6 +3437,9 @@ var Reveal = (function(){
2392
3437
  element.classList.add( 'visible' );
2393
3438
  element.classList.remove( 'current-fragment' );
2394
3439
 
3440
+ // Announce the fragments one by one to the Screen Reader
3441
+ dom.statusDiv.textContent = element.textContent;
3442
+
2395
3443
  if( i === index ) {
2396
3444
  element.classList.add( 'current-fragment' );
2397
3445
  }
@@ -2415,6 +3463,7 @@ var Reveal = (function(){
2415
3463
  }
2416
3464
 
2417
3465
  updateControls();
3466
+ updateProgress();
2418
3467
 
2419
3468
  return !!( fragmentsShown.length || fragmentsHidden.length );
2420
3469
 
@@ -2459,14 +3508,21 @@ var Reveal = (function(){
2459
3508
 
2460
3509
  if( currentSlide ) {
2461
3510
 
3511
+ var currentFragment = currentSlide.querySelector( '.current-fragment' );
3512
+
3513
+ var fragmentAutoSlide = currentFragment ? currentFragment.getAttribute( 'data-autoslide' ) : null;
2462
3514
  var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
3515
  var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
3516
 
2465
3517
  // 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 ) {
3518
+ // 1. Current fragment's data-autoslide
3519
+ // 2. Current slide's data-autoslide
3520
+ // 3. Parent slide's data-autoslide
3521
+ // 4. Global autoSlide setting
3522
+ if( fragmentAutoSlide ) {
3523
+ autoSlide = parseInt( fragmentAutoSlide, 10 );
3524
+ }
3525
+ else if( slideAutoSlide ) {
2470
3526
  autoSlide = parseInt( slideAutoSlide, 10 );
2471
3527
  }
2472
3528
  else if( parentAutoSlide ) {
@@ -2478,14 +3534,17 @@ var Reveal = (function(){
2478
3534
 
2479
3535
  // If there are media elements with data-autoplay,
2480
3536
  // 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;
3537
+ // length of that media. Not applicable if the slide
3538
+ // is divided up into fragments.
3539
+ if( currentSlide.querySelectorAll( '.fragment' ).length === 0 ) {
3540
+ toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
3541
+ if( el.hasAttribute( 'data-autoplay' ) ) {
3542
+ if( autoSlide && el.duration * 1000 > autoSlide ) {
3543
+ autoSlide = ( el.duration * 1000 ) + 1000;
3544
+ }
2486
3545
  }
2487
- }
2488
- } );
3546
+ } );
3547
+ }
2489
3548
 
2490
3549
  // Cue the next auto-slide if:
2491
3550
  // - There is an autoSlide value
@@ -2493,7 +3552,7 @@ var Reveal = (function(){
2493
3552
  // - The presentation isn't paused
2494
3553
  // - The overview isn't active
2495
3554
  // - The presentation isn't over
2496
- if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
3555
+ if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || availableFragments().next || config.loop === true ) ) {
2497
3556
  autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
3557
  autoSlideStartTime = Date.now();
2499
3558
  }
@@ -2518,19 +3577,25 @@ var Reveal = (function(){
2518
3577
 
2519
3578
  function pauseAutoSlide() {
2520
3579
 
2521
- autoSlidePaused = true;
2522
- clearTimeout( autoSlideTimeout );
3580
+ if( autoSlide && !autoSlidePaused ) {
3581
+ autoSlidePaused = true;
3582
+ dispatchEvent( 'autoslidepaused' );
3583
+ clearTimeout( autoSlideTimeout );
2523
3584
 
2524
- if( autoSlidePlayer ) {
2525
- autoSlidePlayer.setPlaying( false );
3585
+ if( autoSlidePlayer ) {
3586
+ autoSlidePlayer.setPlaying( false );
3587
+ }
2526
3588
  }
2527
3589
 
2528
3590
  }
2529
3591
 
2530
3592
  function resumeAutoSlide() {
2531
3593
 
2532
- autoSlidePaused = false;
2533
- cueAutoSlide();
3594
+ if( autoSlide && autoSlidePaused ) {
3595
+ autoSlidePaused = false;
3596
+ dispatchEvent( 'autoslideresumed' );
3597
+ cueAutoSlide();
3598
+ }
2534
3599
 
2535
3600
  }
2536
3601
 
@@ -2597,7 +3662,14 @@ var Reveal = (function(){
2597
3662
  }
2598
3663
  else {
2599
3664
  // Fetch the previous horizontal slide, if there is one
2600
- var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
3665
+ var previousSlide;
3666
+
3667
+ if( config.rtl ) {
3668
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.future' ) ).pop();
3669
+ }
3670
+ else {
3671
+ previousSlide = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.past' ) ).pop();
3672
+ }
2601
3673
 
2602
3674
  if( previousSlide ) {
2603
3675
  var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
@@ -2610,13 +3682,21 @@ var Reveal = (function(){
2610
3682
  }
2611
3683
 
2612
3684
  /**
2613
- * Same as #navigatePrev() but navigates forwards.
3685
+ * The reverse of #navigatePrev().
2614
3686
  */
2615
3687
  function navigateNext() {
2616
3688
 
2617
3689
  // Prioritize revealing fragments
2618
3690
  if( nextFragment() === false ) {
2619
- availableRoutes().down ? navigateDown() : navigateRight();
3691
+ if( availableRoutes().down ) {
3692
+ navigateDown();
3693
+ }
3694
+ else if( config.rtl ) {
3695
+ navigateLeft();
3696
+ }
3697
+ else {
3698
+ navigateRight();
3699
+ }
2620
3700
  }
2621
3701
 
2622
3702
  // If auto-sliding is enabled we need to cue up
@@ -2642,21 +3722,47 @@ var Reveal = (function(){
2642
3722
 
2643
3723
  }
2644
3724
 
3725
+ /**
3726
+ * Handler for the document level 'keypress' event.
3727
+ */
3728
+ function onDocumentKeyPress( event ) {
3729
+
3730
+ // Check if the pressed key is question mark
3731
+ if( event.shiftKey && event.charCode === 63 ) {
3732
+ if( dom.overlay ) {
3733
+ closeOverlay();
3734
+ }
3735
+ else {
3736
+ showHelp( true );
3737
+ }
3738
+ }
3739
+
3740
+ }
3741
+
2645
3742
  /**
2646
3743
  * Handler for the document level 'keydown' event.
2647
3744
  */
2648
3745
  function onDocumentKeyDown( event ) {
2649
3746
 
3747
+ // If there's a condition specified and it returns false,
3748
+ // ignore this event
3749
+ if( typeof config.keyboardCondition === 'function' && config.keyboardCondition() === false ) {
3750
+ return true;
3751
+ }
3752
+
3753
+ // Remember if auto-sliding was paused so we can toggle it
3754
+ var autoSlideWasPaused = autoSlidePaused;
3755
+
2650
3756
  onUserInput( event );
2651
3757
 
2652
3758
  // Check if there's a focused element that could be using
2653
3759
  // the keyboard
2654
- var activeElement = document.activeElement;
2655
- var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
3760
+ var activeElementIsCE = document.activeElement && document.activeElement.contentEditable !== 'inherit';
3761
+ var activeElementIsInput = document.activeElement && document.activeElement.tagName && /input|textarea/i.test( document.activeElement.tagName );
2656
3762
 
2657
3763
  // Disregard the event if there's a focused element or a
2658
3764
  // keyboard modifier key is present
2659
- if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
3765
+ if( activeElementIsCE || activeElementIsInput || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
2660
3766
 
2661
3767
  // While paused only allow "unpausing" keyboard events (b and .)
2662
3768
  if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
@@ -2719,10 +3825,12 @@ var Reveal = (function(){
2719
3825
  case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2720
3826
  // return
2721
3827
  case 13: isOverview() ? deactivateOverview() : triggered = false; break;
2722
- // b, period, Logitech presenter tools "black screen" button
2723
- case 66: case 190: case 191: togglePause(); break;
3828
+ // two-spot, semicolon, b, period, Logitech presenter tools "black screen" button
3829
+ case 58: case 59: case 66: case 190: case 191: togglePause(); break;
2724
3830
  // f
2725
3831
  case 70: enterFullscreen(); break;
3832
+ // a
3833
+ case 65: if ( config.autoSlideStoppable ) toggleAutoSlide( autoSlideWasPaused ); break;
2726
3834
  default:
2727
3835
  triggered = false;
2728
3836
  }
@@ -2732,18 +3840,18 @@ var Reveal = (function(){
2732
3840
  // If the input resulted in a triggered action we should prevent
2733
3841
  // the browsers default behavior
2734
3842
  if( triggered ) {
2735
- event.preventDefault();
3843
+ event.preventDefault && event.preventDefault();
2736
3844
  }
2737
3845
  // ESC or O key
2738
3846
  else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
- if( dom.preview ) {
2740
- closePreview();
3847
+ if( dom.overlay ) {
3848
+ closeOverlay();
2741
3849
  }
2742
3850
  else {
2743
3851
  toggleOverview();
2744
3852
  }
2745
3853
 
2746
- event.preventDefault();
3854
+ event.preventDefault && event.preventDefault();
2747
3855
  }
2748
3856
 
2749
3857
  // If auto-sliding is enabled we need to cue up
@@ -2877,7 +3985,7 @@ var Reveal = (function(){
2877
3985
  */
2878
3986
  function onPointerDown( event ) {
2879
3987
 
2880
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
3988
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2881
3989
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2882
3990
  onTouchStart( event );
2883
3991
  }
@@ -2889,7 +3997,7 @@ var Reveal = (function(){
2889
3997
  */
2890
3998
  function onPointerMove( event ) {
2891
3999
 
2892
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
4000
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2893
4001
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2894
4002
  onTouchMove( event );
2895
4003
  }
@@ -2901,7 +4009,7 @@ var Reveal = (function(){
2901
4009
  */
2902
4010
  function onPointerUp( event ) {
2903
4011
 
2904
- if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
4012
+ if( event.pointerType === event.MSPOINTER_TYPE_TOUCH || event.pointerType === "touch" ) {
2905
4013
  event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2906
4014
  onTouchEnd( event );
2907
4015
  }
@@ -2942,9 +4050,13 @@ var Reveal = (function(){
2942
4050
 
2943
4051
  event.preventDefault();
2944
4052
 
2945
- var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
4053
+ var slidesTotal = toArray( dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2946
4054
  var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2947
4055
 
4056
+ if( config.rtl ) {
4057
+ slideIndex = slidesTotal - slideIndex;
4058
+ }
4059
+
2948
4060
  slide( slideIndex );
2949
4061
 
2950
4062
  }
@@ -2989,7 +4101,10 @@ var Reveal = (function(){
2989
4101
  // If, after clicking a link or similar and we're coming back,
2990
4102
  // focus the document.body to ensure we can use keyboard shortcuts
2991
4103
  if( isHidden === false && document.activeElement !== document.body ) {
2992
- document.activeElement.blur();
4104
+ // Not all elements support .blur() - SVGs among them.
4105
+ if( typeof document.activeElement.blur === 'function' ) {
4106
+ document.activeElement.blur();
4107
+ }
2993
4108
  document.body.focus();
2994
4109
  }
2995
4110
 
@@ -3033,10 +4148,12 @@ var Reveal = (function(){
3033
4148
  */
3034
4149
  function onPreviewLinkClicked( event ) {
3035
4150
 
3036
- var url = event.target.getAttribute( 'href' );
3037
- if( url ) {
3038
- openPreview( url );
3039
- event.preventDefault();
4151
+ if( event.currentTarget && event.currentTarget.hasAttribute( 'href' ) ) {
4152
+ var url = event.currentTarget.getAttribute( 'href' );
4153
+ if( url ) {
4154
+ showPreview( url );
4155
+ event.preventDefault();
4156
+ }
3040
4157
  }
3041
4158
 
3042
4159
  }
@@ -3232,7 +4349,7 @@ var Reveal = (function(){
3232
4349
  // --------------------------------------------------------------------//
3233
4350
 
3234
4351
 
3235
- return {
4352
+ Reveal = {
3236
4353
  initialize: initialize,
3237
4354
  configure: configure,
3238
4355
  sync: sync,
@@ -3275,28 +4392,35 @@ var Reveal = (function(){
3275
4392
  // Toggles the "black screen" mode on/off
3276
4393
  togglePause: togglePause,
3277
4394
 
4395
+ // Toggles the auto slide mode on/off
4396
+ toggleAutoSlide: toggleAutoSlide,
4397
+
3278
4398
  // State checks
3279
4399
  isOverview: isOverview,
3280
4400
  isPaused: isPaused,
4401
+ isAutoSliding: isAutoSliding,
3281
4402
 
3282
4403
  // Adds or removes all internal event listeners (such as keyboard)
3283
4404
  addEventListeners: addEventListeners,
3284
4405
  removeEventListeners: removeEventListeners,
3285
4406
 
4407
+ // Facility for persisting and restoring the presentation state
4408
+ getState: getState,
4409
+ setState: setState,
4410
+
4411
+ // Presentation progress on range of 0-1
4412
+ getProgress: getProgress,
4413
+
3286
4414
  // Returns the indices of the current, or specified, slide
3287
4415
  getIndices: getIndices,
3288
4416
 
3289
- // Returns the slide at the specified index, y is optional
3290
- getSlide: function( x, y ) {
3291
- var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3292
- var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
4417
+ getTotalSlides: getTotalSlides,
3293
4418
 
3294
- if( typeof y !== 'undefined' ) {
3295
- return verticalSlides ? verticalSlides[ y ] : undefined;
3296
- }
4419
+ // Returns the slide element at the specified index
4420
+ getSlide: getSlide,
3297
4421
 
3298
- return horizontalSlide;
3299
- },
4422
+ // Returns the slide background element at the specified index
4423
+ getSlideBackground: getSlideBackground,
3300
4424
 
3301
4425
  // Returns the previous slide element, may be null
3302
4426
  getPreviousSlide: function() {
@@ -3330,12 +4454,7 @@ var Reveal = (function(){
3330
4454
  for( var i in query ) {
3331
4455
  var value = query[ i ];
3332
4456
 
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 );
4457
+ query[ i ] = deserialize( unescape( value ) );
3339
4458
  }
3340
4459
 
3341
4460
  return query;
@@ -3343,7 +4462,7 @@ var Reveal = (function(){
3343
4462
 
3344
4463
  // Returns true if we're currently on the first slide
3345
4464
  isFirstSlide: function() {
3346
- return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
4465
+ return ( indexh === 0 && indexv === 0 );
3347
4466
  },
3348
4467
 
3349
4468
  // Returns true if we're currently on the last slide
@@ -3376,7 +4495,14 @@ var Reveal = (function(){
3376
4495
  if( 'addEventListener' in window ) {
3377
4496
  ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
3378
4497
  }
4498
+ },
4499
+
4500
+ // Programatically triggers a keyboard event
4501
+ triggerKey: function( keyCode ) {
4502
+ onDocumentKeyDown( { keyCode: keyCode } );
3379
4503
  }
3380
4504
  };
3381
4505
 
3382
- })();
4506
+ return Reveal;
4507
+
4508
+ }));