slippery 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }));