Kaynağa Gözat

Merge pull request #1 from hakimel/master

update repo
Anas Bouzid 1 yıl önce
ebeveyn
işleme
dcc21516dd
93 değiştirilmiş dosya ile 5204 ekleme ve 818 silme
  1. 20 13
      .github/workflows/js.yml
  2. 0 2
      .npmignore
  3. 1 1
      LICENSE
  4. 1 12
      README.md
  5. 27 39
      css/print/paper.scss
  6. 6 2
      css/print/pdf.scss
  7. 320 40
      css/reveal.scss
  8. 3 0
      css/theme/source/beige.scss
  9. 49 0
      css/theme/source/black-contrast.scss
  10. 1 1
      css/theme/source/black.scss
  11. 106 0
      css/theme/source/dracula.scss
  12. 0 4
      css/theme/source/moon.scss
  13. 3 0
      css/theme/source/serif.scss
  14. 3 0
      css/theme/source/simple.scss
  15. 3 0
      css/theme/source/sky.scss
  16. 3 0
      css/theme/source/solarized.scss
  17. 52 0
      css/theme/source/white-contrast.scss
  18. 3 0
      css/theme/source/white.scss
  19. 2 0
      css/theme/template/exposer.scss
  20. 5 0
      css/theme/template/settings.scss
  21. 16 16
      demo.html
  22. 2 6
      dist/reveal.css
  23. 2 2
      dist/reveal.esm.js
  24. 0 0
      dist/reveal.esm.js.map
  25. 2 2
      dist/reveal.js
  26. 0 0
      dist/reveal.js.map
  27. 9 7
      dist/theme/beige.css
  28. 362 0
      dist/theme/black-contrast.css
  29. 3 1
      dist/theme/black.css
  30. 2 0
      dist/theme/blood.css
  31. 385 0
      dist/theme/dracula.css
  32. 9 7
      dist/theme/league.css
  33. 2 5
      dist/theme/moon.css
  34. 2 0
      dist/theme/night.css
  35. 2 0
      dist/theme/serif.css
  36. 2 0
      dist/theme/simple.css
  37. 2 0
      dist/theme/sky.css
  38. 2 0
      dist/theme/solarized.css
  39. 362 0
      dist/theme/white-contrast.css
  40. 2 0
      dist/theme/white.css
  41. 360 0
      dist/theme/white_contrast_compact_verbatim_headers.css
  42. 526 0
      examples/500-slides.html
  43. 19 0
      examples/markdown.html
  44. 118 0
      examples/scroll.html
  45. 14 12
      gulpfile.js
  46. 2 10
      index.html
  47. 37 0
      js/config.js
  48. 7 7
      js/controllers/autoanimate.js
  49. 46 13
      js/controllers/backgrounds.js
  50. 5 6
      js/controllers/fragments.js
  51. 197 0
      js/controllers/jumptoslide.js
  52. 15 21
      js/controllers/keyboard.js
  53. 9 7
      js/controllers/location.js
  54. 10 4
      js/controllers/notes.js
  55. 1 1
      js/controllers/overview.js
  56. 2 2
      js/controllers/plugins.js
  57. 4 7
      js/controllers/pointer.js
  58. 10 8
      js/controllers/printview.js
  59. 906 0
      js/controllers/scrollview.js
  60. 14 6
      js/controllers/slidecontent.js
  61. 13 6
      js/controllers/slidenumber.js
  62. 322 73
      js/reveal.js
  63. 7 1
      js/utils/constants.js
  64. 16 0
      js/utils/util.js
  65. 418 328
      package-lock.json
  66. 26 22
      package.json
  67. 0 0
      plugin/highlight/highlight.esm.js
  68. 0 0
      plugin/highlight/highlight.js
  69. 1 1
      plugin/highlight/plugin.js
  70. 0 0
      plugin/markdown/markdown.esm.js
  71. 0 0
      plugin/markdown/markdown.js
  72. 64 48
      plugin/markdown/plugin.js
  73. 0 0
      plugin/notes/notes.esm.js
  74. 0 0
      plugin/notes/notes.js
  75. 7 3
      plugin/notes/plugin.js
  76. 9 2
      plugin/notes/speaker-view.html
  77. 1 1
      plugin/search/plugin.js
  78. 0 0
      plugin/search/search.esm.js
  79. 0 0
      plugin/search/search.js
  80. 1 0
      test/test-auto-animate.html
  81. 1 0
      test/test-dependencies-async.html
  82. 5 1
      test/test-dependencies.html
  83. 5 1
      test/test-grid-navigation.html
  84. 2 0
      test/test-iframe-backgrounds.html
  85. 2 0
      test/test-iframes.html
  86. 36 0
      test/test-markdown.html
  87. 1 0
      test/test-multiple-instances-es5.html
  88. 1 0
      test/test-multiple-instances.html
  89. 5 0
      test/test-pdf.html
  90. 1 0
      test/test-plugins.html
  91. 115 0
      test/test-scroll.html
  92. 65 67
      test/test-state.html
  93. 2 0
      test/test.html

+ 20 - 13
.github/workflows/js.yml

@@ -1,24 +1,31 @@
 name: tests
 
-on: [push]
+on:
+  - push
+
+permissions:
+  contents: read
 
 jobs:
   build:
-
     runs-on: ubuntu-latest
 
     strategy:
       matrix:
-        node-version: [10.x, 14.x, 16.x]
+        node-version:
+          - 18
+          - 20
 
     steps:
-    - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
-      with:
-        node-version: ${{ matrix.node-version }}
-    - run: npm install
-    - run: npm run build --if-present
-    - run: npm test
-      env:
-        CI: true
+      - uses: actions/checkout@v4
+
+      - name: Use Node.js ${{ matrix.node-version }}
+        uses: actions/setup-node@v4
+        with:
+          node-version: ${{ matrix.node-version }}
+
+      - run: npm install
+      - run: npm run build --if-present
+      - run: npm test
+        env:
+          CI: true

+ 0 - 2
.npmignore

@@ -1,7 +1,5 @@
 /test
 /examples
 .github
-.gulpfile
 .sass-cache
 gulpfile.js
-CONTRIBUTING.md

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (C) 2011-2022 Hakim El Hattab, http://hakim.se, and reveal.js contributors
+Copyright (C) 2011-2024 Hakim El Hattab, http://hakim.se, and reveal.js contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 1 - 12
README.md

@@ -32,17 +32,6 @@ Hakim's open source work is supported by <a href="https://github.com/sponsors/ha
         </div>
       </a>
     </td>
-    <td align="center">
-      <a href="https://www.doppler.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=revealjs&utm_source=github">
-        <div>
-          <img src="https://user-images.githubusercontent.com/629429/151510865-9fd454f1-fd8c-4df4-b227-a54b87313db4.png" width="290" alt="Doppler">
-        </div>
-        <b>All your environment variables, in one place</b>
-        <div>
-          <sub>Stop struggling with scattered API keys, hacking together home-brewed tools, and avoiding access controls. Keep your team and servers in sync with Doppler.</sup>
-        </div>
-      </a>
-    </td>
   </table>
 </div>
 
@@ -57,5 +46,5 @@ Hakim's open source work is supported by <a href="https://github.com/sponsors/ha
 
 --- 
 <div align="center">
-  MIT licensed | Copyright © 2011-2022 Hakim El Hattab, https://hakim.se
+  MIT licensed | Copyright © 2011-2024 Hakim El Hattab, https://hakim.se
 </div>

+ 27 - 39
css/print/paper.scss

@@ -1,42 +1,30 @@
-/* Default Print Stylesheet Template
-   by Rob Glazebrook of CSSnewbie.com
-   Last Updated: June 4, 2008
-
-   Feel free (nay, compelled) to edit, append, and
-   manipulate this file as you see fit. */
 
 @media print {
 	html:not(.print-pdf) {
-
-		background: #fff;
+		overflow: visible;
 		width: auto;
 		height: auto;
-		overflow: visible;
 
 		body {
-			background: #fff;
-			font-size: 20pt;
-			width: auto;
-			height: auto;
-			border: 0;
-			margin: 0 5%;
+			margin: 0;
 			padding: 0;
 			overflow: visible;
-			float: none !important;
 		}
+	}
+
+	html:not(.print-pdf) .reveal {
+		background: #fff;
+		font-size: 20pt;
 
-		.nestedarrow,
 		.controls,
-		.fork-reveal,
-		.share-reveal,
 		.state-background,
-		.reveal .progress,
-		.reveal .backgrounds,
-		.reveal .slide-number {
+		.progress,
+		.backgrounds,
+		.slide-number {
 			display: none !important;
 		}
 
-		body, p, td, li {
+		p, td, li {
 			font-size: 20pt!important;
 			color: #000;
 		}
@@ -49,7 +37,6 @@
 			letter-spacing: normal;
 		}
 
-		/* Need to reduce the size of the fonts for printing */
 		h1 { font-size: 28pt !important; }
 		h2 { font-size: 24pt !important; }
 		h3 { font-size: 22pt !important; }
@@ -74,18 +61,19 @@
 			margin: 0;
 			text-align: left !important;
 		}
-		.reveal pre,
-		.reveal table {
+		pre,
+		table {
 			margin-left: 0;
 			margin-right: 0;
 		}
-		.reveal pre code {
+		pre code {
 			padding: 20px;
 		}
-		.reveal blockquote {
+		blockquote {
 			margin: 20px 0;
 		}
-		.reveal .slides {
+
+		.slides {
 			position: static !important;
 			width: auto !important;
 			height: auto !important;
@@ -106,7 +94,7 @@
 
 			perspective-origin: 50% 50%;
 		}
-		.reveal .slides section {
+		.slides section {
 			visibility: visible !important;
 			position: static !important;
 			width: auto !important;
@@ -129,24 +117,24 @@
 			transform: none !important;
 			transition: none !important;
 		}
-		.reveal .slides section.stack {
+		.slides section.stack {
 			padding: 0 !important;
 		}
-		.reveal section:last-of-type {
+		.slides section:last-of-type {
 			page-break-after: avoid !important;
 		}
-		.reveal section .fragment {
+		.slides section .fragment {
 			opacity: 1 !important;
 			visibility: visible !important;
 
 			transform: none !important;
 		}
 
-		.reveal .r-fit-text {
+		.r-fit-text {
 			white-space: normal !important;
 		}
 
-		.reveal section img {
+		section img {
 			display: block;
 			margin: 15px 0px;
 			background: rgba(255,255,255,1);
@@ -154,11 +142,11 @@
 			box-shadow: none;
 		}
 
-		.reveal section small {
+		section small {
 			font-size: 0.8em;
 		}
 
-		.reveal .hljs {
+		.hljs {
 			max-height: 100%;
 			white-space: pre-wrap;
 			word-wrap: break-word;
@@ -166,11 +154,11 @@
 			font-size: 15pt;
 		}
 
-		.reveal .hljs .hljs-ln-numbers {
+		.hljs .hljs-ln-numbers {
 			white-space: nowrap;
 		}
 
-		.reveal .hljs td {
+		.hljs td {
 			font-size: inherit !important;
 			color: inherit !important;
 		}

+ 6 - 2
css/print/pdf.scss

@@ -5,7 +5,7 @@
  * https://revealjs.com/pdf-export/
  */
 
-html.print-pdf {
+html.reveal-print {
 	* {
 		-webkit-print-color-adjust: exact;
 	}
@@ -36,7 +36,6 @@ html.print-pdf {
 
 	.reveal pre code {
 		overflow: hidden !important;
-		font-family: Courier, 'Courier New', monospace !important;
 	}
 
 	.reveal {
@@ -71,6 +70,10 @@ html.print-pdf {
 		page-break-after: always;
 	}
 
+	.reveal .slides .pdf-page:last-of-type {
+		page-break-after: avoid;
+	}
+
 	.reveal .slides section {
 		visibility: visible !important;
 		display: block !important;
@@ -146,6 +149,7 @@ html.print-pdf {
 		display: block;
 		position: absolute;
 		font-size: 14px;
+		visibility: visible;
 	}
 
 	/* This accessibility tool is not useful in PDF and breaks it visually */

+ 320 - 40
css/reveal.scss

@@ -19,6 +19,7 @@ html.reveal-full-page {
 	height: 100%;
 	height: 100vh;
 	height: calc( var(--vh, 1vh) * 100 );
+	height: 100svh;
 	overflow: hidden;
 }
 
@@ -31,6 +32,8 @@ html.reveal-full-page {
 
 	background-color: #fff;
 	color: #000;
+
+	--r-controls-spacing: 12px;
 }
 
 // Force the presentation to cover the full viewport when we
@@ -48,11 +51,14 @@ html.reveal-full-page {
  * VIEW FRAGMENTS
  *********************************************/
 
-.reveal .slides section .fragment {
-	opacity: 0;
-	visibility: hidden;
+.reveal .fragment {
 	transition: all .2s ease;
-	will-change: opacity;
+
+	&:not(.custom) {
+		opacity: 0;
+		visibility: hidden;
+		will-change: opacity;
+	}
 
 	&.visible {
 		opacity: 1;
@@ -64,7 +70,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.grow {
+.reveal .fragment.grow {
 	opacity: 1;
 	visibility: inherit;
 
@@ -73,7 +79,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.shrink {
+.reveal .fragment.shrink {
 	opacity: 1;
 	visibility: inherit;
 
@@ -82,7 +88,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.zoom-in {
+.reveal .fragment.zoom-in {
 	transform: scale( 0.1 );
 
 	&.visible {
@@ -90,7 +96,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-out {
+.reveal .fragment.fade-out {
 	opacity: 1;
 	visibility: inherit;
 
@@ -100,7 +106,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.semi-fade-out {
+.reveal .fragment.semi-fade-out {
 	opacity: 1;
 	visibility: inherit;
 
@@ -110,7 +116,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.strike {
+.reveal .fragment.strike {
 	opacity: 1;
 	visibility: inherit;
 
@@ -119,7 +125,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-up {
+.reveal .fragment.fade-up {
 	transform: translate(0, 40px);
 
 	&.visible {
@@ -127,7 +133,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-down {
+.reveal .fragment.fade-down {
 	transform: translate(0, -40px);
 
 	&.visible {
@@ -135,7 +141,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-right {
+.reveal .fragment.fade-right {
 	transform: translate(-40px, 0);
 
 	&.visible {
@@ -143,7 +149,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-left {
+.reveal .fragment.fade-left {
 	transform: translate(40px, 0);
 
 	&.visible {
@@ -151,8 +157,8 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-in-then-out,
-.reveal .slides section .fragment.current-visible {
+.reveal .fragment.fade-in-then-out,
+.reveal .fragment.current-visible {
 	opacity: 0;
 	visibility: hidden;
 
@@ -162,7 +168,7 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.fade-in-then-semi-out {
+.reveal .fragment.fade-in-then-semi-out {
 	opacity: 0;
 	visibility: hidden;
 
@@ -177,32 +183,32 @@ html.reveal-full-page {
 	}
 }
 
-.reveal .slides section .fragment.highlight-red,
-.reveal .slides section .fragment.highlight-current-red,
-.reveal .slides section .fragment.highlight-green,
-.reveal .slides section .fragment.highlight-current-green,
-.reveal .slides section .fragment.highlight-blue,
-.reveal .slides section .fragment.highlight-current-blue {
+.reveal .fragment.highlight-red,
+.reveal .fragment.highlight-current-red,
+.reveal .fragment.highlight-green,
+.reveal .fragment.highlight-current-green,
+.reveal .fragment.highlight-blue,
+.reveal .fragment.highlight-current-blue {
 	opacity: 1;
 	visibility: inherit;
 }
-	.reveal .slides section .fragment.highlight-red.visible {
+	.reveal .fragment.highlight-red.visible {
 		color: #ff2c2d
 	}
-	.reveal .slides section .fragment.highlight-green.visible {
+	.reveal .fragment.highlight-green.visible {
 		color: #17ff2e;
 	}
-	.reveal .slides section .fragment.highlight-blue.visible {
+	.reveal .fragment.highlight-blue.visible {
 		color: #1b91ff;
 	}
 
-.reveal .slides section .fragment.highlight-current-red.current-fragment {
+.reveal .fragment.highlight-current-red.current-fragment {
 	color: #ff2c2d
 }
-.reveal .slides section .fragment.highlight-current-green.current-fragment {
+.reveal .fragment.highlight-current-green.current-fragment {
 	color: #17ff2e;
 }
-.reveal .slides section .fragment.highlight-current-blue.current-fragment {
+.reveal .fragment.highlight-current-blue.current-fragment {
 	color: #1b91ff;
 }
 
@@ -268,13 +274,11 @@ $controlsArrowAngleActive: 36deg;
 }
 
 .reveal .controls {
-	$spacing: 12px;
-
 	display: none;
 	position: absolute;
 	top: auto;
-	bottom: $spacing;
-	right: $spacing;
+	bottom: var(--r-controls-spacing);
+	right: var(--r-controls-spacing);
 	left: auto;
 	z-index: 11;
 	color: #000;
@@ -506,7 +510,9 @@ $controlsArrowAngleActive: 36deg;
 // Edge aligned controls layout
 @media screen and (min-width: 500px) {
 
-	$spacing: 0.8em;
+	.reveal-viewport {
+		--r-controls-spacing: 0.8em;
+	}
 
 	.reveal .controls[data-controls-layout="edges"] {
 		& {
@@ -526,24 +532,24 @@ $controlsArrowAngleActive: 36deg;
 
 		.navigate-left {
 			top: 50%;
-			left: $spacing;
+			left: var(--r-controls-spacing);
 			margin-top: -$controlArrowSize*0.5;
 		}
 
 		.navigate-right {
 			top: 50%;
-			right: $spacing;
+			right: var(--r-controls-spacing);
 			margin-top: -$controlArrowSize*0.5;
 		}
 
 		.navigate-up {
-			top: $spacing;
+			top: var(--r-controls-spacing);
 			left: 50%;
 			margin-left: -$controlArrowSize*0.5;
 		}
 
 		.navigate-down {
-			bottom: $spacing - $controlArrowSpacing + 0.3em;
+			bottom: calc(var(--r-controls-spacing) - #{$controlArrowSpacing} + 0.3em);
 			left: 50%;
 			margin-left: -$controlArrowSize*0.5;
 		}
@@ -1439,7 +1445,8 @@ $overlayHeaderPadding: 5px;
 	width: 100%;
 	height: 100%;
 	z-index: 1000;
-	background: rgba( 0, 0, 0, 0.9 );
+	background: rgba( 0, 0, 0, 0.95 );
+	backdrop-filter: blur( 6px );
 	transition: all 0.3s ease;
 }
 
@@ -1583,7 +1590,6 @@ $overlayHeaderPadding: 5px;
 		padding-bottom: 20px;
 	}
 
-
 /*********************************************
  * PLAYBACK COMPONENT
  *********************************************/
@@ -1633,6 +1639,10 @@ $overlayHeaderPadding: 5px;
 	opacity: 0.4;
 }
 
+.reveal .hljs.has-highlights.fragment {
+	transition: all .2s ease;
+}
+
 .reveal .hljs:not(:first-child).fragment {
 	position: absolute;
 	top: 0;
@@ -1796,6 +1806,43 @@ $notesWidthPercent: 25%;
 }
 
 
+/*********************************************
+ * JUMP-TO-SLIDE COMPONENT
+ *********************************************/
+
+ .reveal .jump-to-slide {
+	position: absolute;
+	top: 15px;
+	left: 15px;
+	z-index: 30;
+	font-size: 32px;
+	-webkit-tap-highlight-color: rgba( 0, 0, 0, 0 );
+}
+
+.reveal .jump-to-slide-input {
+	background: transparent;
+	padding: 8px;
+	font-size: inherit;
+	color: currentColor;
+	border: 0;
+}
+.reveal .jump-to-slide-input::placeholder {
+	color: currentColor;
+	opacity: 0.5;
+}
+
+.reveal.has-dark-background .jump-to-slide-input {
+	color: #fff;
+}
+.reveal.has-light-background .jump-to-slide-input {
+	color: #222;
+}
+
+.reveal .jump-to-slide-input:focus {
+	outline: none;
+}
+
+
 /*********************************************
  * ZOOM PLUGIN
  *********************************************/
@@ -1820,6 +1867,239 @@ $notesWidthPercent: 25%;
 }
 
 
+/*********************************************
+ * SCROLL VIEW
+ *********************************************/
+.reveal-viewport.loading-scroll-mode {
+	visibility: hidden;
+}
+
+.reveal-viewport.reveal-scroll {
+	& {
+		margin: 0 auto;
+		overflow: auto;
+		overflow-x: hidden;
+		overflow-y: auto;
+		z-index: 1;
+
+		--r-scrollbar-width: 7px;
+		--r-scrollbar-trigger-size: 5px;
+		--r-controls-spacing: 8px;
+	}
+
+	@media screen and (max-width: 500px) {
+		--r-scrollbar-width: 3px;
+		--r-scrollbar-trigger-size: 3px;
+	}
+
+	.controls,
+	.progress,
+	.playback,
+	.backgrounds,
+	.slide-number,
+	.speaker-notes {
+		display: none !important;
+	}
+
+	.overlay,
+	.pause-overlay {
+		position: fixed;
+	}
+
+	.reveal {
+		overflow: visible;
+		touch-action: manipulation;
+	}
+
+	.slides {
+		position: static;
+		pointer-events: initial;
+
+		left: auto;
+		top: auto;
+		width: 100% !important;
+		margin: 0;
+		padding: 0;
+
+		overflow: visible;
+		display: block;
+
+		perspective: none;
+		perspective-origin: 50% 50%;
+	}
+
+	.scroll-page {
+		position: relative;
+		width: 100%;
+		height: calc(var(--page-height) + var(--page-scroll-padding));
+		z-index: 1;
+		overflow: visible;
+	}
+
+	.scroll-page-sticky {
+		position: sticky;
+		height: var(--page-height);
+		top: 0px;
+	}
+
+	.scroll-page-content {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		overflow: hidden;
+	}
+
+	.scroll-page section {
+		visibility: visible !important;
+		display: block !important;
+		position: absolute !important;
+		width: var(--slide-width) !important;
+		height: var(--slide-height) !important;
+		top: 50% !important;
+		left: 50% !important;
+		opacity: 1 !important;
+		transform: scale(var(--slide-scale)) translate(-50%, -50%) !important;
+		transform-style: flat !important;
+		transform-origin: 0 0 !important;
+	}
+
+	.slide-background {
+		display: block !important;
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		z-index: auto !important;
+		visibility: visible;
+		opacity: 1;
+		touch-action: manipulation;
+	}
+}
+
+// Chromium
+.reveal-viewport.reveal-scroll[data-scrollbar="true"]::-webkit-scrollbar,
+.reveal-viewport.reveal-scroll[data-scrollbar="auto"]::-webkit-scrollbar {
+  display: none;
+}
+
+// Firefox
+.reveal-viewport.reveal-scroll[data-scrollbar="true"],
+.reveal-viewport.reveal-scroll[data-scrollbar="auto"] {
+  scrollbar-width: none;
+}
+
+.reveal.has-dark-background,
+.reveal-viewport.has-dark-background {
+	--r-overlay-element-bg-color: 240, 240, 240;
+	--r-overlay-element-fg-color: 0, 0, 0;
+}
+.reveal.has-light-background,
+.reveal-viewport.has-light-background {
+	--r-overlay-element-bg-color: 0, 0, 0;
+	--r-overlay-element-fg-color: 240, 240, 240;
+}
+
+.reveal-viewport.reveal-scroll .scrollbar {
+	position: sticky;
+	top: 50%;
+	z-index: 20;
+	opacity: 0;
+	transition: all 0.3s ease;
+
+	&.visible,
+	&:hover {
+		opacity: 1;
+	}
+
+	.scrollbar-inner {
+		position: absolute;
+		width: var(--r-scrollbar-width);
+		height: calc(var(--viewport-height) - var(--r-controls-spacing) * 2);
+		right: var(--r-controls-spacing);
+		top: 0;
+		transform: translateY(-50%);
+		border-radius: var(--r-scrollbar-width);
+		z-index: 10;
+	}
+
+	.scrollbar-playhead {
+		position: absolute;
+		width: var(--r-scrollbar-width);
+		height: var(--r-scrollbar-width);
+		top: 0;
+		left: 0;
+		border-radius: var(--r-scrollbar-width);
+		background-color: rgba(var(--r-overlay-element-bg-color), 1);
+		z-index: 11;
+		transition: background-color 0.2s ease;
+	}
+
+	.scrollbar-slide {
+		position: absolute;
+		width: 100%;
+		background-color: rgba(var(--r-overlay-element-bg-color), 0.2);
+		box-shadow: 0 0 0px 1px rgba(var(--r-overlay-element-fg-color), 0.1);
+		border-radius: var(--r-scrollbar-width);
+		transition: background-color 0.2s ease;
+	}
+
+	// Hit area
+	.scrollbar-slide:after {
+		content: '';
+		position: absolute;
+		width: 200%;
+		height: 100%;
+		top: 0;
+		left: -50%;
+		background: rgba( 0, 0, 0, 0 );
+		z-index: -1;
+	}
+
+	.scrollbar-slide:hover,
+	.scrollbar-slide.active {
+		background-color: rgba(var(--r-overlay-element-bg-color), 0.4);
+	}
+
+	.scrollbar-trigger {
+		position: absolute;
+		width: 100%;
+		transition: background-color 0.2s ease;
+	}
+
+	.scrollbar-slide.active.has-triggers {
+		background-color: rgba(var(--r-overlay-element-bg-color), 0.4);
+		z-index: 10;
+	}
+
+	.scrollbar-slide.active .scrollbar-trigger:after {
+		content: '';
+		position: absolute;
+		width: var(--r-scrollbar-trigger-size);
+		height: var(--r-scrollbar-trigger-size);
+		border-radius: 20px;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		background-color: rgba(var(--r-overlay-element-bg-color), 1);
+		transition: transform 0.2s ease, opacity 0.2s ease;
+		opacity: 0.4;
+	}
+
+	.scrollbar-slide.active .scrollbar-trigger.active:after,
+	.scrollbar-slide.active .scrollbar-trigger.active ~ .scrollbar-trigger:after {
+		opacity: 1;
+	}
+
+	.scrollbar-slide.active .scrollbar-trigger ~ .scrollbar-trigger.active:after {
+		transform: translate(calc( var(--r-scrollbar-width) * -2), 0);
+		background-color: rgba(var(--r-overlay-element-bg-color), 1);
+	}
+}
+
+
 /*********************************************
  * PRINT STYLES
  *********************************************/

+ 3 - 0
css/theme/source/beige.scss

@@ -27,6 +27,9 @@ $linkColorHover: lighten( $linkColor, 20% );
 $selectionBackgroundColor: rgba(79, 64, 28, 0.99);
 $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
 
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
 // Background generator
 @mixin bodyBackground() {
 	@include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) );

+ 49 - 0
css/theme/source/black-contrast.scss

@@ -0,0 +1,49 @@
+/**
+ * Black compact & high contrast reveal.js theme, with headers not in capitals.
+ *
+ * By Peter Kehl. Based on black.(s)css by Hakim El Hattab, http://hakim.se
+ *
+ * - Keep the source similar to black.css - for easy comparison.
+ * - $mainFontSize controls code blocks, too (although under some ratio).
+ */
+
+
+// Default mixins and settings -----------------
+@import "../template/mixins";
+@import "../template/settings";
+// ---------------------------------------------
+
+
+// Include theme-specific fonts
+@import url(./fonts/source-sans-pro/source-sans-pro.css);
+
+
+// Override theme settings (see ../template/settings.scss)
+$backgroundColor: #000000;
+
+$mainColor: #fff;
+$headingColor: #fff;
+
+$mainFontSize: 42px;
+$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
+$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
+$headingTextShadow: none;
+$headingLetterSpacing: normal;
+$headingTextTransform: uppercase;
+$headingFontWeight: 600;
+$linkColor: #42affa;
+$linkColorHover: lighten( $linkColor, 15% );
+$selectionBackgroundColor: lighten( $linkColor, 25% );
+
+$heading1Size: 2.5em;
+$heading2Size: 1.6em;
+$heading3Size: 1.3em;
+$heading4Size: 1.0em;
+
+// Change text colors against light slide backgrounds
+@include light-bg-text-color(#000);
+
+
+// Theme template ------------------------------
+@import "../template/theme";
+// ---------------------------------------------

+ 1 - 1
css/theme/source/black.scss

@@ -30,7 +30,7 @@ $headingTextTransform: uppercase;
 $headingFontWeight: 600;
 $linkColor: #42affa;
 $linkColorHover: lighten( $linkColor, 15% );
-$selectionBackgroundColor: lighten( $linkColor, 25% );
+$selectionBackgroundColor: rgba( $linkColor, 0.75 );
 
 $heading1Size: 2.5em;
 $heading2Size: 1.6em;

+ 106 - 0
css/theme/source/dracula.scss

@@ -0,0 +1,106 @@
+/**
+ * Dracula Dark theme for reveal.js.
+ * Based on https://draculatheme.com
+ */
+
+
+// Default mixins and settings -----------------
+@import "../template/mixins";
+@import "../template/settings";
+// ---------------------------------------------
+
+
+
+// Include theme-specific fonts
+$systemFontsSansSerif: -apple-system,
+					   BlinkMacSystemFont,
+					   avenir next,
+					   avenir,
+					   segoe ui,
+					   helvetica neue,
+					   helvetica,
+					   Cantarell,
+					   Ubuntu,
+					   roboto,
+					   noto,
+					   arial,
+					   sans-serif;
+$systemFontsMono: Menlo,
+				  Consolas,
+				  Monaco,
+				  Liberation Mono,
+				  Lucida Console,
+				  monospace;
+
+/**
+ * Dracula colors by Zeno Rocha
+ * https://draculatheme.com/contribute
+ */
+html * {
+	color-profile: sRGB;
+	rendering-intent: auto;
+}
+
+$background: #282A36;
+$foreground: #F8F8F2;
+$selection: #44475A;
+$comment: #6272A4;
+$red: #FF5555;
+$orange: #FFB86C;
+$yellow: #F1FA8C;
+$green: #50FA7B;
+$purple: #BD93F9;
+$cyan: #8BE9FD;
+$pink: #FF79C6;
+
+
+
+// Override theme settings (see ../template/settings.scss)
+$mainColor: $foreground;
+$headingColor: $purple;
+$headingTextShadow: none;
+$headingTextTransform: none;
+$backgroundColor: $background;
+$linkColor: $pink;
+$linkColorHover: $cyan;
+$selectionBackgroundColor: $selection;
+$inlineCodeColor: $green;
+$listBulletColor: $cyan;
+
+$mainFont: $systemFontsSansSerif;
+$codeFont: "Fira Code", $systemFontsMono;
+
+// Change text colors against light slide backgrounds
+@include light-bg-text-color($background);
+
+// Theme template ------------------------------
+@import "../template/theme";
+// ---------------------------------------------
+
+// Define additional color effects based on Dracula spec
+// https://spec.draculatheme.com/
+:root {
+	--r-bold-color: #{$orange};
+	--r-italic-color: #{$yellow};
+	--r-inline-code-color: #{$inlineCodeColor};
+	--r-list-bullet-color: #{$listBulletColor};
+}
+
+.reveal {
+	strong, b {
+		color: var(--r-bold-color);
+	}
+	em, i, blockquote {
+		color: var(--r-italic-color);
+	}
+	code {
+		color: var(--r-inline-code-color);
+	}
+	// Dracula colored list bullets and numbers
+	ul, ol {
+		li::marker {
+			color: var(--r-list-bullet-color);
+		}
+	}
+}
+

+ 0 - 4
css/theme/source/moon.scss

@@ -18,10 +18,6 @@
 /**
  * Solarized colors by Ethan Schoonover
  */
-html * {
-	color-profile: sRGB;
-	rendering-intent: auto;
-}
 
 // Solarized colors
 $base03:    #002b36;

+ 3 - 0
css/theme/source/serif.scss

@@ -25,6 +25,9 @@ $linkColor: #51483D;
 $linkColorHover: lighten( $linkColor, 20% );
 $selectionBackgroundColor: #26351C;
 
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
 .reveal a {
   line-height: 1.3em;
 }

+ 3 - 0
css/theme/source/simple.scss

@@ -31,6 +31,9 @@ $linkColor: #00008B;
 $linkColorHover: lighten( $linkColor, 20% );
 $selectionBackgroundColor: rgba(0, 0, 0, 0.99);
 
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
 // Change text colors against dark slide backgrounds
 @include dark-bg-text-color(#fff);
 

+ 3 - 0
css/theme/source/sky.scss

@@ -29,6 +29,9 @@ $linkColor: #3b759e;
 $linkColorHover: lighten( $linkColor, 20% );
 $selectionBackgroundColor: #134674;
 
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
 // Fix links so they are not cut off
 .reveal a {
 	line-height: 1.3em;

+ 3 - 0
css/theme/source/solarized.scss

@@ -51,6 +51,9 @@ $linkColor: $blue;
 $linkColorHover: lighten( $linkColor, 20% );
 $selectionBackgroundColor: $magenta;
 
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
 // Background generator
 // @mixin bodyBackground() {
 // 	@include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) );

+ 52 - 0
css/theme/source/white-contrast.scss

@@ -0,0 +1,52 @@
+/**
+ * White compact & high contrast reveal.js theme, with headers not in capitals.
+ *
+ * By Peter Kehl. Based on white.(s)css by Hakim El Hattab, http://hakim.se
+ *
+ * - Keep the source similar to black.css - for easy comparison.
+ * - $mainFontSize controls code blocks, too (although under some ratio).
+ */
+
+
+// Default mixins and settings -----------------
+@import "../template/mixins";
+@import "../template/settings";
+// ---------------------------------------------
+
+
+// Include theme-specific fonts
+@import url(./fonts/source-sans-pro/source-sans-pro.css);
+
+
+// Override theme settings (see ../template/settings.scss)
+$backgroundColor: #fff;
+
+$mainColor: #000;
+$headingColor: #000;
+
+$mainFontSize: 42px;
+$mainFont: 'Source Sans Pro', Helvetica, sans-serif;
+$headingFont: 'Source Sans Pro', Helvetica, sans-serif;
+$headingTextShadow: none;
+$headingLetterSpacing: normal;
+$headingTextTransform: uppercase;
+$headingFontWeight: 600;
+$linkColor: #2a76dd;
+$linkColorHover: lighten( $linkColor, 15% );
+$selectionBackgroundColor: lighten( $linkColor, 25% );
+
+$heading1Size: 2.5em;
+$heading2Size: 1.6em;
+$heading3Size: 1.3em;
+$heading4Size: 1.0em;
+
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
+// Change text colors against dark slide backgrounds
+@include dark-bg-text-color(#fff);
+
+
+// Theme template ------------------------------
+@import "../template/theme";
+// ---------------------------------------------

+ 3 - 0
css/theme/source/white.scss

@@ -37,6 +37,9 @@ $heading2Size: 1.6em;
 $heading3Size: 1.3em;
 $heading4Size: 1.0em;
 
+$overlayElementBgColor: 0, 0, 0;
+$overlayElementFgColor: 240, 240, 240;
+
 // Change text colors against dark slide backgrounds
 @include dark-bg-text-color(#fff);
 

+ 2 - 0
css/theme/template/exposer.scss

@@ -25,4 +25,6 @@
   --r-link-color-hover: #{$linkColorHover};
   --r-selection-background-color: #{$selectionBackgroundColor};
   --r-selection-color: #{$selectionColor};
+  --r-overlay-element-bg-color: #{$overlayElementBgColor};
+  --r-overlay-element-fg-color: #{$overlayElementFgColor};
 }

+ 5 - 0
css/theme/template/settings.scss

@@ -38,6 +38,11 @@ $linkColorHover: lighten( $linkColor, 20% );
 $selectionBackgroundColor: #FF5E99;
 $selectionColor: #fff;
 
+// Colors used for UI elements that are overlaid on top of
+// the presentation
+$overlayElementBgColor: 240, 240, 240;
+$overlayElementFgColor: 0, 0, 0;
+
 // Generates the presentation background, can be overridden
 // to return a background image or gradient
 @mixin bodyBackground() {

+ 16 - 16
demo.html

@@ -86,7 +86,7 @@
 
 				<section data-auto-animate>
 					<h2 data-id="code-title">Pretty Code</h2>
-					<pre data-id="code-animation"><code class="hljs" data-trim data-line-numbers>
+					<pre data-id="code-animation"><code class="hljs javascript" data-trim data-line-numbers>
 						import React, { useState } from 'react';
 
 						function Example() {
@@ -101,8 +101,8 @@
 				</section>
 
 				<section data-auto-animate>
-					<h2 data-id="code-title">With animations</h2>
-					<pre data-id="code-animation"><code class="hljs" data-trim data-line-numbers="|4,8-11|17|22-24"><script type="text/template">
+					<h2 data-id="code-title">With Animations</h2>
+					<pre data-id="code-animation"><code class="hljs javascript" data-trim data-line-numbers="|4,8-11|17|22-24"><script type="text/template">
 						import React, { useState } from 'react';
 
 						function Example() {
@@ -181,14 +181,14 @@
 
 				<section data-markdown>
 					<script type="text/template">
-						## Markdown support
+						## Markdown Support
 
 						Write content using inline or external Markdown.
 						Instructions and more info available in the [docs](https://revealjs.com/markdown/).
 
 						```html []
 						<section data-markdown>
-						  ## Markdown support
+						  ## Markdown Support
 
 						  Write content using inline or external Markdown.
 						  Instructions and more info available in the [docs](https://revealjs.com/markdown/).
@@ -249,17 +249,17 @@
 					<p>
 						reveal.js comes with a few themes built in: <br>
 						<!-- Hacks to swap themes after the page has loaded. Not flexible and only intended for the reveal.js demo deck. -->
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/black.css'); return false;">Black (default)</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/white.css'); return false;">White</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/league.css'); return false;">League</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/sky.css'); return false;">Sky</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/beige.css'); return false;">Beige</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/simple.css'); return false;">Simple</a> <br>
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/serif.css'); return false;">Serif</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/blood.css'); return false;">Blood</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/night.css'); return false;">Night</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/moon.css'); return false;">Moon</a> -
-						<a href="#" onclick="document.getElementById('theme').setAttribute('href','dist/theme/solarized.css'); return false;">Solarized</a>
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/black.css'); return false;">Black (default)</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/white.css'); return false;">White</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/league.css'); return false;">League</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/sky.css'); return false;">Sky</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/beige.css'); return false;">Beige</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/simple.css'); return false;">Simple</a> <br>
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/serif.css'); return false;">Serif</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/blood.css'); return false;">Blood</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/night.css'); return false;">Night</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/moon.css'); return false;">Moon</a> -
+						<a href="#/themes" onclick="document.getElementById('theme').setAttribute('href','dist/theme/solarized.css'); return false;">Solarized</a>
 					</p>
 				</section>
 

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 6
dist/reveal.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 2
dist/reveal.esm.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
dist/reveal.esm.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 2
dist/reveal.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
dist/reveal.js.map


+ 9 - 7
dist/theme/beige.css

@@ -37,16 +37,18 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
   --r-link-color-hover: #c0a86e;
   --r-selection-background-color: rgba(79, 64, 28, 0.99);
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
 }
 
 .reveal-viewport {
-  background: #f7f2d3;
-  background: -moz-radial-gradient(center, circle cover, white 0%, #f7f2d3 100%);
-  background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%, white), color-stop(100%, #f7f2d3));
-  background: -webkit-radial-gradient(center, circle cover, white 0%, #f7f2d3 100%);
-  background: -o-radial-gradient(center, circle cover, white 0%, #f7f2d3 100%);
-  background: -ms-radial-gradient(center, circle cover, white 0%, #f7f2d3 100%);
-  background: radial-gradient(center, circle cover, white 0%, #f7f2d3 100%);
+  background: rgb(247, 242, 211);
+  background: -moz-radial-gradient(center, circle cover, rgb(255, 255, 255) 0%, rgb(247, 242, 211) 100%);
+  background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%, rgb(255, 255, 255)), color-stop(100%, rgb(247, 242, 211)));
+  background: -webkit-radial-gradient(center, circle cover, rgb(255, 255, 255) 0%, rgb(247, 242, 211) 100%);
+  background: -o-radial-gradient(center, circle cover, rgb(255, 255, 255) 0%, rgb(247, 242, 211) 100%);
+  background: -ms-radial-gradient(center, circle cover, rgb(255, 255, 255) 0%, rgb(247, 242, 211) 100%);
+  background: radial-gradient(center, circle cover, rgb(255, 255, 255) 0%, rgb(247, 242, 211) 100%);
   background-color: var(--r-background-color);
 }
 

+ 362 - 0
dist/theme/black-contrast.css

@@ -0,0 +1,362 @@
+/**
+ * Black compact & high contrast reveal.js theme, with headers not in capitals.
+ *
+ * By Peter Kehl. Based on black.(s)css by Hakim El Hattab, http://hakim.se
+ *
+ * - Keep the source similar to black.css - for easy comparison.
+ * - $mainFontSize controls code blocks, too (although under some ratio).
+ */
+@import url(./fonts/source-sans-pro/source-sans-pro.css);
+section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
+  color: #000;
+}
+
+/*********************************************
+ * GLOBAL STYLES
+ *********************************************/
+:root {
+  --r-background-color: #000000;
+  --r-main-font: Source Sans Pro, Helvetica, sans-serif;
+  --r-main-font-size: 42px;
+  --r-main-color: #fff;
+  --r-block-margin: 20px;
+  --r-heading-margin: 0 0 20px 0;
+  --r-heading-font: Source Sans Pro, Helvetica, sans-serif;
+  --r-heading-color: #fff;
+  --r-heading-line-height: 1.2;
+  --r-heading-letter-spacing: normal;
+  --r-heading-text-transform: uppercase;
+  --r-heading-text-shadow: none;
+  --r-heading-font-weight: 600;
+  --r-heading1-text-shadow: none;
+  --r-heading1-size: 2.5em;
+  --r-heading2-size: 1.6em;
+  --r-heading3-size: 1.3em;
+  --r-heading4-size: 1em;
+  --r-code-font: monospace;
+  --r-link-color: #42affa;
+  --r-link-color-dark: #068de9;
+  --r-link-color-hover: #8dcffc;
+  --r-selection-background-color: #bee4fd;
+  --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
+}
+
+.reveal-viewport {
+  background: #000000;
+  background-color: var(--r-background-color);
+}
+
+.reveal {
+  font-family: var(--r-main-font);
+  font-size: var(--r-main-font-size);
+  font-weight: normal;
+  color: var(--r-main-color);
+}
+
+.reveal ::selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal ::-moz-selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal .slides section,
+.reveal .slides section > section {
+  line-height: 1.3;
+  font-weight: inherit;
+}
+
+/*********************************************
+ * HEADERS
+ *********************************************/
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+  margin: var(--r-heading-margin);
+  color: var(--r-heading-color);
+  font-family: var(--r-heading-font);
+  font-weight: var(--r-heading-font-weight);
+  line-height: var(--r-heading-line-height);
+  letter-spacing: var(--r-heading-letter-spacing);
+  text-transform: var(--r-heading-text-transform);
+  text-shadow: var(--r-heading-text-shadow);
+  word-wrap: break-word;
+}
+
+.reveal h1 {
+  font-size: var(--r-heading1-size);
+}
+
+.reveal h2 {
+  font-size: var(--r-heading2-size);
+}
+
+.reveal h3 {
+  font-size: var(--r-heading3-size);
+}
+
+.reveal h4 {
+  font-size: var(--r-heading4-size);
+}
+
+.reveal h1 {
+  text-shadow: var(--r-heading1-text-shadow);
+}
+
+/*********************************************
+ * OTHER
+ *********************************************/
+.reveal p {
+  margin: var(--r-block-margin) 0;
+  line-height: 1.3;
+}
+
+/* Remove trailing margins after titles */
+.reveal h1:last-child,
+.reveal h2:last-child,
+.reveal h3:last-child,
+.reveal h4:last-child,
+.reveal h5:last-child,
+.reveal h6:last-child {
+  margin-bottom: 0;
+}
+
+/* Ensure certain elements are never larger than the slide itself */
+.reveal img,
+.reveal video,
+.reveal iframe {
+  max-width: 95%;
+  max-height: 95%;
+}
+
+.reveal strong,
+.reveal b {
+  font-weight: bold;
+}
+
+.reveal em {
+  font-style: italic;
+}
+
+.reveal ol,
+.reveal dl,
+.reveal ul {
+  display: inline-block;
+  text-align: left;
+  margin: 0 0 0 1em;
+}
+
+.reveal ol {
+  list-style-type: decimal;
+}
+
+.reveal ul {
+  list-style-type: disc;
+}
+
+.reveal ul ul {
+  list-style-type: square;
+}
+
+.reveal ul ul ul {
+  list-style-type: circle;
+}
+
+.reveal ul ul,
+.reveal ul ol,
+.reveal ol ol,
+.reveal ol ul {
+  display: block;
+  margin-left: 40px;
+}
+
+.reveal dt {
+  font-weight: bold;
+}
+
+.reveal dd {
+  margin-left: 40px;
+}
+
+.reveal blockquote {
+  display: block;
+  position: relative;
+  width: 70%;
+  margin: var(--r-block-margin) auto;
+  padding: 5px;
+  font-style: italic;
+  background: rgba(255, 255, 255, 0.05);
+  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
+}
+
+.reveal blockquote p:first-child,
+.reveal blockquote p:last-child {
+  display: inline-block;
+}
+
+.reveal q {
+  font-style: italic;
+}
+
+.reveal pre {
+  display: block;
+  position: relative;
+  width: 90%;
+  margin: var(--r-block-margin) auto;
+  text-align: left;
+  font-size: 0.55em;
+  font-family: var(--r-code-font);
+  line-height: 1.2em;
+  word-wrap: break-word;
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
+}
+
+.reveal code {
+  font-family: var(--r-code-font);
+  text-transform: none;
+  tab-size: 2;
+}
+
+.reveal pre code {
+  display: block;
+  padding: 5px;
+  overflow: auto;
+  max-height: 400px;
+  word-wrap: normal;
+}
+
+.reveal .code-wrapper {
+  white-space: normal;
+}
+
+.reveal .code-wrapper code {
+  white-space: pre;
+}
+
+.reveal table {
+  margin: auto;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+.reveal table th {
+  font-weight: bold;
+}
+
+.reveal table th,
+.reveal table td {
+  text-align: left;
+  padding: 0.2em 0.5em 0.2em 0.5em;
+  border-bottom: 1px solid;
+}
+
+.reveal table th[align=center],
+.reveal table td[align=center] {
+  text-align: center;
+}
+
+.reveal table th[align=right],
+.reveal table td[align=right] {
+  text-align: right;
+}
+
+.reveal table tbody tr:last-child th,
+.reveal table tbody tr:last-child td {
+  border-bottom: none;
+}
+
+.reveal sup {
+  vertical-align: super;
+  font-size: smaller;
+}
+
+.reveal sub {
+  vertical-align: sub;
+  font-size: smaller;
+}
+
+.reveal small {
+  display: inline-block;
+  font-size: 0.6em;
+  line-height: 1.2em;
+  vertical-align: top;
+}
+
+.reveal small * {
+  vertical-align: top;
+}
+
+.reveal img {
+  margin: var(--r-block-margin) 0;
+}
+
+/*********************************************
+ * LINKS
+ *********************************************/
+.reveal a {
+  color: var(--r-link-color);
+  text-decoration: none;
+  transition: color 0.15s ease;
+}
+
+.reveal a:hover {
+  color: var(--r-link-color-hover);
+  text-shadow: none;
+  border: none;
+}
+
+.reveal .roll span:after {
+  color: #fff;
+  background: var(--r-link-color-dark);
+}
+
+/*********************************************
+ * Frame helper
+ *********************************************/
+.reveal .r-frame {
+  border: 4px solid var(--r-main-color);
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+}
+
+.reveal a .r-frame {
+  transition: all 0.15s linear;
+}
+
+.reveal a:hover .r-frame {
+  border-color: var(--r-link-color);
+  box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
+}
+
+/*********************************************
+ * NAVIGATION CONTROLS
+ *********************************************/
+.reveal .controls {
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PROGRESS BAR
+ *********************************************/
+.reveal .progress {
+  background: rgba(0, 0, 0, 0.2);
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PRINT BACKGROUND
+ *********************************************/
+@media print {
+  .backgrounds {
+    background-color: var(--r-background-color);
+  }
+}

+ 3 - 1
dist/theme/black.css

@@ -34,8 +34,10 @@ section.has-light-background, section.has-light-background h1, section.has-light
   --r-link-color: #42affa;
   --r-link-color-dark: #068de9;
   --r-link-color-hover: #8dcffc;
-  --r-selection-background-color: #bee4fd;
+  --r-selection-background-color: rgba(66, 175, 250, 0.75);
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
 }
 
 .reveal-viewport {

+ 2 - 0
dist/theme/blood.css

@@ -42,6 +42,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
   --r-link-color-hover: #dd5566;
   --r-selection-background-color: #a23;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
 }
 
 .reveal-viewport {

+ 385 - 0
dist/theme/dracula.css

@@ -0,0 +1,385 @@
+/**
+ * Dracula Dark theme for reveal.js.
+ * Based on https://draculatheme.com
+ */
+/**
+ * Dracula colors by Zeno Rocha
+ * https://draculatheme.com/contribute
+ */
+html * {
+  color-profile: sRGB;
+  rendering-intent: auto;
+}
+
+section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
+  color: #282A36;
+}
+
+/*********************************************
+ * GLOBAL STYLES
+ *********************************************/
+:root {
+  --r-background-color: #282A36;
+  --r-main-font: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
+  --r-main-font-size: 40px;
+  --r-main-color: #F8F8F2;
+  --r-block-margin: 20px;
+  --r-heading-margin: 0 0 20px 0;
+  --r-heading-font: League Gothic, Impact, sans-serif;
+  --r-heading-color: #BD93F9;
+  --r-heading-line-height: 1.2;
+  --r-heading-letter-spacing: normal;
+  --r-heading-text-transform: none;
+  --r-heading-text-shadow: none;
+  --r-heading-font-weight: normal;
+  --r-heading1-text-shadow: none;
+  --r-heading1-size: 3.77em;
+  --r-heading2-size: 2.11em;
+  --r-heading3-size: 1.55em;
+  --r-heading4-size: 1em;
+  --r-code-font: Fira Code, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
+  --r-link-color: #FF79C6;
+  --r-link-color-dark: #ff2da5;
+  --r-link-color-hover: #8BE9FD;
+  --r-selection-background-color: #44475A;
+  --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
+}
+
+.reveal-viewport {
+  background: #282A36;
+  background-color: var(--r-background-color);
+}
+
+.reveal {
+  font-family: var(--r-main-font);
+  font-size: var(--r-main-font-size);
+  font-weight: normal;
+  color: var(--r-main-color);
+}
+
+.reveal ::selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal ::-moz-selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal .slides section,
+.reveal .slides section > section {
+  line-height: 1.3;
+  font-weight: inherit;
+}
+
+/*********************************************
+ * HEADERS
+ *********************************************/
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+  margin: var(--r-heading-margin);
+  color: var(--r-heading-color);
+  font-family: var(--r-heading-font);
+  font-weight: var(--r-heading-font-weight);
+  line-height: var(--r-heading-line-height);
+  letter-spacing: var(--r-heading-letter-spacing);
+  text-transform: var(--r-heading-text-transform);
+  text-shadow: var(--r-heading-text-shadow);
+  word-wrap: break-word;
+}
+
+.reveal h1 {
+  font-size: var(--r-heading1-size);
+}
+
+.reveal h2 {
+  font-size: var(--r-heading2-size);
+}
+
+.reveal h3 {
+  font-size: var(--r-heading3-size);
+}
+
+.reveal h4 {
+  font-size: var(--r-heading4-size);
+}
+
+.reveal h1 {
+  text-shadow: var(--r-heading1-text-shadow);
+}
+
+/*********************************************
+ * OTHER
+ *********************************************/
+.reveal p {
+  margin: var(--r-block-margin) 0;
+  line-height: 1.3;
+}
+
+/* Remove trailing margins after titles */
+.reveal h1:last-child,
+.reveal h2:last-child,
+.reveal h3:last-child,
+.reveal h4:last-child,
+.reveal h5:last-child,
+.reveal h6:last-child {
+  margin-bottom: 0;
+}
+
+/* Ensure certain elements are never larger than the slide itself */
+.reveal img,
+.reveal video,
+.reveal iframe {
+  max-width: 95%;
+  max-height: 95%;
+}
+
+.reveal strong,
+.reveal b {
+  font-weight: bold;
+}
+
+.reveal em {
+  font-style: italic;
+}
+
+.reveal ol,
+.reveal dl,
+.reveal ul {
+  display: inline-block;
+  text-align: left;
+  margin: 0 0 0 1em;
+}
+
+.reveal ol {
+  list-style-type: decimal;
+}
+
+.reveal ul {
+  list-style-type: disc;
+}
+
+.reveal ul ul {
+  list-style-type: square;
+}
+
+.reveal ul ul ul {
+  list-style-type: circle;
+}
+
+.reveal ul ul,
+.reveal ul ol,
+.reveal ol ol,
+.reveal ol ul {
+  display: block;
+  margin-left: 40px;
+}
+
+.reveal dt {
+  font-weight: bold;
+}
+
+.reveal dd {
+  margin-left: 40px;
+}
+
+.reveal blockquote {
+  display: block;
+  position: relative;
+  width: 70%;
+  margin: var(--r-block-margin) auto;
+  padding: 5px;
+  font-style: italic;
+  background: rgba(255, 255, 255, 0.05);
+  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
+}
+
+.reveal blockquote p:first-child,
+.reveal blockquote p:last-child {
+  display: inline-block;
+}
+
+.reveal q {
+  font-style: italic;
+}
+
+.reveal pre {
+  display: block;
+  position: relative;
+  width: 90%;
+  margin: var(--r-block-margin) auto;
+  text-align: left;
+  font-size: 0.55em;
+  font-family: var(--r-code-font);
+  line-height: 1.2em;
+  word-wrap: break-word;
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
+}
+
+.reveal code {
+  font-family: var(--r-code-font);
+  text-transform: none;
+  tab-size: 2;
+}
+
+.reveal pre code {
+  display: block;
+  padding: 5px;
+  overflow: auto;
+  max-height: 400px;
+  word-wrap: normal;
+}
+
+.reveal .code-wrapper {
+  white-space: normal;
+}
+
+.reveal .code-wrapper code {
+  white-space: pre;
+}
+
+.reveal table {
+  margin: auto;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+.reveal table th {
+  font-weight: bold;
+}
+
+.reveal table th,
+.reveal table td {
+  text-align: left;
+  padding: 0.2em 0.5em 0.2em 0.5em;
+  border-bottom: 1px solid;
+}
+
+.reveal table th[align=center],
+.reveal table td[align=center] {
+  text-align: center;
+}
+
+.reveal table th[align=right],
+.reveal table td[align=right] {
+  text-align: right;
+}
+
+.reveal table tbody tr:last-child th,
+.reveal table tbody tr:last-child td {
+  border-bottom: none;
+}
+
+.reveal sup {
+  vertical-align: super;
+  font-size: smaller;
+}
+
+.reveal sub {
+  vertical-align: sub;
+  font-size: smaller;
+}
+
+.reveal small {
+  display: inline-block;
+  font-size: 0.6em;
+  line-height: 1.2em;
+  vertical-align: top;
+}
+
+.reveal small * {
+  vertical-align: top;
+}
+
+.reveal img {
+  margin: var(--r-block-margin) 0;
+}
+
+/*********************************************
+ * LINKS
+ *********************************************/
+.reveal a {
+  color: var(--r-link-color);
+  text-decoration: none;
+  transition: color 0.15s ease;
+}
+
+.reveal a:hover {
+  color: var(--r-link-color-hover);
+  text-shadow: none;
+  border: none;
+}
+
+.reveal .roll span:after {
+  color: #fff;
+  background: var(--r-link-color-dark);
+}
+
+/*********************************************
+ * Frame helper
+ *********************************************/
+.reveal .r-frame {
+  border: 4px solid var(--r-main-color);
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+}
+
+.reveal a .r-frame {
+  transition: all 0.15s linear;
+}
+
+.reveal a:hover .r-frame {
+  border-color: var(--r-link-color);
+  box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
+}
+
+/*********************************************
+ * NAVIGATION CONTROLS
+ *********************************************/
+.reveal .controls {
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PROGRESS BAR
+ *********************************************/
+.reveal .progress {
+  background: rgba(0, 0, 0, 0.2);
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PRINT BACKGROUND
+ *********************************************/
+@media print {
+  .backgrounds {
+    background-color: var(--r-background-color);
+  }
+}
+:root {
+  --r-bold-color: #FFB86C;
+  --r-italic-color: #F1FA8C;
+  --r-inline-code-color: #50FA7B;
+  --r-list-bullet-color: #8BE9FD;
+}
+
+.reveal strong, .reveal b {
+  color: var(--r-bold-color);
+}
+.reveal em, .reveal i, .reveal blockquote {
+  color: var(--r-italic-color);
+}
+.reveal code {
+  color: var(--r-inline-code-color);
+}
+.reveal ul li::marker, .reveal ol li::marker {
+  color: var(--r-list-bullet-color);
+}

+ 9 - 7
dist/theme/league.css

@@ -39,16 +39,18 @@ section.has-light-background, section.has-light-background h1, section.has-light
   --r-link-color-hover: #71e9f4;
   --r-selection-background-color: #FF5E99;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
 }
 
 .reveal-viewport {
-  background: #1c1e20;
-  background: -moz-radial-gradient(center, circle cover, #555a5f 0%, #1c1e20 100%);
-  background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%, #555a5f), color-stop(100%, #1c1e20));
-  background: -webkit-radial-gradient(center, circle cover, #555a5f 0%, #1c1e20 100%);
-  background: -o-radial-gradient(center, circle cover, #555a5f 0%, #1c1e20 100%);
-  background: -ms-radial-gradient(center, circle cover, #555a5f 0%, #1c1e20 100%);
-  background: radial-gradient(center, circle cover, #555a5f 0%, #1c1e20 100%);
+  background: rgb(28, 30, 32);
+  background: -moz-radial-gradient(center, circle cover, rgb(85, 90, 95) 0%, rgb(28, 30, 32) 100%);
+  background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%, rgb(85, 90, 95)), color-stop(100%, rgb(28, 30, 32)));
+  background: -webkit-radial-gradient(center, circle cover, rgb(85, 90, 95) 0%, rgb(28, 30, 32) 100%);
+  background: -o-radial-gradient(center, circle cover, rgb(85, 90, 95) 0%, rgb(28, 30, 32) 100%);
+  background: -ms-radial-gradient(center, circle cover, rgb(85, 90, 95) 0%, rgb(28, 30, 32) 100%);
+  background: radial-gradient(center, circle cover, rgb(85, 90, 95) 0%, rgb(28, 30, 32) 100%);
   background-color: var(--r-background-color);
 }
 

+ 2 - 5
dist/theme/moon.css

@@ -7,11 +7,6 @@
 /**
  * Solarized colors by Ethan Schoonover
  */
-html * {
-  color-profile: sRGB;
-  rendering-intent: auto;
-}
-
 section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
   color: #222;
 }
@@ -44,6 +39,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
   --r-link-color-hover: #78b9e6;
   --r-selection-background-color: #d33682;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
 }
 
 .reveal-viewport {

+ 2 - 0
dist/theme/night.css

@@ -37,6 +37,8 @@ section.has-light-background, section.has-light-background h1, section.has-light
   --r-link-color-hover: #f3d7ac;
   --r-selection-background-color: #e7ad52;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 240, 240, 240;
+  --r-overlay-element-fg-color: 0, 0, 0;
 }
 
 .reveal-viewport {

+ 2 - 0
dist/theme/serif.css

@@ -40,6 +40,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
   --r-link-color-hover: #8b7c69;
   --r-selection-background-color: #26351C;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
 }
 
 .reveal-viewport {

+ 2 - 0
dist/theme/simple.css

@@ -39,6 +39,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
   --r-link-color-hover: #0000f1;
   --r-selection-background-color: rgba(0, 0, 0, 0.99);
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
 }
 
 .reveal-viewport {

+ 2 - 0
dist/theme/sky.css

@@ -41,6 +41,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
   --r-link-color-hover: #74a7cb;
   --r-selection-background-color: #134674;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
 }
 
 .reveal-viewport {

+ 2 - 0
dist/theme/solarized.css

@@ -40,6 +40,8 @@ html * {
   --r-link-color-hover: #78b9e6;
   --r-selection-background-color: #d33682;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
 }
 
 .reveal-viewport {

+ 362 - 0
dist/theme/white-contrast.css

@@ -0,0 +1,362 @@
+/**
+ * White compact & high contrast reveal.js theme, with headers not in capitals.
+ *
+ * By Peter Kehl. Based on white.(s)css by Hakim El Hattab, http://hakim.se
+ *
+ * - Keep the source similar to black.css - for easy comparison.
+ * - $mainFontSize controls code blocks, too (although under some ratio).
+ */
+@import url(./fonts/source-sans-pro/source-sans-pro.css);
+section.has-dark-background, section.has-dark-background h1, section.has-dark-background h2, section.has-dark-background h3, section.has-dark-background h4, section.has-dark-background h5, section.has-dark-background h6 {
+  color: #fff;
+}
+
+/*********************************************
+ * GLOBAL STYLES
+ *********************************************/
+:root {
+  --r-background-color: #fff;
+  --r-main-font: Source Sans Pro, Helvetica, sans-serif;
+  --r-main-font-size: 42px;
+  --r-main-color: #000;
+  --r-block-margin: 20px;
+  --r-heading-margin: 0 0 20px 0;
+  --r-heading-font: Source Sans Pro, Helvetica, sans-serif;
+  --r-heading-color: #000;
+  --r-heading-line-height: 1.2;
+  --r-heading-letter-spacing: normal;
+  --r-heading-text-transform: uppercase;
+  --r-heading-text-shadow: none;
+  --r-heading-font-weight: 600;
+  --r-heading1-text-shadow: none;
+  --r-heading1-size: 2.5em;
+  --r-heading2-size: 1.6em;
+  --r-heading3-size: 1.3em;
+  --r-heading4-size: 1em;
+  --r-code-font: monospace;
+  --r-link-color: #2a76dd;
+  --r-link-color-dark: #1a53a1;
+  --r-link-color-hover: #6ca0e8;
+  --r-selection-background-color: #98bdef;
+  --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
+}
+
+.reveal-viewport {
+  background: #fff;
+  background-color: var(--r-background-color);
+}
+
+.reveal {
+  font-family: var(--r-main-font);
+  font-size: var(--r-main-font-size);
+  font-weight: normal;
+  color: var(--r-main-color);
+}
+
+.reveal ::selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal ::-moz-selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal .slides section,
+.reveal .slides section > section {
+  line-height: 1.3;
+  font-weight: inherit;
+}
+
+/*********************************************
+ * HEADERS
+ *********************************************/
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+  margin: var(--r-heading-margin);
+  color: var(--r-heading-color);
+  font-family: var(--r-heading-font);
+  font-weight: var(--r-heading-font-weight);
+  line-height: var(--r-heading-line-height);
+  letter-spacing: var(--r-heading-letter-spacing);
+  text-transform: var(--r-heading-text-transform);
+  text-shadow: var(--r-heading-text-shadow);
+  word-wrap: break-word;
+}
+
+.reveal h1 {
+  font-size: var(--r-heading1-size);
+}
+
+.reveal h2 {
+  font-size: var(--r-heading2-size);
+}
+
+.reveal h3 {
+  font-size: var(--r-heading3-size);
+}
+
+.reveal h4 {
+  font-size: var(--r-heading4-size);
+}
+
+.reveal h1 {
+  text-shadow: var(--r-heading1-text-shadow);
+}
+
+/*********************************************
+ * OTHER
+ *********************************************/
+.reveal p {
+  margin: var(--r-block-margin) 0;
+  line-height: 1.3;
+}
+
+/* Remove trailing margins after titles */
+.reveal h1:last-child,
+.reveal h2:last-child,
+.reveal h3:last-child,
+.reveal h4:last-child,
+.reveal h5:last-child,
+.reveal h6:last-child {
+  margin-bottom: 0;
+}
+
+/* Ensure certain elements are never larger than the slide itself */
+.reveal img,
+.reveal video,
+.reveal iframe {
+  max-width: 95%;
+  max-height: 95%;
+}
+
+.reveal strong,
+.reveal b {
+  font-weight: bold;
+}
+
+.reveal em {
+  font-style: italic;
+}
+
+.reveal ol,
+.reveal dl,
+.reveal ul {
+  display: inline-block;
+  text-align: left;
+  margin: 0 0 0 1em;
+}
+
+.reveal ol {
+  list-style-type: decimal;
+}
+
+.reveal ul {
+  list-style-type: disc;
+}
+
+.reveal ul ul {
+  list-style-type: square;
+}
+
+.reveal ul ul ul {
+  list-style-type: circle;
+}
+
+.reveal ul ul,
+.reveal ul ol,
+.reveal ol ol,
+.reveal ol ul {
+  display: block;
+  margin-left: 40px;
+}
+
+.reveal dt {
+  font-weight: bold;
+}
+
+.reveal dd {
+  margin-left: 40px;
+}
+
+.reveal blockquote {
+  display: block;
+  position: relative;
+  width: 70%;
+  margin: var(--r-block-margin) auto;
+  padding: 5px;
+  font-style: italic;
+  background: rgba(255, 255, 255, 0.05);
+  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
+}
+
+.reveal blockquote p:first-child,
+.reveal blockquote p:last-child {
+  display: inline-block;
+}
+
+.reveal q {
+  font-style: italic;
+}
+
+.reveal pre {
+  display: block;
+  position: relative;
+  width: 90%;
+  margin: var(--r-block-margin) auto;
+  text-align: left;
+  font-size: 0.55em;
+  font-family: var(--r-code-font);
+  line-height: 1.2em;
+  word-wrap: break-word;
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
+}
+
+.reveal code {
+  font-family: var(--r-code-font);
+  text-transform: none;
+  tab-size: 2;
+}
+
+.reveal pre code {
+  display: block;
+  padding: 5px;
+  overflow: auto;
+  max-height: 400px;
+  word-wrap: normal;
+}
+
+.reveal .code-wrapper {
+  white-space: normal;
+}
+
+.reveal .code-wrapper code {
+  white-space: pre;
+}
+
+.reveal table {
+  margin: auto;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+.reveal table th {
+  font-weight: bold;
+}
+
+.reveal table th,
+.reveal table td {
+  text-align: left;
+  padding: 0.2em 0.5em 0.2em 0.5em;
+  border-bottom: 1px solid;
+}
+
+.reveal table th[align=center],
+.reveal table td[align=center] {
+  text-align: center;
+}
+
+.reveal table th[align=right],
+.reveal table td[align=right] {
+  text-align: right;
+}
+
+.reveal table tbody tr:last-child th,
+.reveal table tbody tr:last-child td {
+  border-bottom: none;
+}
+
+.reveal sup {
+  vertical-align: super;
+  font-size: smaller;
+}
+
+.reveal sub {
+  vertical-align: sub;
+  font-size: smaller;
+}
+
+.reveal small {
+  display: inline-block;
+  font-size: 0.6em;
+  line-height: 1.2em;
+  vertical-align: top;
+}
+
+.reveal small * {
+  vertical-align: top;
+}
+
+.reveal img {
+  margin: var(--r-block-margin) 0;
+}
+
+/*********************************************
+ * LINKS
+ *********************************************/
+.reveal a {
+  color: var(--r-link-color);
+  text-decoration: none;
+  transition: color 0.15s ease;
+}
+
+.reveal a:hover {
+  color: var(--r-link-color-hover);
+  text-shadow: none;
+  border: none;
+}
+
+.reveal .roll span:after {
+  color: #fff;
+  background: var(--r-link-color-dark);
+}
+
+/*********************************************
+ * Frame helper
+ *********************************************/
+.reveal .r-frame {
+  border: 4px solid var(--r-main-color);
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+}
+
+.reveal a .r-frame {
+  transition: all 0.15s linear;
+}
+
+.reveal a:hover .r-frame {
+  border-color: var(--r-link-color);
+  box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
+}
+
+/*********************************************
+ * NAVIGATION CONTROLS
+ *********************************************/
+.reveal .controls {
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PROGRESS BAR
+ *********************************************/
+.reveal .progress {
+  background: rgba(0, 0, 0, 0.2);
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PRINT BACKGROUND
+ *********************************************/
+@media print {
+  .backgrounds {
+    background-color: var(--r-background-color);
+  }
+}

+ 2 - 0
dist/theme/white.css

@@ -36,6 +36,8 @@ section.has-dark-background, section.has-dark-background h1, section.has-dark-ba
   --r-link-color-hover: #6ca0e8;
   --r-selection-background-color: #98bdef;
   --r-selection-color: #fff;
+  --r-overlay-element-bg-color: 0, 0, 0;
+  --r-overlay-element-fg-color: 240, 240, 240;
 }
 
 .reveal-viewport {

+ 360 - 0
dist/theme/white_contrast_compact_verbatim_headers.css

@@ -0,0 +1,360 @@
+/**
+ * White compact & high contrast reveal.js theme, with headers not in capitals.
+ *
+ * By Peter Kehl. Based on white.(s)css by Hakim El Hattab, http://hakim.se
+ *
+ * - Keep the source similar to black.css - for easy comparison.
+ * - $mainFontSize controls code blocks, too (although under some ratio).
+ */
+@import url(./fonts/source-sans-pro/source-sans-pro.css);
+section.has-dark-background, section.has-dark-background h1, section.has-dark-background h2, section.has-dark-background h3, section.has-dark-background h4, section.has-dark-background h5, section.has-dark-background h6 {
+  color: #fff;
+}
+
+/*********************************************
+ * GLOBAL STYLES
+ *********************************************/
+:root {
+  --r-background-color: #fff;
+  --r-main-font: Source Sans Pro, Helvetica, sans-serif;
+  --r-main-font-size: 25px;
+  --r-main-color: #000;
+  --r-block-margin: 20px;
+  --r-heading-margin: 0 0 20px 0;
+  --r-heading-font: Source Sans Pro, Helvetica, sans-serif;
+  --r-heading-color: #000;
+  --r-heading-line-height: 1.2;
+  --r-heading-letter-spacing: normal;
+  --r-heading-text-transform: none;
+  --r-heading-text-shadow: none;
+  --r-heading-font-weight: 450;
+  --r-heading1-text-shadow: none;
+  --r-heading1-size: 2.5em;
+  --r-heading2-size: 1.6em;
+  --r-heading3-size: 1.3em;
+  --r-heading4-size: 1em;
+  --r-code-font: monospace;
+  --r-link-color: #2a76dd;
+  --r-link-color-dark: #1a53a1;
+  --r-link-color-hover: #6ca0e8;
+  --r-selection-background-color: #98bdef;
+  --r-selection-color: #fff;
+}
+
+.reveal-viewport {
+  background: #fff;
+  background-color: var(--r-background-color);
+}
+
+.reveal {
+  font-family: var(--r-main-font);
+  font-size: var(--r-main-font-size);
+  font-weight: normal;
+  color: var(--r-main-color);
+}
+
+.reveal ::selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal ::-moz-selection {
+  color: var(--r-selection-color);
+  background: var(--r-selection-background-color);
+  text-shadow: none;
+}
+
+.reveal .slides section,
+.reveal .slides section > section {
+  line-height: 1.3;
+  font-weight: inherit;
+}
+
+/*********************************************
+ * HEADERS
+ *********************************************/
+.reveal h1,
+.reveal h2,
+.reveal h3,
+.reveal h4,
+.reveal h5,
+.reveal h6 {
+  margin: var(--r-heading-margin);
+  color: var(--r-heading-color);
+  font-family: var(--r-heading-font);
+  font-weight: var(--r-heading-font-weight);
+  line-height: var(--r-heading-line-height);
+  letter-spacing: var(--r-heading-letter-spacing);
+  text-transform: var(--r-heading-text-transform);
+  text-shadow: var(--r-heading-text-shadow);
+  word-wrap: break-word;
+}
+
+.reveal h1 {
+  font-size: var(--r-heading1-size);
+}
+
+.reveal h2 {
+  font-size: var(--r-heading2-size);
+}
+
+.reveal h3 {
+  font-size: var(--r-heading3-size);
+}
+
+.reveal h4 {
+  font-size: var(--r-heading4-size);
+}
+
+.reveal h1 {
+  text-shadow: var(--r-heading1-text-shadow);
+}
+
+/*********************************************
+ * OTHER
+ *********************************************/
+.reveal p {
+  margin: var(--r-block-margin) 0;
+  line-height: 1.3;
+}
+
+/* Remove trailing margins after titles */
+.reveal h1:last-child,
+.reveal h2:last-child,
+.reveal h3:last-child,
+.reveal h4:last-child,
+.reveal h5:last-child,
+.reveal h6:last-child {
+  margin-bottom: 0;
+}
+
+/* Ensure certain elements are never larger than the slide itself */
+.reveal img,
+.reveal video,
+.reveal iframe {
+  max-width: 95%;
+  max-height: 95%;
+}
+
+.reveal strong,
+.reveal b {
+  font-weight: bold;
+}
+
+.reveal em {
+  font-style: italic;
+}
+
+.reveal ol,
+.reveal dl,
+.reveal ul {
+  display: inline-block;
+  text-align: left;
+  margin: 0 0 0 1em;
+}
+
+.reveal ol {
+  list-style-type: decimal;
+}
+
+.reveal ul {
+  list-style-type: disc;
+}
+
+.reveal ul ul {
+  list-style-type: square;
+}
+
+.reveal ul ul ul {
+  list-style-type: circle;
+}
+
+.reveal ul ul,
+.reveal ul ol,
+.reveal ol ol,
+.reveal ol ul {
+  display: block;
+  margin-left: 40px;
+}
+
+.reveal dt {
+  font-weight: bold;
+}
+
+.reveal dd {
+  margin-left: 40px;
+}
+
+.reveal blockquote {
+  display: block;
+  position: relative;
+  width: 70%;
+  margin: var(--r-block-margin) auto;
+  padding: 5px;
+  font-style: italic;
+  background: rgba(255, 255, 255, 0.05);
+  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2);
+}
+
+.reveal blockquote p:first-child,
+.reveal blockquote p:last-child {
+  display: inline-block;
+}
+
+.reveal q {
+  font-style: italic;
+}
+
+.reveal pre {
+  display: block;
+  position: relative;
+  width: 90%;
+  margin: var(--r-block-margin) auto;
+  text-align: left;
+  font-size: 0.55em;
+  font-family: var(--r-code-font);
+  line-height: 1.2em;
+  word-wrap: break-word;
+  box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
+}
+
+.reveal code {
+  font-family: var(--r-code-font);
+  text-transform: none;
+  tab-size: 2;
+}
+
+.reveal pre code {
+  display: block;
+  padding: 5px;
+  overflow: auto;
+  max-height: 400px;
+  word-wrap: normal;
+}
+
+.reveal .code-wrapper {
+  white-space: normal;
+}
+
+.reveal .code-wrapper code {
+  white-space: pre;
+}
+
+.reveal table {
+  margin: auto;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+.reveal table th {
+  font-weight: bold;
+}
+
+.reveal table th,
+.reveal table td {
+  text-align: left;
+  padding: 0.2em 0.5em 0.2em 0.5em;
+  border-bottom: 1px solid;
+}
+
+.reveal table th[align=center],
+.reveal table td[align=center] {
+  text-align: center;
+}
+
+.reveal table th[align=right],
+.reveal table td[align=right] {
+  text-align: right;
+}
+
+.reveal table tbody tr:last-child th,
+.reveal table tbody tr:last-child td {
+  border-bottom: none;
+}
+
+.reveal sup {
+  vertical-align: super;
+  font-size: smaller;
+}
+
+.reveal sub {
+  vertical-align: sub;
+  font-size: smaller;
+}
+
+.reveal small {
+  display: inline-block;
+  font-size: 0.6em;
+  line-height: 1.2em;
+  vertical-align: top;
+}
+
+.reveal small * {
+  vertical-align: top;
+}
+
+.reveal img {
+  margin: var(--r-block-margin) 0;
+}
+
+/*********************************************
+ * LINKS
+ *********************************************/
+.reveal a {
+  color: var(--r-link-color);
+  text-decoration: none;
+  transition: color 0.15s ease;
+}
+
+.reveal a:hover {
+  color: var(--r-link-color-hover);
+  text-shadow: none;
+  border: none;
+}
+
+.reveal .roll span:after {
+  color: #fff;
+  background: var(--r-link-color-dark);
+}
+
+/*********************************************
+ * Frame helper
+ *********************************************/
+.reveal .r-frame {
+  border: 4px solid var(--r-main-color);
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
+}
+
+.reveal a .r-frame {
+  transition: all 0.15s linear;
+}
+
+.reveal a:hover .r-frame {
+  border-color: var(--r-link-color);
+  box-shadow: 0 0 20px rgba(0, 0, 0, 0.55);
+}
+
+/*********************************************
+ * NAVIGATION CONTROLS
+ *********************************************/
+.reveal .controls {
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PROGRESS BAR
+ *********************************************/
+.reveal .progress {
+  background: rgba(0, 0, 0, 0.2);
+  color: var(--r-link-color);
+}
+
+/*********************************************
+ * PRINT BACKGROUND
+ *********************************************/
+@media print {
+  .backgrounds {
+    background-color: var(--r-background-color);
+  }
+}

+ 526 - 0
examples/500-slides.html

@@ -0,0 +1,526 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - 500 slides</title>
+
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+
+		<link rel="stylesheet" href="../dist/reveal.css">
+		<link rel="stylesheet" href="../dist/theme/black.css">
+	</head>
+
+	<body>
+		<div class="reveal">
+			<div class="slides">
+				<section><h1>1</h1></section>
+				<section><h1>2</h1></section>
+				<section><h1>3</h1></section>
+				<section><h1>4</h1></section>
+				<section><h1>5</h1></section>
+				<section><h1>6</h1></section>
+				<section><h1>7</h1></section>
+				<section><h1>8</h1></section>
+				<section><h1>9</h1></section>
+				<section><h1>10</h1></section>
+				<section><h1>11</h1></section>
+				<section><h1>12</h1></section>
+				<section><h1>13</h1></section>
+				<section><h1>14</h1></section>
+				<section><h1>15</h1></section>
+				<section><h1>16</h1></section>
+				<section><h1>17</h1></section>
+				<section><h1>18</h1></section>
+				<section><h1>19</h1></section>
+				<section><h1>20</h1></section>
+				<section><h1>21</h1></section>
+				<section><h1>22</h1></section>
+				<section><h1>23</h1></section>
+				<section><h1>24</h1></section>
+				<section><h1>25</h1></section>
+				<section><h1>26</h1></section>
+				<section><h1>27</h1></section>
+				<section><h1>28</h1></section>
+				<section><h1>29</h1></section>
+				<section><h1>30</h1></section>
+				<section><h1>31</h1></section>
+				<section><h1>32</h1></section>
+				<section><h1>33</h1></section>
+				<section><h1>34</h1></section>
+				<section><h1>35</h1></section>
+				<section><h1>36</h1></section>
+				<section><h1>37</h1></section>
+				<section><h1>38</h1></section>
+				<section><h1>39</h1></section>
+				<section><h1>40</h1></section>
+				<section><h1>41</h1></section>
+				<section><h1>42</h1></section>
+				<section><h1>43</h1></section>
+				<section><h1>44</h1></section>
+				<section><h1>45</h1></section>
+				<section><h1>46</h1></section>
+				<section><h1>47</h1></section>
+				<section><h1>48</h1></section>
+				<section><h1>49</h1></section>
+				<section><h1>50</h1></section>
+				<section><h1>51</h1></section>
+				<section><h1>52</h1></section>
+				<section><h1>53</h1></section>
+				<section><h1>54</h1></section>
+				<section><h1>55</h1></section>
+				<section><h1>56</h1></section>
+				<section><h1>57</h1></section>
+				<section><h1>58</h1></section>
+				<section><h1>59</h1></section>
+				<section><h1>60</h1></section>
+				<section><h1>61</h1></section>
+				<section><h1>62</h1></section>
+				<section><h1>63</h1></section>
+				<section><h1>64</h1></section>
+				<section><h1>65</h1></section>
+				<section><h1>66</h1></section>
+				<section><h1>67</h1></section>
+				<section><h1>68</h1></section>
+				<section><h1>69</h1></section>
+				<section><h1>70</h1></section>
+				<section><h1>71</h1></section>
+				<section><h1>72</h1></section>
+				<section><h1>73</h1></section>
+				<section><h1>74</h1></section>
+				<section><h1>75</h1></section>
+				<section><h1>76</h1></section>
+				<section><h1>77</h1></section>
+				<section><h1>78</h1></section>
+				<section><h1>79</h1></section>
+				<section><h1>80</h1></section>
+				<section><h1>81</h1></section>
+				<section><h1>82</h1></section>
+				<section><h1>83</h1></section>
+				<section><h1>84</h1></section>
+				<section><h1>85</h1></section>
+				<section><h1>86</h1></section>
+				<section><h1>87</h1></section>
+				<section><h1>88</h1></section>
+				<section><h1>89</h1></section>
+				<section><h1>90</h1></section>
+				<section><h1>91</h1></section>
+				<section><h1>92</h1></section>
+				<section><h1>93</h1></section>
+				<section><h1>94</h1></section>
+				<section><h1>95</h1></section>
+				<section><h1>96</h1></section>
+				<section><h1>97</h1></section>
+				<section><h1>98</h1></section>
+				<section><h1>99</h1></section>
+				<section><h1>100</h1></section>
+				<section><h1>101</h1></section>
+				<section><h1>102</h1></section>
+				<section><h1>103</h1></section>
+				<section><h1>104</h1></section>
+				<section><h1>105</h1></section>
+				<section><h1>106</h1></section>
+				<section><h1>107</h1></section>
+				<section><h1>108</h1></section>
+				<section><h1>109</h1></section>
+				<section><h1>110</h1></section>
+				<section><h1>111</h1></section>
+				<section><h1>112</h1></section>
+				<section><h1>113</h1></section>
+				<section><h1>114</h1></section>
+				<section><h1>115</h1></section>
+				<section><h1>116</h1></section>
+				<section><h1>117</h1></section>
+				<section><h1>118</h1></section>
+				<section><h1>119</h1></section>
+				<section><h1>120</h1></section>
+				<section><h1>121</h1></section>
+				<section><h1>122</h1></section>
+				<section><h1>123</h1></section>
+				<section><h1>124</h1></section>
+				<section><h1>125</h1></section>
+				<section><h1>126</h1></section>
+				<section><h1>127</h1></section>
+				<section><h1>128</h1></section>
+				<section><h1>129</h1></section>
+				<section><h1>130</h1></section>
+				<section><h1>131</h1></section>
+				<section><h1>132</h1></section>
+				<section><h1>133</h1></section>
+				<section><h1>134</h1></section>
+				<section><h1>135</h1></section>
+				<section><h1>136</h1></section>
+				<section><h1>137</h1></section>
+				<section><h1>138</h1></section>
+				<section><h1>139</h1></section>
+				<section><h1>140</h1></section>
+				<section><h1>141</h1></section>
+				<section><h1>142</h1></section>
+				<section><h1>143</h1></section>
+				<section><h1>144</h1></section>
+				<section><h1>145</h1></section>
+				<section><h1>146</h1></section>
+				<section><h1>147</h1></section>
+				<section><h1>148</h1></section>
+				<section><h1>149</h1></section>
+				<section><h1>150</h1></section>
+				<section><h1>151</h1></section>
+				<section><h1>152</h1></section>
+				<section><h1>153</h1></section>
+				<section><h1>154</h1></section>
+				<section><h1>155</h1></section>
+				<section><h1>156</h1></section>
+				<section><h1>157</h1></section>
+				<section><h1>158</h1></section>
+				<section><h1>159</h1></section>
+				<section><h1>160</h1></section>
+				<section><h1>161</h1></section>
+				<section><h1>162</h1></section>
+				<section><h1>163</h1></section>
+				<section><h1>164</h1></section>
+				<section><h1>165</h1></section>
+				<section><h1>166</h1></section>
+				<section><h1>167</h1></section>
+				<section><h1>168</h1></section>
+				<section><h1>169</h1></section>
+				<section><h1>170</h1></section>
+				<section><h1>171</h1></section>
+				<section><h1>172</h1></section>
+				<section><h1>173</h1></section>
+				<section><h1>174</h1></section>
+				<section><h1>175</h1></section>
+				<section><h1>176</h1></section>
+				<section><h1>177</h1></section>
+				<section><h1>178</h1></section>
+				<section><h1>179</h1></section>
+				<section><h1>180</h1></section>
+				<section><h1>181</h1></section>
+				<section><h1>182</h1></section>
+				<section><h1>183</h1></section>
+				<section><h1>184</h1></section>
+				<section><h1>185</h1></section>
+				<section><h1>186</h1></section>
+				<section><h1>187</h1></section>
+				<section><h1>188</h1></section>
+				<section><h1>189</h1></section>
+				<section><h1>190</h1></section>
+				<section><h1>191</h1></section>
+				<section><h1>192</h1></section>
+				<section><h1>193</h1></section>
+				<section><h1>194</h1></section>
+				<section><h1>195</h1></section>
+				<section><h1>196</h1></section>
+				<section><h1>197</h1></section>
+				<section><h1>198</h1></section>
+				<section><h1>199</h1></section>
+				<section><h1>200</h1></section>
+				<section><h1>201</h1></section>
+				<section><h1>202</h1></section>
+				<section><h1>203</h1></section>
+				<section><h1>204</h1></section>
+				<section><h1>205</h1></section>
+				<section><h1>206</h1></section>
+				<section><h1>207</h1></section>
+				<section><h1>208</h1></section>
+				<section><h1>209</h1></section>
+				<section><h1>210</h1></section>
+				<section><h1>211</h1></section>
+				<section><h1>212</h1></section>
+				<section><h1>213</h1></section>
+				<section><h1>214</h1></section>
+				<section><h1>215</h1></section>
+				<section><h1>216</h1></section>
+				<section><h1>217</h1></section>
+				<section><h1>218</h1></section>
+				<section><h1>219</h1></section>
+				<section><h1>220</h1></section>
+				<section><h1>221</h1></section>
+				<section><h1>222</h1></section>
+				<section><h1>223</h1></section>
+				<section><h1>224</h1></section>
+				<section><h1>225</h1></section>
+				<section><h1>226</h1></section>
+				<section><h1>227</h1></section>
+				<section><h1>228</h1></section>
+				<section><h1>229</h1></section>
+				<section><h1>230</h1></section>
+				<section><h1>231</h1></section>
+				<section><h1>232</h1></section>
+				<section><h1>233</h1></section>
+				<section><h1>234</h1></section>
+				<section><h1>235</h1></section>
+				<section><h1>236</h1></section>
+				<section><h1>237</h1></section>
+				<section><h1>238</h1></section>
+				<section><h1>239</h1></section>
+				<section><h1>240</h1></section>
+				<section><h1>241</h1></section>
+				<section><h1>242</h1></section>
+				<section><h1>243</h1></section>
+				<section><h1>244</h1></section>
+				<section><h1>245</h1></section>
+				<section><h1>246</h1></section>
+				<section><h1>247</h1></section>
+				<section><h1>248</h1></section>
+				<section><h1>249</h1></section>
+				<section><h1>250</h1></section>
+				<section><h1>251</h1></section>
+				<section><h1>252</h1></section>
+				<section><h1>253</h1></section>
+				<section><h1>254</h1></section>
+				<section><h1>255</h1></section>
+				<section><h1>256</h1></section>
+				<section><h1>257</h1></section>
+				<section><h1>258</h1></section>
+				<section><h1>259</h1></section>
+				<section><h1>260</h1></section>
+				<section><h1>261</h1></section>
+				<section><h1>262</h1></section>
+				<section><h1>263</h1></section>
+				<section><h1>264</h1></section>
+				<section><h1>265</h1></section>
+				<section><h1>266</h1></section>
+				<section><h1>267</h1></section>
+				<section><h1>268</h1></section>
+				<section><h1>269</h1></section>
+				<section><h1>270</h1></section>
+				<section><h1>271</h1></section>
+				<section><h1>272</h1></section>
+				<section><h1>273</h1></section>
+				<section><h1>274</h1></section>
+				<section><h1>275</h1></section>
+				<section><h1>276</h1></section>
+				<section><h1>277</h1></section>
+				<section><h1>278</h1></section>
+				<section><h1>279</h1></section>
+				<section><h1>280</h1></section>
+				<section><h1>281</h1></section>
+				<section><h1>282</h1></section>
+				<section><h1>283</h1></section>
+				<section><h1>284</h1></section>
+				<section><h1>285</h1></section>
+				<section><h1>286</h1></section>
+				<section><h1>287</h1></section>
+				<section><h1>288</h1></section>
+				<section><h1>289</h1></section>
+				<section><h1>290</h1></section>
+				<section><h1>291</h1></section>
+				<section><h1>292</h1></section>
+				<section><h1>293</h1></section>
+				<section><h1>294</h1></section>
+				<section><h1>295</h1></section>
+				<section><h1>296</h1></section>
+				<section><h1>297</h1></section>
+				<section><h1>298</h1></section>
+				<section><h1>299</h1></section>
+				<section><h1>300</h1></section>
+				<section><h1>301</h1></section>
+				<section><h1>302</h1></section>
+				<section><h1>303</h1></section>
+				<section><h1>304</h1></section>
+				<section><h1>305</h1></section>
+				<section><h1>306</h1></section>
+				<section><h1>307</h1></section>
+				<section><h1>308</h1></section>
+				<section><h1>309</h1></section>
+				<section><h1>310</h1></section>
+				<section><h1>311</h1></section>
+				<section><h1>312</h1></section>
+				<section><h1>313</h1></section>
+				<section><h1>314</h1></section>
+				<section><h1>315</h1></section>
+				<section><h1>316</h1></section>
+				<section><h1>317</h1></section>
+				<section><h1>318</h1></section>
+				<section><h1>319</h1></section>
+				<section><h1>320</h1></section>
+				<section><h1>321</h1></section>
+				<section><h1>322</h1></section>
+				<section><h1>323</h1></section>
+				<section><h1>324</h1></section>
+				<section><h1>325</h1></section>
+				<section><h1>326</h1></section>
+				<section><h1>327</h1></section>
+				<section><h1>328</h1></section>
+				<section><h1>329</h1></section>
+				<section><h1>330</h1></section>
+				<section><h1>331</h1></section>
+				<section><h1>332</h1></section>
+				<section><h1>333</h1></section>
+				<section><h1>334</h1></section>
+				<section><h1>335</h1></section>
+				<section><h1>336</h1></section>
+				<section><h1>337</h1></section>
+				<section><h1>338</h1></section>
+				<section><h1>339</h1></section>
+				<section><h1>340</h1></section>
+				<section><h1>341</h1></section>
+				<section><h1>342</h1></section>
+				<section><h1>343</h1></section>
+				<section><h1>344</h1></section>
+				<section><h1>345</h1></section>
+				<section><h1>346</h1></section>
+				<section><h1>347</h1></section>
+				<section><h1>348</h1></section>
+				<section><h1>349</h1></section>
+				<section><h1>350</h1></section>
+				<section><h1>351</h1></section>
+				<section><h1>352</h1></section>
+				<section><h1>353</h1></section>
+				<section><h1>354</h1></section>
+				<section><h1>355</h1></section>
+				<section><h1>356</h1></section>
+				<section><h1>357</h1></section>
+				<section><h1>358</h1></section>
+				<section><h1>359</h1></section>
+				<section><h1>360</h1></section>
+				<section><h1>361</h1></section>
+				<section><h1>362</h1></section>
+				<section><h1>363</h1></section>
+				<section><h1>364</h1></section>
+				<section><h1>365</h1></section>
+				<section><h1>366</h1></section>
+				<section><h1>367</h1></section>
+				<section><h1>368</h1></section>
+				<section><h1>369</h1></section>
+				<section><h1>370</h1></section>
+				<section><h1>371</h1></section>
+				<section><h1>372</h1></section>
+				<section><h1>373</h1></section>
+				<section><h1>374</h1></section>
+				<section><h1>375</h1></section>
+				<section><h1>376</h1></section>
+				<section><h1>377</h1></section>
+				<section><h1>378</h1></section>
+				<section><h1>379</h1></section>
+				<section><h1>380</h1></section>
+				<section><h1>381</h1></section>
+				<section><h1>382</h1></section>
+				<section><h1>383</h1></section>
+				<section><h1>384</h1></section>
+				<section><h1>385</h1></section>
+				<section><h1>386</h1></section>
+				<section><h1>387</h1></section>
+				<section><h1>388</h1></section>
+				<section><h1>389</h1></section>
+				<section><h1>390</h1></section>
+				<section><h1>391</h1></section>
+				<section><h1>392</h1></section>
+				<section><h1>393</h1></section>
+				<section><h1>394</h1></section>
+				<section><h1>395</h1></section>
+				<section><h1>396</h1></section>
+				<section><h1>397</h1></section>
+				<section><h1>398</h1></section>
+				<section><h1>399</h1></section>
+				<section><h1>400</h1></section>
+				<section><h1>401</h1></section>
+				<section><h1>402</h1></section>
+				<section><h1>403</h1></section>
+				<section><h1>404</h1></section>
+				<section><h1>405</h1></section>
+				<section><h1>406</h1></section>
+				<section><h1>407</h1></section>
+				<section><h1>408</h1></section>
+				<section><h1>409</h1></section>
+				<section><h1>410</h1></section>
+				<section><h1>411</h1></section>
+				<section><h1>412</h1></section>
+				<section><h1>413</h1></section>
+				<section><h1>414</h1></section>
+				<section><h1>415</h1></section>
+				<section><h1>416</h1></section>
+				<section><h1>417</h1></section>
+				<section><h1>418</h1></section>
+				<section><h1>419</h1></section>
+				<section><h1>420</h1></section>
+				<section><h1>421</h1></section>
+				<section><h1>422</h1></section>
+				<section><h1>423</h1></section>
+				<section><h1>424</h1></section>
+				<section><h1>425</h1></section>
+				<section><h1>426</h1></section>
+				<section><h1>427</h1></section>
+				<section><h1>428</h1></section>
+				<section><h1>429</h1></section>
+				<section><h1>430</h1></section>
+				<section><h1>431</h1></section>
+				<section><h1>432</h1></section>
+				<section><h1>433</h1></section>
+				<section><h1>434</h1></section>
+				<section><h1>435</h1></section>
+				<section><h1>436</h1></section>
+				<section><h1>437</h1></section>
+				<section><h1>438</h1></section>
+				<section><h1>439</h1></section>
+				<section><h1>440</h1></section>
+				<section><h1>441</h1></section>
+				<section><h1>442</h1></section>
+				<section><h1>443</h1></section>
+				<section><h1>444</h1></section>
+				<section><h1>445</h1></section>
+				<section><h1>446</h1></section>
+				<section><h1>447</h1></section>
+				<section><h1>448</h1></section>
+				<section><h1>449</h1></section>
+				<section><h1>450</h1></section>
+				<section><h1>451</h1></section>
+				<section><h1>452</h1></section>
+				<section><h1>453</h1></section>
+				<section><h1>454</h1></section>
+				<section><h1>455</h1></section>
+				<section><h1>456</h1></section>
+				<section><h1>457</h1></section>
+				<section><h1>458</h1></section>
+				<section><h1>459</h1></section>
+				<section><h1>460</h1></section>
+				<section><h1>461</h1></section>
+				<section><h1>462</h1></section>
+				<section><h1>463</h1></section>
+				<section><h1>464</h1></section>
+				<section><h1>465</h1></section>
+				<section><h1>466</h1></section>
+				<section><h1>467</h1></section>
+				<section><h1>468</h1></section>
+				<section><h1>469</h1></section>
+				<section><h1>470</h1></section>
+				<section><h1>471</h1></section>
+				<section><h1>472</h1></section>
+				<section><h1>473</h1></section>
+				<section><h1>474</h1></section>
+				<section><h1>475</h1></section>
+				<section><h1>476</h1></section>
+				<section><h1>477</h1></section>
+				<section><h1>478</h1></section>
+				<section><h1>479</h1></section>
+				<section><h1>480</h1></section>
+				<section><h1>481</h1></section>
+				<section><h1>482</h1></section>
+				<section><h1>483</h1></section>
+				<section><h1>484</h1></section>
+				<section><h1>485</h1></section>
+				<section><h1>486</h1></section>
+				<section><h1>487</h1></section>
+				<section><h1>488</h1></section>
+				<section><h1>489</h1></section>
+				<section><h1>490</h1></section>
+				<section><h1>491</h1></section>
+				<section><h1>492</h1></section>
+				<section><h1>493</h1></section>
+				<section><h1>494</h1></section>
+				<section><h1>495</h1></section>
+				<section><h1>496</h1></section>
+				<section><h1>497</h1></section>
+				<section><h1>498</h1></section>
+				<section><h1>499</h1></section>
+			</div>
+		</div>
+
+		<script src="../dist/reveal.js"></script>
+		<script>
+			Reveal.initialize({
+				transition: 'linear'
+			});
+		</script>
+	</body>
+</html>

+ 19 - 0
examples/markdown.html

@@ -99,6 +99,25 @@
                     </script>
                 </section>
 
+                <!-- add optional line count offset, in this case 287 -->
+                <section data-markdown>
+                    <script type="text/template">
+                        ## echo.c
+
+                        ```c [287: 2|4,6]
+                        /* All of the options in this arg are valid, so handle them. */
+                        p = arg + 1;
+                        do {
+                            if (*p == 'n')
+                                nflag = 0;
+                            if (*p == 'e')
+                                eflag = '\\';
+                        } while (*++p); 
+                        ```
+                        [source](https://git.busybox.net/busybox/tree/coreutils/echo.c?h=1_36_stable#n287)
+                    </script>
+                </section>
+
                 <!-- Images -->
                 <section data-markdown>
                     <script type="text/template">

+ 118 - 0
examples/scroll.html

@@ -0,0 +1,118 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Scroll View</title>
+
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+
+		<link rel="stylesheet" href="../dist/reset.css">
+		<link rel="stylesheet" href="../dist/reveal.css">
+		<link rel="stylesheet" href="../dist/theme/black.css" id="theme">
+    <link rel="stylesheet" href="../plugin/highlight/monokai.css">
+	</head>
+
+	<body>
+
+		<div class="reveal">
+
+			<div class="slides">
+
+				<section><h1>Scroll View</h1></section>
+				<section data-background="indigo">
+					<h2>Scroll triggered fragments</h2>
+					<ul>
+						<li class="fragment fade-left">Step one</li>
+						<li class="fragment fade-left">Step two</li>
+						<li class="fragment fade-left">Step three</li>
+					</ul>
+				</section>
+				<section data-background-color="#fff"><h2>Scrollbar inverts<br>based on slide bg</h2></section>
+				<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
+					<h2>Auto-Animate</h2>
+					<p>Scroll triggered auto-animations 😍</p>
+					<div class="r-hstack justify-center">
+						<div data-id="box1" style="background: #999; width: 50px; height: 50px; margin: 10px; border-radius: 5px;"></div>
+						<div data-id="box2" style="background: #999; width: 50px; height: 50px; margin: 10px; border-radius: 5px;"></div>
+						<div data-id="box3" style="background: #999; width: 50px; height: 50px; margin: 10px; border-radius: 5px;"></div>
+					</div>
+				</section>
+				<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
+					<div class="r-hstack justify-center">
+						<div data-id="box1" data-auto-animate-delay="0" style="background: cyan; width: 150px; height: 100px; margin: 10px;"></div>
+						<div data-id="box2" data-auto-animate-delay="0.1" style="background: magenta; width: 150px; height: 100px; margin: 10px;"></div>
+						<div data-id="box3" data-auto-animate-delay="0.2" style="background: yellow; width: 150px; height: 100px; margin: 10px;"></div>
+					</div>
+					<h2 style="margin-top: 20px;">Auto-Animate</h2>
+				</section>
+				<section data-auto-animate data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)">
+					<div class="r-stack">
+						<div data-id="box1" style="background: cyan; width: 300px; height: 300px; border-radius: 200px;"></div>
+						<div data-id="box2" style="background: magenta; width: 200px; height: 200px; border-radius: 200px;"></div>
+						<div data-id="box3" style="background: yellow; width: 100px; height: 100px; border-radius: 200px;"></div>
+					</div>
+					<h2 style="margin-top: 20px;">Auto-Animate</h2>
+				</section>
+				<section data-background-gradient="linear-gradient(to bottom, #283b95, #17b2c3)" id="gradient-bg">
+					<h2 data-id="code-title">Code highlights,<br />meet scroll triggers</h2>
+					<pre data-id="code-animation"><code class="hljs javascript" data-trim data-line-numbers="|4,8-11|17|22-24"><script type="text/template">
+						import React, { useState } from 'react';
+
+						function Example() {
+						  const [count, setCount] = useState(0);
+
+						  return (
+						    <div>
+						      <p>You clicked {count} times</p>
+						      <button onClick={() => setCount(count + 1)}>
+						        Click me
+						      </button>
+						    </div>
+						  );
+						}
+
+						function SecondExample() {
+						  const [count, setCount] = useState(0);
+
+						  return (
+						    <div>
+						      <p>You clicked {count} times</p>
+						      <button onClick={() => setCount(count + 1)}>
+						        Click me
+						      </button>
+						    </div>
+						  );
+						}
+					</script></code></pre>
+				</section>
+				<section class="stack">
+          <section data-background="https://static.slid.es/reveal/image-placeholder.png" id="image-bg">
+            <h2>Image Backgrounds</h2>
+          </section>
+          <section data-background-video-muted data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.webm">
+            <h2>Video background</h2>
+          </section>
+        </section>
+				<section><h2>The end</h2></section>
+
+			</div>
+
+		</div>
+
+		<script src="../dist/reveal.js"></script>
+		<script src="../plugin/notes/notes.js"></script>
+		<script src="../plugin/markdown/markdown.js"></script>
+		<script src="../plugin/highlight/highlight.js"></script>
+		<script>
+      Reveal.initialize({
+        view: 'scroll',
+        hash: true,
+
+				plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
+			});
+    </script>
+
+	</body>
+</html>

+ 14 - 12
gulpfile.js

@@ -1,13 +1,11 @@
 const pkg = require('./package.json')
-const path = require('path')
 const glob = require('glob')
 const yargs = require('yargs')
-const colors = require('colors')
 const through = require('through2');
 const qunit = require('node-qunit-puppeteer')
 
 const {rollup} = require('rollup')
-const {terser} = require('rollup-plugin-terser')
+const terser = require('@rollup/plugin-terser')
 const babel = require('@rollup/plugin-babel').default
 const commonjs = require('@rollup/plugin-commonjs')
 const resolve = require('@rollup/plugin-node-resolve').default
@@ -31,7 +29,7 @@ const banner = `/*!
 * ${pkg.homepage}
 * MIT licensed
 *
-* Copyright (C) 2011-2022 Hakim El Hattab, https://hakim.se
+* Copyright (C) 2011-2024 Hakim El Hattab, https://hakim.se
 */\n`
 
 // Prevents warnings from opening too many test pages
@@ -164,11 +162,10 @@ function compileSass() {
 
     sass.render({
         data: transformedFile.contents.toString(),
-        includePaths: ['css/', 'css/theme/template']
+        file: transformedFile.path,
     }, ( err, result ) => {
         if( err ) {
-            console.log( vinylFile.path );
-            console.log( err.formatted );
+            callback(err);
         }
         else {
             transformedFile.extname = '.css';
@@ -278,7 +275,7 @@ gulp.task('package', gulp.series(() =>
             './lib/**',
             './images/**',
             './plugin/**',
-            './**.md'
+            './**/*.md'
         ],
         { base: './' }
     )
@@ -286,7 +283,7 @@ gulp.task('package', gulp.series(() =>
 
 ))
 
-gulp.task('reload', () => gulp.src(['*.html', '*.md'])
+gulp.task('reload', () => gulp.src(['index.html'])
     .pipe(connect.reload()));
 
 gulp.task('serve', () => {
@@ -298,14 +295,19 @@ gulp.task('serve', () => {
         livereload: true
     })
 
-    gulp.watch(['*.html', '*.md'], gulp.series('reload'))
+    const slidesRoot = root.endsWith('/') ? root : root + '/'
+    gulp.watch([
+        slidesRoot + '**/*.html',
+        slidesRoot + '**/*.md',
+        `!${slidesRoot}**/node_modules/**`, // ignore node_modules
+    ], gulp.series('reload'))
 
     gulp.watch(['js/**'], gulp.series('js', 'reload', 'eslint'))
 
     gulp.watch(['plugin/**/plugin.js', 'plugin/**/*.html'], gulp.series('plugins', 'reload'))
 
     gulp.watch([
-        'css/theme/source/*.{sass,scss}',
+        'css/theme/source/**/*.{sass,scss}',
         'css/theme/template/*.{sass,scss}',
     ], gulp.series('css-themes', 'reload'))
 
@@ -316,4 +318,4 @@ gulp.task('serve', () => {
 
     gulp.watch(['test/*.html'], gulp.series('test'))
 
-})
+})

+ 2 - 10
index.html

@@ -1,5 +1,5 @@
 <!doctype html>
-<html>
+<html lang="en">
 	<head>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
@@ -17,15 +17,7 @@
 		<div class="reveal">
 			<div class="slides">
 				<section>Slide 1</section>
-				<section>Slide 2
-						<aside class="notes">
-						<p>Some notes</p>
-						</aside>
-						<p>Some slide text</p>
-						<aside class="notes">
-						<p>and some more notes</p>
-					</aside>
-				</section>
+				<section>Slide 2</section>
 			</div>
 		</div>
 

+ 37 - 0
js/config.js

@@ -65,6 +65,9 @@ export default {
 	// Flags if we should monitor the hash and change slides accordingly
 	respondToHashChanges: true,
 
+	// Enable support for jump-to-slide navigation shortcuts
+	jumpToSlide: true,
+
 	// Push each slide change to the browser history.  Implies `hash: true`
 	history: false,
 
@@ -253,6 +256,36 @@ export default {
 	parallaxBackgroundHorizontal: null,
 	parallaxBackgroundVertical: null,
 
+	// Can be used to initialize reveal.js in one of the following views:
+	// - print:   Render the presentation so that it can be printed to PDF
+	// - scroll:  Show the presentation as a tall scrollable page with scroll
+	//            triggered animations
+	view: null,
+
+	// Adjusts the height of each slide in the scroll view.
+	// - full:       Each slide is as tall as the viewport
+	// - compact:    Slides are as small as possible, allowing multiple slides
+	//               to be visible in parallel on tall devices
+	scrollLayout: 'full',
+
+	// Control how scroll snapping works in the scroll view.
+	// - false:   	No snapping, scrolling is continuous
+	// - proximity:  Snap when close to a slide
+	// - mandatory:  Always snap to the closest slide
+	//
+	// Only applies to presentations in scroll view.
+	scrollSnap: 'mandatory',
+
+	// Enables and configure the scroll view progress bar.
+	// - 'auto':    Show the scrollbar while scrolling, hide while idle
+	// - true:      Always show the scrollbar
+	// - false:     Never show the scrollbar
+	scrollProgress: 'auto',
+
+	// Automatically activate the scroll view when we the viewport falls
+	// below the given width.
+	scrollActivationWidth: 435,
+
 	// The maximum number of pages a single slide can expand onto when printing
 	// to PDF, unlimited by default
 	pdfMaxPagesPerSlide: Number.POSITIVE_INFINITY,
@@ -284,6 +317,10 @@ export default {
 	// Time before the cursor is hidden (in ms)
 	hideCursorTime: 5000,
 
+	// Should we automatically sort and set indices for fragments
+	// at each sync? (See Reveal.sync)
+	sortFragmentsOnSync: true,
+
 	// Script dependencies to load
 	dependencies: [],
 

+ 7 - 7
js/controllers/autoanimate.js

@@ -461,7 +461,7 @@ export default class AutoAnimate {
 		const textNodes = 'h1, h2, h3, h4, h5, h6, p, li';
 		const mediaNodes = 'img, video, iframe';
 
-		// Eplicit matches via data-id
+		// Explicit matches via data-id
 		this.findAutoAnimateMatches( pairs, fromSlide, toSlide, '[data-id]', node => {
 			return node.nodeName + ':::' + node.getAttribute( 'data-id' );
 		} );
@@ -504,7 +504,7 @@ export default class AutoAnimate {
 				} );
 
 				// Line numbers
-				this.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-line[data-line-number]', node => {
+				this.findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-numbers[data-line-number]', node => {
 					return node.getAttribute( 'data-line-number' );
 				}, {
 					scale: false,
@@ -573,14 +573,14 @@ export default class AutoAnimate {
 
 			// Retrieve the 'from' element
 			if( fromMatches[key] ) {
-				const pimaryIndex = toMatches[key].length - 1;
+				const primaryIndex = toMatches[key].length - 1;
 				const secondaryIndex = fromMatches[key].length - 1;
 
 				// If there are multiple identical from elements, retrieve
 				// the one at the same index as our to-element.
-				if( fromMatches[key][ pimaryIndex ] ) {
-					fromElement = fromMatches[key][ pimaryIndex ];
-					fromMatches[key][ pimaryIndex ] = null;
+				if( fromMatches[key][ primaryIndex ] ) {
+					fromElement = fromMatches[key][ primaryIndex ];
+					fromMatches[key][ primaryIndex ] = null;
 				}
 				// If there are no matching from-elements at the same index,
 				// use the last one.
@@ -608,7 +608,7 @@ export default class AutoAnimate {
 	 * fading of unmatched elements is turned on, these elements
 	 * will fade when going between auto-animate slides.
 	 *
-	 * Note that parents of auto-animate targets are NOT considerd
+	 * Note that parents of auto-animate targets are NOT considered
 	 * unmatched since fading them would break the auto-animation.
 	 *
 	 * @param {HTMLElement} rootElement

+ 46 - 13
js/controllers/backgrounds.js

@@ -190,10 +190,30 @@ export default class Backgrounds {
 		if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition;
 		if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity;
 
+		const contrastClass = this.getContrastClass( slide );
+
+		if( typeof contrastClass === 'string' ) {
+			slide.classList.add( contrastClass );
+		}
+
+	}
+
+	/**
+	 * Returns a class name that can be applied to a slide to indicate
+	 * if it has a light or dark background.
+	 *
+	 * @param {*} slide
+	 *
+	 * @returns {string|null}
+	 */
+	getContrastClass( slide ) {
+
+		const element = slide.slideBackgroundElement;
+
 		// If this slide has a background color, we add a class that
 		// signals if it is light or dark. If the slide has no background
 		// color, no class will be added
-		let contrastColor = data.backgroundColor;
+		let contrastColor = slide.getAttribute( 'data-background-color' );
 
 		// If no bg color was found, or it cannot be converted by colorToRgb, check the computed background
 		if( !contrastColor || !colorToRgb( contrastColor ) ) {
@@ -211,14 +231,32 @@ export default class Backgrounds {
 			// an element with no background
 			if( rgb && rgb.a !== 0 ) {
 				if( colorBrightness( contrastColor ) < 128 ) {
-					slide.classList.add( 'has-dark-background' );
+					return 'has-dark-background';
 				}
 				else {
-					slide.classList.add( 'has-light-background' );
+					return 'has-light-background';
 				}
 			}
 		}
 
+		return null;
+
+	}
+
+	/**
+	 * Bubble the 'has-light-background'/'has-dark-background' classes.
+	 */
+	bubbleSlideContrastClassToElement( slide, target ) {
+
+		[ 'has-light-background', 'has-dark-background' ].forEach( classToBubble => {
+			if( slide.classList.contains( classToBubble ) ) {
+				target.classList.add( classToBubble );
+			}
+			else {
+				target.classList.remove( classToBubble );
+			}
+		}, this );
+
 	}
 
 	/**
@@ -263,10 +301,12 @@ export default class Backgrounds {
 
 					backgroundv.classList.remove( 'past', 'present', 'future' );
 
-					if( v < indices.v ) {
+					const indexv = typeof indices.v === 'number' ? indices.v : 0;
+
+					if( v < indexv ) {
 						backgroundv.classList.add( 'past' );
 					}
-					else if ( v > indices.v ) {
+					else if ( v > indexv ) {
 						backgroundv.classList.add( 'future' );
 					}
 					else {
@@ -322,14 +362,7 @@ export default class Backgrounds {
 		// If there's a background brightness flag for this slide,
 		// bubble it to the .reveal container
 		if( currentSlide ) {
-			[ 'has-light-background', 'has-dark-background' ].forEach( classToBubble => {
-				if( currentSlide.classList.contains( classToBubble ) ) {
-					this.Reveal.getRevealElement().classList.add( classToBubble );
-				}
-				else {
-					this.Reveal.getRevealElement().classList.remove( classToBubble );
-				}
-			}, this );
+			this.bubbleSlideContrastClassToElement( currentSlide, this.Reveal.getRevealElement() );
 		}
 
 		// Allow the first background to apply without transition

+ 5 - 6
js/controllers/fragments.js

@@ -174,24 +174,23 @@ export default class Fragments {
 	 *
 	 * @return {{shown: array, hidden: array}}
 	 */
-	update( index, fragments ) {
+	update( index, fragments, slide = this.Reveal.getCurrentSlide() ) {
 
 		let changedFragments = {
 			shown: [],
 			hidden: []
 		};
 
-		let currentSlide = this.Reveal.getCurrentSlide();
-		if( currentSlide && this.Reveal.getConfig().fragments ) {
+		if( slide && this.Reveal.getConfig().fragments ) {
 
-			fragments = fragments || this.sort( currentSlide.querySelectorAll( '.fragment' ) );
+			fragments = fragments || this.sort( slide.querySelectorAll( '.fragment' ) );
 
 			if( fragments.length ) {
 
 				let maxIndex = 0;
 
 				if( typeof index !== 'number' ) {
-					let currentFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
+					let currentFragment = this.sort( slide.querySelectorAll( '.fragment.visible' ) ).pop();
 					if( currentFragment ) {
 						index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
 					}
@@ -252,7 +251,7 @@ export default class Fragments {
 				// the current fragment index.
 				index = typeof index === 'number' ? index : -1;
 				index = Math.max( Math.min( index, maxIndex ), -1 );
-				currentSlide.setAttribute( 'data-fragment', index );
+				slide.setAttribute( 'data-fragment', index );
 
 			}
 

+ 197 - 0
js/controllers/jumptoslide.js

@@ -0,0 +1,197 @@
+import {
+	SLIDE_NUMBER_FORMAT_CURRENT,
+	SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL
+} from "../utils/constants";
+
+/**
+ * Makes it possible to jump to a slide by entering its
+ * slide number or id.
+ */
+export default class JumpToSlide {
+
+	constructor( Reveal ) {
+
+		this.Reveal = Reveal;
+
+		this.onInput = this.onInput.bind( this );
+		this.onBlur = this.onBlur.bind( this );
+		this.onKeyDown = this.onKeyDown.bind( this );
+
+	}
+
+	render() {
+
+		this.element = document.createElement( 'div' );
+		this.element.className = 'jump-to-slide';
+
+    this.jumpInput = document.createElement( 'input' );
+    this.jumpInput.type = 'text';
+    this.jumpInput.className = 'jump-to-slide-input';
+    this.jumpInput.placeholder = 'Jump to slide';
+		this.jumpInput.addEventListener( 'input', this.onInput );
+		this.jumpInput.addEventListener( 'keydown', this.onKeyDown );
+		this.jumpInput.addEventListener( 'blur', this.onBlur );
+
+    this.element.appendChild( this.jumpInput );
+
+	}
+
+	show() {
+
+		this.indicesOnShow = this.Reveal.getIndices();
+
+		this.Reveal.getRevealElement().appendChild( this.element );
+		this.jumpInput.focus();
+
+	}
+
+	hide() {
+
+		if( this.isVisible() ) {
+			this.element.remove();
+			this.jumpInput.value = '';
+
+			clearTimeout( this.jumpTimeout );
+			delete this.jumpTimeout;
+		}
+
+	}
+
+	isVisible() {
+
+		return !!this.element.parentNode;
+
+	}
+
+	/**
+	 * Parses the current input and jumps to the given slide.
+	 */
+	jump() {
+
+		clearTimeout( this.jumpTimeout );
+		delete this.jumpTimeout;
+
+		let query = this.jumpInput.value.trim( '' );
+		let indices;
+
+		// When slide numbers are formatted to be a single linear mumber
+		// (instead of showing a separate horizontal/vertical index) we
+		// use the same format for slide jumps
+		if( /^\d+$/.test( query ) ) {
+			const slideNumberFormat = this.Reveal.getConfig().slideNumber;
+			if( slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT || slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL ) {
+				const slide = this.Reveal.getSlides()[ parseInt( query, 10 ) - 1 ];
+				if( slide ) {
+					indices = this.Reveal.getIndices( slide );
+				}
+			}
+		}
+
+		if( !indices ) {
+			// If the query uses "horizontal.vertical" format, convert to
+			// "horizontal/vertical" so that our URL parser can understand
+			if( /^\d+\.\d+$/.test( query ) ) {
+				query = query.replace( '.', '/' );
+			}
+
+			indices = this.Reveal.location.getIndicesFromHash( query, { oneBasedIndex: true } );
+		}
+
+		// Still no valid index? Fall back on a text search
+		if( !indices && /\S+/i.test( query ) && query.length > 1 ) {
+			indices = this.search( query );
+		}
+
+		if( indices && query !== '' ) {
+			this.Reveal.slide( indices.h, indices.v, indices.f );
+			return true;
+		}
+		else {
+			this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
+			return false;
+		}
+
+	}
+
+	jumpAfter( delay ) {
+
+		clearTimeout( this.jumpTimeout );
+		this.jumpTimeout = setTimeout( () => this.jump(), delay );
+
+	}
+
+	/**
+	 * A lofi search that looks for the given query in all
+	 * of our slides and returns the first match.
+	 */
+	search( query ) {
+
+		const regex = new RegExp( '\\b' + query.trim() + '\\b', 'i' );
+
+		const slide = this.Reveal.getSlides().find( ( slide ) => {
+			return regex.test( slide.innerText );
+		} );
+
+		if( slide ) {
+			return this.Reveal.getIndices( slide );
+		}
+		else {
+			return null;
+		}
+
+	}
+
+	/**
+	 * Reverts back to the slide we were on when jump to slide was
+	 * invoked.
+	 */
+	cancel() {
+
+		this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
+		this.hide();
+
+	}
+
+	confirm() {
+
+		this.jump();
+		this.hide();
+
+	}
+
+	destroy() {
+
+		this.jumpInput.removeEventListener( 'input', this.onInput );
+		this.jumpInput.removeEventListener( 'keydown', this.onKeyDown );
+		this.jumpInput.removeEventListener( 'blur', this.onBlur );
+
+		this.element.remove();
+
+	}
+
+	onKeyDown( event ) {
+
+		if( event.keyCode === 13 ) {
+			this.confirm();
+		}
+		else if( event.keyCode === 27 ) {
+			this.cancel();
+
+			event.stopImmediatePropagation();
+		}
+
+	}
+
+	onInput( event ) {
+
+		this.jumpAfter( 200 );
+
+	}
+
+	onBlur() {
+
+		setTimeout( () => this.hide(), 1 );
+
+	}
+
+}

+ 15 - 21
js/controllers/keyboard.js

@@ -17,7 +17,6 @@ export default class Keyboard {
 		this.bindings = {};
 
 		this.onDocumentKeyDown = this.onDocumentKeyDown.bind( this );
-		this.onDocumentKeyPress = this.onDocumentKeyPress.bind( this );
 
 	}
 
@@ -43,6 +42,7 @@ export default class Keyboard {
 		this.shortcuts['Shift + &#8592;/&#8593/&#8594;/&#8595;']      = 'Jump to first/last slide';
 		this.shortcuts['B  ,  .']                       = 'Pause';
 		this.shortcuts['F']                             = 'Fullscreen';
+		this.shortcuts['G']                             = 'Jump to slide';
 		this.shortcuts['ESC, O']                        = 'Slide overview';
 
 	}
@@ -53,7 +53,6 @@ export default class Keyboard {
 	bind() {
 
 		document.addEventListener( 'keydown', this.onDocumentKeyDown, false );
-		document.addEventListener( 'keypress', this.onDocumentKeyPress, false );
 
 	}
 
@@ -63,7 +62,6 @@ export default class Keyboard {
 	unbind() {
 
 		document.removeEventListener( 'keydown', this.onDocumentKeyDown, false );
-		document.removeEventListener( 'keypress', this.onDocumentKeyPress, false );
 
 	}
 
@@ -134,20 +132,6 @@ export default class Keyboard {
 
 	}
 
-	/**
-	 * Handler for the document level 'keypress' event.
-	 *
-	 * @param {object} event
-	 */
-	onDocumentKeyPress( event ) {
-
-		// Check if the pressed key is question mark
-		if( event.shiftKey && event.charCode === 63 ) {
-			this.Reveal.toggleHelp();
-		}
-
-	}
-
 	/**
 	 * Handler for the document level 'keydown' event.
 	 *
@@ -183,10 +167,10 @@ export default class Keyboard {
 		let activeElementIsNotes = document.activeElement && document.activeElement.className && /speaker-notes/i.test( document.activeElement.className);
 
 		// Whitelist certain modifiers for slide navigation shortcuts
-		let isNavigationKey = [32, 37, 38, 39, 40, 78, 80].indexOf( event.keyCode ) !== -1;
+		let keyCodeUsesModifier = [32, 37, 38, 39, 40, 78, 80, 191].indexOf( event.keyCode ) !== -1;
 
 		// Prevent all other events when a modifier is pressed
-		let unusedModifier = 	!( isNavigationKey && event.shiftKey || event.altKey ) &&
+		let unusedModifier = 	!( keyCodeUsesModifier && event.shiftKey || event.altKey ) &&
 								( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey );
 
 		// Disregard the event if there's a focused element or a
@@ -350,7 +334,7 @@ export default class Keyboard {
 				}
 			}
 			// TWO-SPOT, SEMICOLON, B, V, PERIOD, LOGITECH PRESENTER TOOLS "BLACK SCREEN" BUTTON
-			else if( keyCode === 58 || keyCode === 59 || keyCode === 66 || keyCode === 86 || keyCode === 190 || keyCode === 191 ) {
+			else if( [58, 59, 66, 86, 190].includes( keyCode ) || ( keyCode === 191 && !event.shiftKey ) ) {
 				this.Reveal.togglePause();
 			}
 			// F
@@ -359,10 +343,20 @@ export default class Keyboard {
 			}
 			// A
 			else if( keyCode === 65 ) {
-				if ( config.autoSlideStoppable ) {
+				if( config.autoSlideStoppable ) {
 					this.Reveal.toggleAutoSlide( autoSlideWasPaused );
 				}
 			}
+			// G
+			else if( keyCode === 71 ) {
+				if( config.jumpToSlide ) {
+					this.Reveal.toggleJumpToSlide();
+				}
+			}
+			// ?
+			else if( keyCode === 191 && event.shiftKey ) {
+				this.Reveal.toggleHelp();
+			}
 			else {
 				triggered = false;
 			}

+ 9 - 7
js/controllers/location.js

@@ -40,7 +40,7 @@ export default class Location {
 	 *
 	 * @returns slide indices or null
 	 */
-	getIndicesFromHash( hash=window.location.hash ) {
+	getIndicesFromHash( hash=window.location.hash, options={} ) {
 
 		// Attempt to parse the hash as either an index or name
 		let name = hash.replace( /^#\/?/, '' );
@@ -49,7 +49,7 @@ export default class Location {
 		// If the first bit is not fully numeric and there is a name we
 		// can assume that this is a named link
 		if( !/^[0-9]*$/.test( bits[0] ) && name.length ) {
-			let element;
+			let slide;
 
 			let f;
 
@@ -62,17 +62,19 @@ export default class Location {
 
 			// Ensure the named link is a valid HTML ID attribute
 			try {
-				element = document.getElementById( decodeURIComponent( name ) );
+				slide = document
+					.getElementById( decodeURIComponent( name ) )
+					.closest('.slides section');
 			}
 			catch ( error ) { }
 
-			if( element ) {
-				return { ...this.Reveal.getIndices( element ), f };
+			if( slide ) {
+				return { ...this.Reveal.getIndices( slide ), f };
 			}
 		}
 		else {
 			const config = this.Reveal.getConfig();
-			let hashIndexBase = config.hashOneBasedIndex ? 1 : 0;
+			let hashIndexBase = config.hashOneBasedIndex || options.oneBasedIndex ? 1 : 0;
 
 			// Read the index components of the hash
 			let h = ( parseInt( bits[0], 10 ) - hashIndexBase ) || 0,
@@ -139,7 +141,7 @@ export default class Location {
 			let hash = this.getHash();
 
 			// If we're configured to push to history OR the history
-			// API is not avaialble.
+			// API is not available.
 			if( config.history ) {
 				window.location.hash = hash;
 			}

+ 10 - 4
js/controllers/notes.js

@@ -38,10 +38,12 @@ export default class Notes {
 	 */
 	update() {
 
-		if( this.Reveal.getConfig().showNotes && this.element && this.Reveal.getCurrentSlide() && !this.Reveal.print.isPrintingPDF() ) {
-
+		if( this.Reveal.getConfig().showNotes &&
+			this.element && this.Reveal.getCurrentSlide() &&
+			!this.Reveal.isScrollView() &&
+			!this.Reveal.isPrintView()
+		) {
 			this.element.innerHTML = this.getSlideNotes() || '<span class="notes-placeholder">No notes on this slide.</span>';
-
 		}
 
 	}
@@ -54,7 +56,11 @@ export default class Notes {
 	 */
 	updateVisibility() {
 
-		if( this.Reveal.getConfig().showNotes && this.hasNotes() && !this.Reveal.print.isPrintingPDF() ) {
+		if( this.Reveal.getConfig().showNotes &&
+			this.hasNotes() &&
+			!this.Reveal.isScrollView() &&
+			!this.Reveal.isPrintView()
+		) {
 			this.Reveal.getRevealElement().classList.add( 'show-notes' );
 		}
 		else {

+ 1 - 1
js/controllers/overview.js

@@ -24,7 +24,7 @@ export default class Overview {
 	activate() {
 
 		// Only proceed if enabled in config
-		if( this.Reveal.getConfig().overview && !this.isActive() ) {
+		if( this.Reveal.getConfig().overview && !this.Reveal.isScrollView() && !this.isActive() ) {
 
 			this.active = true;
 

+ 2 - 2
js/controllers/plugins.js

@@ -12,7 +12,7 @@ export default class Plugins {
 		// Flags our current state (idle -> loading -> loaded)
 		this.state = 'idle';
 
-		// An id:instance map of currently registed plugins
+		// An id:instance map of currently registered plugins
 		this.registeredPlugins = {};
 
 		this.asyncDependencies = [];
@@ -171,7 +171,7 @@ export default class Plugins {
 	/**
 	 * Registers a new plugin with this reveal.js instance.
 	 *
-	 * reveal.js waits for all regisered plugins to initialize
+	 * reveal.js waits for all registered plugins to initialize
 	 * before considering itself ready, as long as the plugin
 	 * is registered before calling `Reveal.initialize()`.
 	 */

+ 4 - 7
js/controllers/pointer.js

@@ -27,12 +27,10 @@ export default class Pointer {
 	configure( config, oldConfig ) {
 
 		if( config.mouseWheel ) {
-			document.addEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false ); // FF
-			document.addEventListener( 'mousewheel', this.onDocumentMouseScroll, false );
+			document.addEventListener( 'wheel', this.onDocumentMouseScroll, false );
 		}
 		else {
-			document.removeEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false ); // FF
-			document.removeEventListener( 'mousewheel', this.onDocumentMouseScroll, false );
+			document.removeEventListener( 'wheel', this.onDocumentMouseScroll, false );
 		}
 
 		// Auto-hide the mouse pointer when its inactive
@@ -79,8 +77,7 @@ export default class Pointer {
 
 		this.showCursor();
 
-		document.removeEventListener( 'DOMMouseScroll', this.onDocumentMouseScroll, false );
-		document.removeEventListener( 'mousewheel', this.onDocumentMouseScroll, false );
+		document.removeEventListener( 'wheel', this.onDocumentMouseScroll, false );
 		document.removeEventListener( 'mousemove', this.onDocumentCursorActive, false );
 		document.removeEventListener( 'mousedown', this.onDocumentCursorActive, false );
 
@@ -126,4 +123,4 @@ export default class Pointer {
 
 	}
 
-}
+}

+ 10 - 8
js/controllers/print.js → js/controllers/printview.js

@@ -4,7 +4,7 @@ import { queryAll, createStyleSheet } from '../utils/util.js'
 /**
  * Setups up our presentation for printing/exporting to PDF.
  */
-export default class Print {
+export default class PrintView {
 
 	constructor( Reveal ) {
 
@@ -16,7 +16,7 @@ export default class Print {
 	 * Configures the presentation for printing to a static
 	 * PDF.
 	 */
-	async setupPDF() {
+	async activate() {
 
 		const config = this.Reveal.getConfig();
 		const slides = queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR )
@@ -42,11 +42,11 @@ export default class Print {
 		// Limit the size of certain elements to the dimensions of the slide
 		createStyleSheet( '.reveal section>img, .reveal section>video, .reveal section>iframe{max-width: '+ slideWidth +'px; max-height:'+ slideHeight +'px}' );
 
-		document.documentElement.classList.add( 'print-pdf' );
+		document.documentElement.classList.add( 'reveal-print', 'print-pdf' );
 		document.body.style.width = pageWidth + 'px';
 		document.body.style.height = pageHeight + 'px';
 
-		const viewportElement = document.querySelector( '.reveal-viewport' );
+		const viewportElement = this.Reveal.getViewportElement();
 		let presentationBackground;
 		if( viewportElement ) {
 			const viewportStyles = window.getComputedStyle( viewportElement );
@@ -223,15 +223,17 @@ export default class Print {
 		// Notify subscribers that the PDF layout is good to go
 		this.Reveal.dispatchEvent({ type: 'pdf-ready' });
 
+		viewportElement.classList.remove( 'loading-scroll-mode' );
+
 	}
 
 	/**
-	 * Checks if this instance is being used to print a PDF.
+	 * Checks if the print mode is/should be activated.
 	 */
-	isPrintingPDF() {
+	isActive() {
 
-		return ( /print-pdf/gi ).test( window.location.search );
+		return this.Reveal.getConfig().view === 'print';
 
 	}
 
-}
+}

+ 906 - 0
js/controllers/scrollview.js

@@ -0,0 +1,906 @@
+import { HORIZONTAL_SLIDES_SELECTOR } from '../utils/constants.js'
+import { queryAll } from '../utils/util.js'
+
+const HIDE_SCROLLBAR_TIMEOUT = 500;
+const MAX_PROGRESS_SPACING = 4;
+const MIN_PROGRESS_SEGMENT_HEIGHT = 6;
+const MIN_PLAYHEAD_HEIGHT = 8;
+
+/**
+ * The scroll view lets you read a reveal.js presentation
+ * as a linear scrollable page.
+ */
+export default class ScrollView {
+
+	constructor( Reveal ) {
+
+		this.Reveal = Reveal;
+
+		this.active = false;
+		this.activatedCallbacks = [];
+
+		this.onScroll = this.onScroll.bind( this );
+
+	}
+
+	/**
+	 * Activates the scroll view. This rearranges the presentation DOM
+	 * by—among other things—wrapping each slide in a page element.
+	 */
+	activate() {
+
+		if( this.active ) return;
+
+		const stateBeforeActivation = this.Reveal.getState();
+
+		this.active = true;
+
+		// Store the full presentation HTML so that we can restore it
+		// when/if the scroll view is deactivated
+		this.slideHTMLBeforeActivation = this.Reveal.getSlidesElement().innerHTML;
+
+		const horizontalSlides = queryAll( this.Reveal.getRevealElement(), HORIZONTAL_SLIDES_SELECTOR );
+
+		this.viewportElement.classList.add( 'loading-scroll-mode', 'reveal-scroll' );
+
+		let presentationBackground;
+
+		const viewportStyles = window.getComputedStyle( this.viewportElement );
+		if( viewportStyles && viewportStyles.background ) {
+			presentationBackground = viewportStyles.background;
+		}
+
+		const pageElements = [];
+		const pageContainer = horizontalSlides[0].parentNode;
+
+		let previousSlide;
+
+		// Creates a new page element and appends the given slide/bg
+		// to it.
+		const createPageElement = ( slide, h, v ) => {
+
+			let contentContainer;
+
+			// If this slide is part of an auto-animation sequence, we
+			// group it under the same page element as the previous slide
+			if( previousSlide && this.Reveal.shouldAutoAnimateBetween( previousSlide, slide ) ) {
+				contentContainer = document.createElement( 'div' );
+				contentContainer.className = 'scroll-page-content scroll-auto-animate-page';
+				contentContainer.style.display = 'none';
+				previousSlide.closest( '.scroll-page-content' ).parentNode.appendChild( contentContainer );
+			}
+			else {
+				// Wrap the slide in a page element and hide its overflow
+				// so that no page ever flows onto another
+				const page = document.createElement( 'div' );
+				page.className = 'scroll-page';
+				pageElements.push( page );
+
+				// Copy the presentation-wide background to each page
+				if( presentationBackground ) {
+					page.style.background = presentationBackground;
+				}
+
+				const stickyContainer = document.createElement( 'div' );
+				stickyContainer.className = 'scroll-page-sticky';
+				page.appendChild( stickyContainer );
+
+				contentContainer = document.createElement( 'div' );
+				contentContainer.className = 'scroll-page-content';
+				stickyContainer.appendChild( contentContainer );
+			}
+
+			contentContainer.appendChild( slide );
+
+			slide.classList.remove( 'past', 'future' );
+			slide.setAttribute( 'data-index-h', h );
+			slide.setAttribute( 'data-index-v', v );
+
+			if( slide.slideBackgroundElement ) {
+				slide.slideBackgroundElement.remove( 'past', 'future' );
+				contentContainer.insertBefore( slide.slideBackgroundElement, slide );
+			}
+
+			previousSlide = slide;
+
+		}
+
+		// Slide and slide background layout
+		horizontalSlides.forEach( ( horizontalSlide, h ) => {
+
+			if( this.Reveal.isVerticalStack( horizontalSlide ) ) {
+				horizontalSlide.querySelectorAll( 'section' ).forEach( ( verticalSlide, v ) => {
+					createPageElement( verticalSlide, h, v );
+				});
+			}
+			else {
+				createPageElement( horizontalSlide, h, 0 );
+			}
+
+		}, this );
+
+		this.createProgressBar();
+
+		// Remove leftover stacks
+		queryAll( this.Reveal.getRevealElement(), '.stack' ).forEach( stack => stack.remove() );
+
+		// Add our newly created pages to the DOM
+		pageElements.forEach( page => pageContainer.appendChild( page ) );
+
+		// Re-run JS-based content layout after the slide is added to page DOM
+		this.Reveal.slideContent.layout( this.Reveal.getSlidesElement() );
+
+		this.Reveal.layout();
+		this.Reveal.setState( stateBeforeActivation );
+
+		this.activatedCallbacks.forEach( callback => callback() );
+		this.activatedCallbacks = [];
+
+		this.restoreScrollPosition();
+
+		this.viewportElement.classList.remove( 'loading-scroll-mode' );
+		this.viewportElement.addEventListener( 'scroll', this.onScroll, { passive: true } );
+
+	}
+
+	/**
+	 * Deactivates the scroll view and restores the standard slide-based
+	 * presentation.
+	 */
+	deactivate() {
+
+		if( !this.active ) return;
+
+		const stateBeforeDeactivation = this.Reveal.getState();
+
+		this.active = false;
+
+		this.viewportElement.removeEventListener( 'scroll', this.onScroll );
+		this.viewportElement.classList.remove( 'reveal-scroll' );
+
+		this.removeProgressBar();
+
+		this.Reveal.getSlidesElement().innerHTML = this.slideHTMLBeforeActivation;
+		this.Reveal.sync();
+		this.Reveal.setState( stateBeforeDeactivation );
+
+		this.slideHTMLBeforeActivation = null;
+
+	}
+
+	toggle( override ) {
+
+		if( typeof override === 'boolean' ) {
+			override ? this.activate() : this.deactivate();
+		}
+		else {
+			this.isActive() ? this.deactivate() : this.activate();
+		}
+
+	}
+
+	/**
+	 * Checks if the scroll view is currently active.
+	 */
+	isActive() {
+
+		return this.active;
+
+	}
+
+	/**
+	 * Renders the progress bar component.
+	 */
+	createProgressBar() {
+
+		this.progressBar = document.createElement( 'div' );
+		this.progressBar.className = 'scrollbar';
+
+		this.progressBarInner = document.createElement( 'div' );
+		this.progressBarInner.className = 'scrollbar-inner';
+		this.progressBar.appendChild( this.progressBarInner );
+
+		this.progressBarPlayhead = document.createElement( 'div' );
+		this.progressBarPlayhead.className = 'scrollbar-playhead';
+		this.progressBarInner.appendChild( this.progressBarPlayhead );
+
+		this.viewportElement.insertBefore( this.progressBar, this.viewportElement.firstChild );
+
+		const handleDocumentMouseMove	= ( event ) => {
+
+			let progress = ( event.clientY - this.progressBarInner.getBoundingClientRect().top ) / this.progressBarHeight;
+			progress = Math.max( Math.min( progress, 1 ), 0 );
+
+			this.viewportElement.scrollTop = progress * ( this.viewportElement.scrollHeight - this.viewportElement.offsetHeight );
+
+		};
+
+		const handleDocumentMouseUp = ( event ) => {
+
+			this.draggingProgressBar = false;
+			this.showProgressBar();
+
+			document.removeEventListener( 'mousemove', handleDocumentMouseMove );
+			document.removeEventListener( 'mouseup', handleDocumentMouseUp );
+
+		};
+
+		const handleMouseDown = ( event ) => {
+
+			event.preventDefault();
+
+			this.draggingProgressBar = true;
+
+			document.addEventListener( 'mousemove', handleDocumentMouseMove );
+			document.addEventListener( 'mouseup', handleDocumentMouseUp );
+
+			handleDocumentMouseMove( event );
+
+		};
+
+		this.progressBarInner.addEventListener( 'mousedown', handleMouseDown );
+
+	}
+
+	removeProgressBar() {
+
+		if( this.progressBar ) {
+			this.progressBar.remove();
+			this.progressBar = null;
+		}
+
+	}
+
+	layout() {
+
+		if( this.isActive() ) {
+			this.syncPages();
+			this.syncScrollPosition();
+		}
+
+	}
+
+	/**
+	 * Updates our pages to match the latest configuration and
+	 * presentation size.
+	 */
+	syncPages() {
+
+		const config = this.Reveal.getConfig();
+
+		const slideSize = this.Reveal.getComputedSlideSize( window.innerWidth, window.innerHeight );
+		const scale = this.Reveal.getScale();
+		const useCompactLayout = config.scrollLayout === 'compact';
+
+		const viewportHeight = this.viewportElement.offsetHeight;
+		const compactHeight = slideSize.height * scale;
+		const pageHeight = useCompactLayout ? compactHeight : viewportHeight;
+
+		// The height that needs to be scrolled between scroll triggers
+		this.scrollTriggerHeight = useCompactLayout ? compactHeight : viewportHeight;
+
+		this.viewportElement.style.setProperty( '--page-height', pageHeight + 'px' );
+		this.viewportElement.style.scrollSnapType = typeof config.scrollSnap === 'string' ? `y ${config.scrollSnap}` : '';
+
+		// This will hold all scroll triggers used to show/hide slides
+		this.slideTriggers = [];
+
+		const pageElements = Array.from( this.Reveal.getRevealElement().querySelectorAll( '.scroll-page' ) );
+
+		this.pages = pageElements.map( pageElement => {
+			const page = this.createPage({
+				pageElement,
+				slideElement: pageElement.querySelector( 'section' ),
+				stickyElement: pageElement.querySelector( '.scroll-page-sticky' ),
+				contentElement: pageElement.querySelector( '.scroll-page-content' ),
+				backgroundElement: pageElement.querySelector( '.slide-background' ),
+				autoAnimateElements: pageElement.querySelectorAll( '.scroll-auto-animate-page' ),
+				autoAnimatePages: []
+			});
+
+			page.pageElement.style.setProperty( '--slide-height', config.center === true ? 'auto' : slideSize.height + 'px' );
+
+			this.slideTriggers.push({
+				page: page,
+				activate: () => this.activatePage( page ),
+				deactivate: () => this.deactivatePage( page )
+			});
+
+			// Create scroll triggers that show/hide fragments
+			this.createFragmentTriggersForPage( page );
+
+			// Create scroll triggers for triggering auto-animate steps
+			if( page.autoAnimateElements.length > 0 ) {
+				this.createAutoAnimateTriggersForPage( page );
+			}
+
+			let totalScrollTriggerCount = Math.max( page.scrollTriggers.length - 1, 0 );
+
+			// Each auto-animate step may include its own scroll triggers
+			// for fragments, ensure we count those as well
+			totalScrollTriggerCount += page.autoAnimatePages.reduce( ( total, page ) => {
+				return total + Math.max( page.scrollTriggers.length - 1, 0 );
+			}, page.autoAnimatePages.length );
+
+			// Clean up from previous renders
+			page.pageElement.querySelectorAll( '.scroll-snap-point' ).forEach( el => el.remove() );
+
+			// Create snap points for all scroll triggers
+			// - Can't be absolute in FF
+			// - Can't be 0-height in Safari
+			// - Can't use snap-align on parent in Safari because then
+			//   inner triggers won't work
+			for( let i = 0; i < totalScrollTriggerCount + 1; i++ ) {
+				const triggerStick = document.createElement( 'div' );
+				triggerStick.className = 'scroll-snap-point';
+				triggerStick.style.height = this.scrollTriggerHeight + 'px';
+				triggerStick.style.scrollSnapAlign = useCompactLayout ? 'center' : 'start';
+				page.pageElement.appendChild( triggerStick );
+
+				if( i === 0 ) {
+					triggerStick.style.marginTop = -this.scrollTriggerHeight + 'px';
+				}
+			}
+
+			// In the compact layout, only slides with scroll triggers cover the
+			// full viewport height. This helps avoid empty gaps before or after
+			// a sticky slide.
+			if( useCompactLayout && page.scrollTriggers.length > 0 ) {
+				page.pageHeight = viewportHeight;
+				page.pageElement.style.setProperty( '--page-height', viewportHeight + 'px' );
+			}
+			else {
+				page.pageHeight = pageHeight;
+				page.pageElement.style.removeProperty( '--page-height' );
+			}
+
+			// Add scroll padding based on how many scroll triggers we have
+			page.scrollPadding = this.scrollTriggerHeight * totalScrollTriggerCount;
+
+			// The total height including scrollable space
+			page.totalHeight = page.pageHeight + page.scrollPadding;
+
+			// This is used to pad the height of our page in CSS
+			page.pageElement.style.setProperty( '--page-scroll-padding', page.scrollPadding + 'px' );
+
+			// If this is a sticky page, stick it to the vertical center
+			if( totalScrollTriggerCount > 0 ) {
+				page.stickyElement.style.position = 'sticky';
+				page.stickyElement.style.top = Math.max( ( viewportHeight - page.pageHeight ) / 2, 0 ) + 'px';
+			}
+			else {
+				page.stickyElement.style.position = 'relative';
+				page.pageElement.style.scrollSnapAlign = page.pageHeight < viewportHeight ? 'center' : 'start';
+			}
+
+			return page;
+		} );
+
+		this.setTriggerRanges();
+
+		/*
+		console.log(this.slideTriggers.map( t => {
+			return {
+				range: `${t.range[0].toFixed(2)}-${t.range[1].toFixed(2)}`,
+				triggers: t.page.scrollTriggers.map( t => {
+					return `${t.range[0].toFixed(2)}-${t.range[1].toFixed(2)}`
+				}).join( ', ' ),
+			}
+		}))
+		*/
+
+		this.viewportElement.setAttribute( 'data-scrollbar', config.scrollProgress );
+
+		if( config.scrollProgress && this.totalScrollTriggerCount > 1 ) {
+			// Create the progress bar if it doesn't already exist
+			if( !this.progressBar ) this.createProgressBar();
+
+			this.syncProgressBar();
+		}
+		else {
+			this.removeProgressBar();
+		}
+
+	}
+
+	/**
+	 * Calculates and sets the scroll range for all of our scroll
+	 * triggers.
+	 */
+	setTriggerRanges() {
+
+		// Calculate the total number of scroll triggers
+		this.totalScrollTriggerCount = this.slideTriggers.reduce( ( total, trigger ) => {
+			return total + Math.max( trigger.page.scrollTriggers.length, 1 );
+		}, 0 );
+
+		let rangeStart = 0;
+
+		// Calculate the scroll range of each scroll trigger on a scale
+		// of 0-1
+		this.slideTriggers.forEach( ( trigger, i ) => {
+			trigger.range = [
+				rangeStart,
+				rangeStart + Math.max( trigger.page.scrollTriggers.length, 1 ) / this.totalScrollTriggerCount
+			];
+
+			const scrollTriggerSegmentSize = ( trigger.range[1] - trigger.range[0] ) / trigger.page.scrollTriggers.length;
+			// Set the range for each inner scroll trigger
+			trigger.page.scrollTriggers.forEach( ( scrollTrigger, i ) => {
+				scrollTrigger.range = [
+					rangeStart + i * scrollTriggerSegmentSize,
+					rangeStart + ( i + 1 ) * scrollTriggerSegmentSize
+				];
+			} );
+
+			rangeStart = trigger.range[1];
+		} );
+
+	}
+
+	/**
+	 * Creates one scroll trigger for each fragments in the given page.
+	 *
+	 * @param {*} page
+	 */
+	createFragmentTriggersForPage( page, slideElement ) {
+
+		slideElement = slideElement || page.slideElement;
+
+		// Each fragment 'group' is an array containing one or more
+		// fragments. Multiple fragments that appear at the same time
+		// are part of the same group.
+		const fragmentGroups = this.Reveal.fragments.sort( slideElement.querySelectorAll( '.fragment' ), true );
+
+		// Create scroll triggers that show/hide fragments
+		if( fragmentGroups.length ) {
+			page.fragments = this.Reveal.fragments.sort( slideElement.querySelectorAll( '.fragment:not(.disabled)' ) );
+			page.scrollTriggers.push(
+				// Trigger for the initial state with no fragments visible
+				{
+					activate: () => {
+						this.Reveal.fragments.update( -1, page.fragments, slideElement );
+					}
+				}
+			);
+
+			// Triggers for each fragment group
+			fragmentGroups.forEach( ( fragments, i ) => {
+				page.scrollTriggers.push({
+					activate: () => {
+						this.Reveal.fragments.update( i, page.fragments, slideElement );
+					}
+				});
+			} );
+		}
+
+
+		return page.scrollTriggers.length;
+
+	}
+
+	/**
+	 * Creates scroll triggers for the auto-animate steps in the
+	 * given page.
+	 *
+	 * @param {*} page
+	 */
+	createAutoAnimateTriggersForPage( page ) {
+
+		if( page.autoAnimateElements.length > 0 ) {
+
+			// Triggers for each subsequent auto-animate slide
+			this.slideTriggers.push( ...Array.from( page.autoAnimateElements ).map( ( autoAnimateElement, i ) => {
+				let autoAnimatePage = this.createPage({
+					slideElement: autoAnimateElement.querySelector( 'section' ),
+					contentElement: autoAnimateElement,
+					backgroundElement: autoAnimateElement.querySelector( '.slide-background' )
+				});
+
+				// Create fragment scroll triggers for the auto-animate slide
+				this.createFragmentTriggersForPage( autoAnimatePage, autoAnimatePage.slideElement );
+
+				page.autoAnimatePages.push( autoAnimatePage );
+
+				// Return our slide trigger
+				return {
+					page: autoAnimatePage,
+					activate: () => this.activatePage( autoAnimatePage ),
+					deactivate: () => this.deactivatePage( autoAnimatePage )
+				};
+			}));
+		}
+
+	}
+
+	/**
+	 * Helper method for creating a page definition and adding
+	 * required fields. A "page" is a slide or auto-animate step.
+	 */
+	createPage( page ) {
+
+		page.scrollTriggers = [];
+		page.indexh = parseInt( page.slideElement.getAttribute( 'data-index-h' ), 10 );
+		page.indexv = parseInt( page.slideElement.getAttribute( 'data-index-v' ), 10 );
+
+		return page;
+
+	}
+
+	/**
+	 * Rerenders progress bar segments so that they match the current
+	 * reveal.js config and size.
+	 */
+	syncProgressBar() {
+
+		this.progressBarInner.querySelectorAll( '.scrollbar-slide' ).forEach( slide => slide.remove() );
+
+		const scrollHeight = this.viewportElement.scrollHeight;
+		const viewportHeight = this.viewportElement.offsetHeight;
+		const viewportHeightFactor = viewportHeight / scrollHeight;
+
+		this.progressBarHeight = this.progressBarInner.offsetHeight;
+		this.playheadHeight = Math.max( viewportHeightFactor * this.progressBarHeight, MIN_PLAYHEAD_HEIGHT );
+		this.progressBarScrollableHeight = this.progressBarHeight - this.playheadHeight;
+
+		const progressSegmentHeight = viewportHeight / scrollHeight * this.progressBarHeight;
+		const spacing = Math.min( progressSegmentHeight / 8, MAX_PROGRESS_SPACING );
+
+		this.progressBarPlayhead.style.height = this.playheadHeight - spacing + 'px';
+
+		// Don't show individual segments if they're too small
+		if( progressSegmentHeight > MIN_PROGRESS_SEGMENT_HEIGHT ) {
+
+			this.slideTriggers.forEach( slideTrigger => {
+
+				const { page } = slideTrigger;
+
+				// Visual representation of a slide
+				page.progressBarSlide = document.createElement( 'div' );
+				page.progressBarSlide.className = 'scrollbar-slide';
+				page.progressBarSlide.style.top = slideTrigger.range[0] * this.progressBarHeight + 'px';
+				page.progressBarSlide.style.height = ( slideTrigger.range[1] - slideTrigger.range[0] ) * this.progressBarHeight - spacing + 'px';
+				page.progressBarSlide.classList.toggle( 'has-triggers', page.scrollTriggers.length > 0 );
+				this.progressBarInner.appendChild( page.progressBarSlide );
+
+				// Visual representations of each scroll trigger
+				page.scrollTriggerElements = page.scrollTriggers.map( ( trigger, i ) => {
+
+					const triggerElement = document.createElement( 'div' );
+					triggerElement.className = 'scrollbar-trigger';
+					triggerElement.style.top = ( trigger.range[0] - slideTrigger.range[0] ) * this.progressBarHeight + 'px';
+					triggerElement.style.height = ( trigger.range[1] - trigger.range[0] ) * this.progressBarHeight - spacing + 'px';
+					page.progressBarSlide.appendChild( triggerElement );
+
+					if( i === 0 ) triggerElement.style.display = 'none';
+
+					return triggerElement;
+
+				} );
+
+			} );
+
+		}
+		else {
+
+			this.pages.forEach( page => page.progressBarSlide = null );
+
+		}
+
+	}
+
+	/**
+	 * Reads the current scroll position and updates our active
+	 * trigger states accordingly.
+	 */
+	syncScrollPosition() {
+
+		const viewportHeight = this.viewportElement.offsetHeight;
+		const viewportHeightFactor = viewportHeight / this.viewportElement.scrollHeight;
+
+		const scrollTop = this.viewportElement.scrollTop;
+		const scrollHeight = this.viewportElement.scrollHeight - viewportHeight
+		const scrollProgress = Math.max( Math.min( scrollTop / scrollHeight, 1 ), 0 );
+		const scrollProgressMid = Math.max( Math.min( ( scrollTop + viewportHeight / 2 ) / this.viewportElement.scrollHeight, 1 ), 0 );
+
+		let activePage;
+
+		this.slideTriggers.forEach( ( trigger ) => {
+			const { page } = trigger;
+
+			const shouldPreload = scrollProgress >= trigger.range[0] - viewportHeightFactor*2 &&
+														scrollProgress <= trigger.range[1] + viewportHeightFactor*2;
+
+			// Load slides that are within the preload range
+			if( shouldPreload && !page.loaded ) {
+				page.loaded = true;
+				this.Reveal.slideContent.load( page.slideElement );
+			}
+			else if( page.loaded ) {
+				page.loaded = false;
+				this.Reveal.slideContent.unload( page.slideElement );
+			}
+
+			// If we're within this trigger range, activate it
+			if( scrollProgress >= trigger.range[0] && scrollProgress <= trigger.range[1] ) {
+				this.activateTrigger( trigger );
+				activePage = trigger.page;
+			}
+			// .. otherwise deactivate
+			else if( trigger.active ) {
+				this.deactivateTrigger( trigger );
+			}
+		} );
+
+		// Each page can have its own scroll triggers, check if any of those
+		// need to be activated/deactivated
+		if( activePage ) {
+			activePage.scrollTriggers.forEach( ( trigger ) => {
+				if( scrollProgressMid >= trigger.range[0] && scrollProgressMid <= trigger.range[1] ) {
+					this.activateTrigger( trigger );
+				}
+				else if( trigger.active ) {
+					this.deactivateTrigger( trigger );
+				}
+			} );
+		}
+
+		// Update our visual progress indication
+		this.setProgressBarValue( scrollTop / ( this.viewportElement.scrollHeight - viewportHeight ) );
+
+	}
+
+	/**
+	 * Moves the progress bar playhead to the specified position.
+	 *
+	 * @param {number} progress 0-1
+	 */
+	setProgressBarValue( progress ) {
+
+		if( this.progressBar ) {
+
+			this.progressBarPlayhead.style.transform = `translateY(${progress * this.progressBarScrollableHeight}px)`;
+
+			this.getAllPages()
+				.filter( page => page.progressBarSlide )
+				.forEach( ( page ) => {
+					page.progressBarSlide.classList.toggle( 'active', page.active === true );
+
+					page.scrollTriggers.forEach( ( trigger, i ) => {
+						page.scrollTriggerElements[i].classList.toggle( 'active', page.active === true && trigger.active === true );
+					} );
+				} );
+
+			this.showProgressBar();
+
+		}
+
+	}
+
+	/**
+	 * Show the progress bar and, if configured, automatically hide
+	 * it after a delay.
+	 */
+	showProgressBar() {
+
+		this.progressBar.classList.add( 'visible' );
+
+		clearTimeout( this.hideProgressBarTimeout );
+
+		if( this.Reveal.getConfig().scrollProgress === 'auto' && !this.draggingProgressBar ) {
+
+			this.hideProgressBarTimeout = setTimeout( () => {
+				if( this.progressBar ) {
+					this.progressBar.classList.remove( 'visible' );
+				}
+			}, HIDE_SCROLLBAR_TIMEOUT );
+
+		}
+
+	}
+
+	/**
+	 * Scroll to the previous page.
+	 */
+	prev() {
+
+		this.viewportElement.scrollTop -= this.scrollTriggerHeight;
+
+	}
+
+	/**
+	 * Scroll to the next page.
+	 */
+	next() {
+
+		this.viewportElement.scrollTop += this.scrollTriggerHeight;
+
+	}
+
+	/**
+	 * Scrolls the given slide element into view.
+	 *
+	 * @param {HTMLElement} slideElement
+	 */
+	scrollToSlide( slideElement ) {
+
+		// If the scroll view isn't active yet, queue this action
+		if( !this.active ) {
+			this.activatedCallbacks.push( () => this.scrollToSlide( slideElement ) );
+		}
+		else {
+			// Find the trigger for this slide
+			const trigger = this.getScrollTriggerBySlide( slideElement );
+
+			if( trigger ) {
+				// Use the trigger's range to calculate the scroll position
+				this.viewportElement.scrollTop = trigger.range[0] * ( this.viewportElement.scrollHeight - this.viewportElement.offsetHeight );
+			}
+		}
+
+	}
+
+	/**
+	 * Persists the current scroll position to session storage
+	 * so that it can be restored.
+	 */
+	storeScrollPosition() {
+
+		clearTimeout( this.storeScrollPositionTimeout );
+
+		this.storeScrollPositionTimeout = setTimeout( () => {
+			sessionStorage.setItem( 'reveal-scroll-top', this.viewportElement.scrollTop );
+			sessionStorage.setItem( 'reveal-scroll-origin', location.origin + location.pathname );
+
+			this.storeScrollPositionTimeout = null;
+		}, 50 );
+
+	}
+
+	/**
+	 * Restores the scroll position when a deck is reloader.
+	 */
+	restoreScrollPosition() {
+
+		const scrollPosition = sessionStorage.getItem( 'reveal-scroll-top' );
+		const scrollOrigin = sessionStorage.getItem( 'reveal-scroll-origin' );
+
+		if( scrollPosition && scrollOrigin === location.origin + location.pathname ) {
+			this.viewportElement.scrollTop = parseInt( scrollPosition, 10 );
+		}
+
+	}
+
+	/**
+	 * Activates the given page and starts its embedded content
+	 * if there is any.
+	 *
+	 * @param {object} page
+	 */
+	activatePage( page ) {
+
+		if( !page.active ) {
+
+			page.active = true;
+
+			const { slideElement, backgroundElement, contentElement, indexh, indexv } = page;
+
+			contentElement.style.display = 'block';
+
+			slideElement.classList.add( 'present' );
+
+			if( backgroundElement ) {
+				backgroundElement.classList.add( 'present' );
+			}
+
+			this.Reveal.setCurrentScrollPage( slideElement, indexh, indexv );
+			this.Reveal.backgrounds.bubbleSlideContrastClassToElement( slideElement, this.viewportElement );
+
+			// If this page is part of an auto-animation there will be one
+			// content element per auto-animated page. We need to show the
+			// current page and hide all others.
+			Array.from( contentElement.parentNode.querySelectorAll( '.scroll-page-content' ) ).forEach( sibling => {
+				if( sibling !== contentElement ) {
+					sibling.style.display = 'none';
+				}
+			});
+
+		}
+
+	}
+
+	/**
+	 * Deactivates the page after it has been visible.
+	 *
+	 * @param {object} page
+	 */
+	deactivatePage( page ) {
+
+		if( page.active ) {
+
+			page.active = false;
+			if( page.slideElement ) page.slideElement.classList.remove( 'present' );
+			if( page.backgroundElement ) page.backgroundElement.classList.remove( 'present' );
+
+		}
+
+	}
+
+	activateTrigger( trigger ) {
+
+		if( !trigger.active ) {
+			trigger.active = true;
+			trigger.activate();
+		}
+
+	}
+
+	deactivateTrigger( trigger ) {
+
+		if( trigger.active ) {
+			trigger.active = false;
+
+			if( trigger.deactivate ) {
+				trigger.deactivate();
+			}
+		}
+
+	}
+
+	/**
+	 * Retrieve a slide by its original h/v index (i.e. the indices the
+	 * slide had before being linearized).
+	 *
+	 * @param {number} h
+	 * @param {number} v
+	 * @returns {HTMLElement}
+	 */
+	getSlideByIndices( h, v ) {
+
+		const page = this.getAllPages().find( page => {
+			return page.indexh === h && page.indexv === v;
+		} );
+
+		return page ? page.slideElement : null;
+
+	}
+
+	/**
+	 * Retrieve a list of all scroll triggers for the given slide
+	 * DOM element.
+	 *
+	 * @param {HTMLElement} slide
+	 * @returns {Array}
+	 */
+	getScrollTriggerBySlide( slide ) {
+
+		return this.slideTriggers.find( trigger => trigger.page.slideElement === slide );
+
+	}
+
+	/**
+	 * Get a list of all pages in the scroll view. This includes
+	 * both top-level slides and auto-animate steps.
+	 *
+	 * @returns {Array}
+	 */
+	getAllPages() {
+
+		return this.pages.flatMap( page => [page, ...(page.autoAnimatePages || [])] );
+
+	}
+
+	onScroll() {
+
+		this.syncScrollPosition();
+		this.storeScrollPosition();
+
+	}
+
+	get viewportElement() {
+
+		return this.Reveal.getViewportElement();
+
+	}
+
+}

+ 14 - 6
js/controllers/slidecontent.js

@@ -1,4 +1,4 @@
-import { extend, queryAll, closest, getMimeTypeFromFile } from '../utils/util.js'
+import { extend, queryAll, closest, getMimeTypeFromFile, encodeRFC3986URI } from '../utils/util.js'
 import { isMobile } from '../utils/device.js'
 
 import fitty from 'fitty';
@@ -25,6 +25,10 @@ export default class SlideContent {
 	 */
 	shouldPreload( element ) {
 
+		if( this.Reveal.isScrollView() ) {
+			return true;
+		}
+
 		// Prefer an explicit global preload setting
 		let preload = this.Reveal.getConfig().preloadIframes;
 
@@ -108,7 +112,9 @@ export default class SlideContent {
 					// URL(s)
 					else {
 						backgroundContent.style.backgroundImage = backgroundImage.split( ',' ).map( background => {
-							return `url(${encodeURI(background.trim())})`;
+							// Decode URL(s) that are already encoded first
+							let decoded = decodeURI(background.trim());
+							return `url(${encodeRFC3986URI(decoded)})`;
 						}).join( ',' );
 					}
 				}
@@ -136,13 +142,15 @@ export default class SlideContent {
 
 					// Support comma separated lists of video sources
 					backgroundVideo.split( ',' ).forEach( source => {
+						const sourceElement = document.createElement( 'source' );
+						sourceElement.setAttribute( 'src', source );
+
 						let type = getMimeTypeFromFile( source );
 						if( type ) {
-							video.innerHTML += `<source src="${source}" type="${type}">`;
-						}
-						else {
-							video.innerHTML += `<source src="${source}">`;
+							sourceElement.setAttribute( 'type', type );
 						}
+
+						video.appendChild( sourceElement );
 					} );
 
 					backgroundContent.appendChild( video );

+ 13 - 6
js/controllers/slidenumber.js

@@ -1,3 +1,10 @@
+import {
+	SLIDE_NUMBER_FORMAT_CURRENT,
+	SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL,
+	SLIDE_NUMBER_FORMAT_HORIZONTAL_DOT_VERTICAL,
+	SLIDE_NUMBER_FORMAT_HORIZONTAL_SLASH_VERTICAL
+} from "../utils/constants";
+
 /**
  * Handles the display of reveal.js' optional slide number.
  */
@@ -23,7 +30,7 @@ export default class SlideNumber {
 	configure( config, oldConfig ) {
 
 		let slideNumberDisplay = 'none';
-		if( config.slideNumber && !this.Reveal.isPrintingPDF() ) {
+		if( config.slideNumber && !this.Reveal.isPrintView() ) {
 			if( config.showSlideNumber === 'all' ) {
 				slideNumberDisplay = 'block';
 			}
@@ -56,7 +63,7 @@ export default class SlideNumber {
 
 		let config = this.Reveal.getConfig();
 		let value;
-		let format = 'h.v';
+		let format = SLIDE_NUMBER_FORMAT_HORIZONTAL_DOT_VERTICAL;
 
 		if ( typeof config.slideNumber === 'function' ) {
 			value = config.slideNumber( slide );
@@ -69,7 +76,7 @@ export default class SlideNumber {
 			// If there are ONLY vertical slides in this deck, always use
 			// a flattened slide number
 			if( !/c/.test( format ) && this.Reveal.getHorizontalSlides().length === 1 ) {
-				format = 'c';
+				format = SLIDE_NUMBER_FORMAT_CURRENT;
 			}
 
 			// Offset the current slide number by 1 to make it 1-indexed
@@ -77,16 +84,16 @@ export default class SlideNumber {
 
 			value = [];
 			switch( format ) {
-				case 'c':
+				case SLIDE_NUMBER_FORMAT_CURRENT:
 					value.push( this.Reveal.getSlidePastCount( slide ) + horizontalOffset );
 					break;
-				case 'c/t':
+				case SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL:
 					value.push( this.Reveal.getSlidePastCount( slide ) + horizontalOffset, '/', this.Reveal.getTotalSlides() );
 					break;
 				default:
 					let indices = this.Reveal.getIndices( slide );
 					value.push( indices.h + horizontalOffset );
-					let sep = format === 'h/v' ? '/' : '.';
+					let sep = format === SLIDE_NUMBER_FORMAT_HORIZONTAL_SLASH_VERTICAL ? '/' : '.';
 					if( this.Reveal.isVerticalSlide( slide ) ) value.push( sep, indices.v + 1 );
 			}
 		}

+ 322 - 73
js/reveal.js

@@ -1,7 +1,10 @@
 import SlideContent from './controllers/slidecontent.js'
 import SlideNumber from './controllers/slidenumber.js'
+import JumpToSlide from './controllers/jumptoslide.js'
 import Backgrounds from './controllers/backgrounds.js'
 import AutoAnimate from './controllers/autoanimate.js'
+import ScrollView from './controllers/scrollview.js'
+import PrintView from './controllers/printview.js'
 import Fragments from './controllers/fragments.js'
 import Overview from './controllers/overview.js'
 import Keyboard from './controllers/keyboard.js'
@@ -10,7 +13,6 @@ import Controls from './controllers/controls.js'
 import Progress from './controllers/progress.js'
 import Pointer from './controllers/pointer.js'
 import Plugins from './controllers/plugins.js'
-import Print from './controllers/print.js'
 import Touch from './controllers/touch.js'
 import Focus from './controllers/focus.js'
 import Notes from './controllers/notes.js'
@@ -26,7 +28,7 @@ import {
 } from './utils/constants.js'
 
 // The reveal.js version
-export const VERSION = '4.3.1';
+export const VERSION = '5.0.1';
 
 /**
  * reveal.js
@@ -101,8 +103,11 @@ export default function( revealElement, options ) {
 		// may be multiple presentations running in parallel.
 		slideContent = new SlideContent( Reveal ),
 		slideNumber = new SlideNumber( Reveal ),
+		jumpToSlide = new JumpToSlide( Reveal ),
 		autoAnimate = new AutoAnimate( Reveal ),
 		backgrounds = new Backgrounds( Reveal ),
+		scrollView = new ScrollView( Reveal ),
+		printView = new PrintView( Reveal ),
 		fragments = new Fragments( Reveal ),
 		overview = new Overview( Reveal ),
 		keyboard = new Keyboard( Reveal ),
@@ -111,7 +116,6 @@ export default function( revealElement, options ) {
 		progress = new Progress( Reveal ),
 		pointer = new Pointer( Reveal ),
 		plugins = new Plugins( Reveal ),
-		print = new Print( Reveal ),
 		focus = new Focus( Reveal ),
 		touch = new Touch( Reveal ),
 		notes = new Notes( Reveal );
@@ -138,6 +142,11 @@ export default function( revealElement, options ) {
 		// 5. Query params
 		config = { ...defaultConfig, ...config, ...options, ...initOptions, ...Util.getQueryHash() };
 
+		// Legacy support for the ?print-pdf query
+		if( /print-pdf/gi.test( window.location.search ) ) {
+			config.view = 'print';
+		}
+
 		setViewport();
 
 		// Force a layout when the whole page, incl fonts, has loaded
@@ -199,12 +208,15 @@ export default function( revealElement, options ) {
 		// Updates the presentation to match the current configuration values
 		configure();
 
-		// Read the initial hash
-		location.readURL();
-
 		// Create slide backgrounds
 		backgrounds.update( true );
 
+		// Activate the print/scroll view if configured
+		activateInitialView();
+
+		// Read the initial hash
+		location.readURL();
+
 		// Notify listeners that the presentation is ready but use a 1ms
 		// timeout to ensure it's not fired synchronously after #initialize()
 		setTimeout( () => {
@@ -223,19 +235,41 @@ export default function( revealElement, options ) {
 			});
 		}, 1 );
 
-		// Special setup and config is required when printing to PDF
-		if( print.isPrintingPDF() ) {
-			removeEventListeners();
+	}
+
+	/**
+	 * Activates the correct reveal.js view based on our config.
+	 * This is only invoked once during initialization.
+	 */
+	function activateInitialView() {
+
+		const activatePrintView = config.view === 'print';
+		const activateScrollView = config.view === 'scroll' || config.view === 'reader';
 
-			// The document needs to have loaded for the PDF layout
-			// measurements to be accurate
-			if( document.readyState === 'complete' ) {
-				print.setupPDF();
+		if( activatePrintView || activateScrollView ) {
+
+			if( activatePrintView ) {
+				removeEventListeners();
 			}
 			else {
-				window.addEventListener( 'load', () => {
-					print.setupPDF();
-				} );
+				touch.unbind();
+			}
+
+			// Avoid content flickering during layout
+			dom.viewport.classList.add( 'loading-scroll-mode' );
+
+			if( activatePrintView ) {
+				// The document needs to have loaded for the PDF layout
+				// measurements to be accurate
+				if( document.readyState === 'complete' ) {
+					printView.activate();
+				}
+				else {
+					window.addEventListener( 'load', () => printView.activate() );
+				}
+			}
+			else {
+				scrollView.activate();
 			}
 		}
 
@@ -253,7 +287,18 @@ export default function( revealElement, options ) {
 
 		if( !config.showHiddenSlides ) {
 			Util.queryAll( dom.wrapper, 'section[data-visibility="hidden"]' ).forEach( slide => {
-				slide.parentNode.removeChild( slide );
+				const parent = slide.parentNode;
+
+				// If this slide is part of a stack and that stack will be
+				// empty after removing the hidden slide, remove the entire
+				// stack
+				if( parent.childElementCount === 1 && /section/i.test( parent.nodeName ) ) {
+					parent.remove();
+				}
+				else {
+					slide.remove();
+				}
+
 			} );
 		}
 
@@ -278,6 +323,7 @@ export default function( revealElement, options ) {
 
 		backgrounds.render();
 		slideNumber.render();
+		jumpToSlide.render();
 		controls.render();
 		progress.render();
 		notes.render();
@@ -371,7 +417,7 @@ export default function( revealElement, options ) {
 	function setupScrollPrevention() {
 
 		setInterval( () => {
-			if( dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
+			if( !scrollView.isActive() && dom.wrapper.scrollTop !== 0 || dom.wrapper.scrollLeft !== 0 ) {
 				dom.wrapper.scrollTop = 0;
 				dom.wrapper.scrollLeft = 0;
 			}
@@ -438,8 +484,8 @@ export default function( revealElement, options ) {
 		dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
 
 		// Expose our configured slide dimensions as custom props
-		dom.viewport.style.setProperty( '--slide-width', config.width + 'px' );
-		dom.viewport.style.setProperty( '--slide-height', config.height + 'px' );
+		dom.viewport.style.setProperty( '--slide-width', typeof config.width === 'string' ? config.width :  config.width + 'px' );
+		dom.viewport.style.setProperty( '--slide-height', typeof config.height === 'string' ? config.height :  config.height + 'px' );
 
 		if( config.shuffle ) {
 			shuffle();
@@ -571,6 +617,7 @@ export default function( revealElement, options ) {
 		progress.destroy();
 		backgrounds.destroy();
 		slideNumber.destroy();
+		jumpToSlide.destroy();
 
 		// Remove event listeners
 		document.removeEventListener( 'fullscreenchange', onFullscreenChange );
@@ -674,6 +721,26 @@ export default function( revealElement, options ) {
 
 	}
 
+	/**
+	 * Dispatches a slidechanged event.
+	 *
+	 * @param {string} origin Used to identify multiplex clients
+	 */
+	function dispatchSlideChanged( origin ) {
+
+		dispatchEvent({
+			type: 'slidechanged',
+			data: {
+				indexh,
+				indexv,
+				previousSlide,
+				currentSlide,
+				origin
+			}
+		});
+
+	}
+
 	/**
 	 * Dispatched a postMessage of the given type from our window.
 	 */
@@ -857,7 +924,10 @@ export default function( revealElement, options ) {
 	 */
 	function layout() {
 
-		if( dom.wrapper && !print.isPrintingPDF() ) {
+		if( dom.wrapper && !printView.isActive() ) {
+
+			const viewportWidth = dom.viewport.offsetWidth;
+			const viewportHeight = dom.viewport.offsetHeight;
 
 			if( !config.disableLayout ) {
 
@@ -871,7 +941,9 @@ export default function( revealElement, options ) {
 					document.documentElement.style.setProperty( '--vh', ( window.innerHeight * 0.01 ) + 'px' );
 				}
 
-				const size = getComputedSlideSize();
+				const size = scrollView.isActive() ?
+							 getComputedSlideSize( viewportWidth, viewportHeight ) :
+							 getComputedSlideSize();
 
 				const oldScale = scale;
 
@@ -888,8 +960,9 @@ export default function( revealElement, options ) {
 				scale = Math.max( scale, config.minScale );
 				scale = Math.min( scale, config.maxScale );
 
-				// Don't apply any scaling styles if scale is 1
-				if( scale === 1 ) {
+				// Don't apply any scaling styles if scale is 1 or we're
+				// in the scroll view
+				if( scale === 1 || scrollView.isActive() ) {
 					dom.slides.style.zoom = '';
 					dom.slides.style.left = '';
 					dom.slides.style.top = '';
@@ -917,7 +990,7 @@ export default function( revealElement, options ) {
 						continue;
 					}
 
-					if( config.center || slide.classList.contains( 'center' ) ) {
+					if( ( config.center || slide.classList.contains( 'center' ) ) ) {
 						// Vertical stacks are not centred since their section
 						// children will be
 						if( slide.classList.contains( 'stack' ) ) {
@@ -945,7 +1018,13 @@ export default function( revealElement, options ) {
 				}
 			}
 
+			checkResponsiveScrollView();
+
 			dom.viewport.style.setProperty( '--slide-scale', scale );
+			dom.viewport.style.setProperty( '--viewport-width', viewportWidth + 'px' );
+			dom.viewport.style.setProperty( '--viewport-height', viewportHeight + 'px' );
+
+			scrollView.layout();
 
 			progress.update();
 			backgrounds.updateParallax();
@@ -966,7 +1045,6 @@ export default function( revealElement, options ) {
 	 * @param {string|number} height
 	 */
 	function layoutSlideContents( width, height ) {
-
 		// Handle sizing of elements with the 'r-stretch' class
 		Util.queryAll( dom.slides, 'section > .stretch, section > .r-stretch' ).forEach( element => {
 
@@ -993,6 +1071,40 @@ export default function( revealElement, options ) {
 
 	}
 
+	/**
+	 * Responsively activates the scroll mode when we reach the configured
+	 * activation width.
+	 */
+	function checkResponsiveScrollView() {
+
+		// Only proceed if...
+		// 1. The DOM is ready
+		// 2. Layouts aren't disabled via config
+		// 3. We're not currently printing
+		// 4. There is a scrollActivationWidth set
+		// 5. The deck isn't configured to always use the scroll view
+		if(
+			dom.wrapper &&
+			!config.disableLayout &&
+			!printView.isActive() &&
+			typeof config.scrollActivationWidth === 'number' &&
+			config.view !== 'scroll'
+		) {
+			const size = getComputedSlideSize();
+
+			if( size.presentationWidth > 0 && size.presentationWidth <= config.scrollActivationWidth ) {
+				if( !scrollView.isActive() ) {
+					backgrounds.create();
+					scrollView.activate()
+				};
+			}
+			else {
+				if( scrollView.isActive() ) scrollView.deactivate();
+			}
+		}
+
+	}
+
 	/**
 	 * Calculates the computed pixel size of our slides. These
 	 * values are based on the width and height configuration
@@ -1003,10 +1115,18 @@ export default function( revealElement, options ) {
 	 */
 	function getComputedSlideSize( presentationWidth, presentationHeight ) {
 
+		let width = config.width;
+		let height = config.height;
+
+		if( config.disableLayout ) {
+			width = dom.slides.offsetWidth;
+			height = dom.slides.offsetHeight;
+		}
+
 		const size = {
 			// Slide size
-			width: config.width,
-			height: config.height,
+			width: width,
+			height: height,
 
 			// Presentation size
 			presentationWidth: presentationWidth || dom.wrapper.offsetWidth,
@@ -1081,6 +1201,19 @@ export default function( revealElement, options ) {
 
 	}
 
+	/**
+	 * Checks if the current or specified slide is a stack containing
+	 * vertical slides.
+	 *
+	 * @param {HTMLElement} [slide=currentSlide]
+	 * @return {Boolean}
+	 */
+	function isVerticalStack( slide = currentSlide ) {
+
+		return slide.classList.contains( '.stack' ) || slide.querySelector( 'section' ) !== null;
+
+	}
+
 	/**
 	 * Returns true if we're on the last slide in the current
 	 * vertical stack.
@@ -1190,6 +1323,20 @@ export default function( revealElement, options ) {
 
 	}
 
+	/**
+	 * Toggles visibility of the jump-to-slide UI.
+	 */
+	function toggleJumpToSlide( override ) {
+
+		if( typeof override === 'boolean' ) {
+			override ? jumpToSlide.show() : jumpToSlide.hide();
+		}
+		else {
+			jumpToSlide.isVisible() ? jumpToSlide.hide() : jumpToSlide.show();
+		}
+
+	}
+
 	/**
 	 * Toggles the auto slide mode on and off.
 	 *
@@ -1233,7 +1380,7 @@ export default function( revealElement, options ) {
 	 */
 	function slide( h, v, f, origin ) {
 
-		// Dispatch an event before hte slide
+		// Dispatch an event before the slide
 		const slidechange = dispatchEvent({
 			type: 'beforeslidechange',
 			data: {
@@ -1252,6 +1399,14 @@ export default function( revealElement, options ) {
 		// Query all horizontal slides in the deck
 		const horizontalSlides = dom.wrapper.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
 
+		// If we're in scroll mode, we scroll the target slide into view
+		// instead of running our standard slide transition
+		if( scrollView.isActive() ) {
+			const scrollToSlide = scrollView.getSlideByIndices( h, v );
+			if( scrollToSlide ) scrollView.scrollToSlide( scrollToSlide );
+			return;
+		}
+
 		// Abort if there are no slides
 		if( horizontalSlides.length === 0 ) return;
 
@@ -1298,6 +1453,9 @@ export default function( revealElement, options ) {
 
 		// Detect if we're moving between two auto-animated slides
 		if( slideChanged && previousSlide && currentSlide && !overview.isActive() ) {
+			transition = 'running';
+
+			autoAnimateTransition = shouldAutoAnimateBetween( previousSlide, currentSlide, indexhBefore, indexvBefore );
 
 			// If this is an auto-animated transition, we disable the
 			// regular slide transition
@@ -1305,16 +1463,9 @@ export default function( revealElement, options ) {
 			// Note 20-03-2020:
 			// This needs to happen before we update slide visibility,
 			// otherwise transitions will still run in Safari.
-			if( previousSlide.hasAttribute( 'data-auto-animate' ) && currentSlide.hasAttribute( 'data-auto-animate' )
-					&& previousSlide.getAttribute( 'data-auto-animate-id' ) === currentSlide.getAttribute( 'data-auto-animate-id' )
-					&& !( ( indexh > indexhBefore || indexv > indexvBefore ) ? currentSlide : previousSlide ).hasAttribute( 'data-auto-animate-restart' ) ) {
-
-				autoAnimateTransition = true;
-				dom.slides.classList.add( 'disable-slide-transitions' );
+			if( autoAnimateTransition ) {
+				dom.slides.classList.add( 'disable-slide-transitions' )
 			}
-
-			transition = 'running';
-
 		}
 
 		// Update the visibility of slides now that the indices have changed
@@ -1373,16 +1524,7 @@ export default function( revealElement, options ) {
 		}
 
 		if( slideChanged ) {
-			dispatchEvent({
-				type: 'slidechanged',
-				data: {
-					indexh,
-					indexv,
-					previousSlide,
-					currentSlide,
-					origin
-				}
-			});
+			dispatchSlideChanged( origin );
 		}
 
 		// Handle embedded content
@@ -1427,6 +1569,71 @@ export default function( revealElement, options ) {
 
 	}
 
+	/**
+	 * Checks whether or not an auto-animation should take place between
+	 * the two given slides.
+	 *
+	 * @param {HTMLElement} fromSlide
+	 * @param {HTMLElement} toSlide
+	 * @param {number} indexhBefore
+	 * @param {number} indexvBefore
+	 *
+	 * @returns {boolean}
+	 */
+	function shouldAutoAnimateBetween( fromSlide, toSlide, indexhBefore, indexvBefore ) {
+
+		return 	fromSlide.hasAttribute( 'data-auto-animate' ) && toSlide.hasAttribute( 'data-auto-animate' ) &&
+				fromSlide.getAttribute( 'data-auto-animate-id' ) === toSlide.getAttribute( 'data-auto-animate-id' ) &&
+				!( ( indexh > indexhBefore || indexv > indexvBefore ) ? toSlide : fromSlide ).hasAttribute( 'data-auto-animate-restart' );
+
+	}
+
+	/**
+	 * Called anytime a new slide should be activated while in the scroll
+	 * view. The active slide is the page that occupies the most space in
+	 * the scrollable viewport.
+	 *
+	 * @param {number} pageIndex
+	 * @param {HTMLElement} slideElement
+	 */
+	function setCurrentScrollPage( slideElement, h, v ) {
+
+		let indexhBefore = indexh || 0;
+
+		indexh = h;
+		indexv = v;
+
+		const slideChanged = currentSlide !== slideElement;
+
+		previousSlide = currentSlide;
+		currentSlide = slideElement;
+
+		if( currentSlide && previousSlide ) {
+			if( config.autoAnimate && shouldAutoAnimateBetween( previousSlide, currentSlide, indexhBefore, indexv ) ) {
+				// Run the auto-animation between our slides
+				autoAnimate.run( previousSlide, currentSlide );
+			}
+		}
+
+		// Start or stop embedded content like videos and iframes
+		if( slideChanged ) {
+			if( previousSlide ) {
+				slideContent.stopEmbeddedContent( previousSlide );
+				slideContent.stopEmbeddedContent( previousSlide.slideBackgroundElement );
+			}
+
+			slideContent.startEmbeddedContent( currentSlide );
+			slideContent.startEmbeddedContent( currentSlide.slideBackgroundElement );
+		}
+
+		requestAnimationFrame( () => {
+			announceStatus( getStatusText( currentSlide ) );
+		});
+
+		dispatchSlideChanged();
+
+	}
+
 	/**
 	 * Syncs the presentation with the current DOM. Useful
 	 * when new slides or control elements are added or when
@@ -1453,7 +1660,9 @@ export default function( revealElement, options ) {
 		// Write the current hash to the URL
 		location.writeURL();
 
-		fragments.sortAll();
+		if( config.sortFragmentsOnSync === true ) {
+			fragments.sortAll();
+		}
 
 		controls.update();
 		progress.update();
@@ -1570,7 +1779,7 @@ export default function( revealElement, options ) {
 		let slides = Util.queryAll( dom.wrapper, selector ),
 			slidesLength = slides.length;
 
-		let printMode = print.isPrintingPDF();
+		let printMode = scrollView.isActive() || printView.isActive();
 		let loopedForwards = false;
 		let loopedBackwards = false;
 
@@ -1682,7 +1891,7 @@ export default function( revealElement, options ) {
 	}
 
 	/**
-	 * Shows all fragment elements within the given contaienr.
+	 * Shows all fragment elements within the given container.
 	 */
 	function showFragmentsIn( container ) {
 
@@ -1694,7 +1903,7 @@ export default function( revealElement, options ) {
 	}
 
 	/**
-	 * Hides all fragment elements within the given contaienr.
+	 * Hides all fragment elements within the given container.
 	 */
 	function hideFragmentsIn( container ) {
 
@@ -1730,7 +1939,7 @@ export default function( revealElement, options ) {
 			}
 
 			// All slides need to be visible when exporting to PDF
-			if( print.isPrintingPDF() ) {
+			if( printView.isActive() ) {
 				viewDistance = Number.MAX_VALUE;
 			}
 
@@ -1834,7 +2043,7 @@ export default function( revealElement, options ) {
 		}
 
 		// If includeFragments is set, a route will be considered
-		// availalbe if either a slid OR fragment is available in
+		// available if either a slid OR fragment is available in
 		// the given direction
 		if( includeFragments === true ) {
 			let fragmentRoutes = fragments.availableRoutes();
@@ -1961,21 +2170,31 @@ export default function( revealElement, options ) {
 
 		// If a slide is specified, return the indices of that slide
 		if( slide ) {
-			let isVertical = isVerticalSlide( slide );
-			let slideh = isVertical ? slide.parentNode : slide;
+			// In scroll mode the original h/x index is stored on the slide
+			if( scrollView.isActive() ) {
+				h = parseInt( slide.getAttribute( 'data-index-h' ), 10 );
 
-			// Select all horizontal slides
-			let horizontalSlides = getHorizontalSlides();
+				if( slide.getAttribute( 'data-index-v' ) ) {
+					v = parseInt( slide.getAttribute( 'data-index-v' ), 10 );
+				}
+			}
+			else {
+				let isVertical = isVerticalSlide( slide );
+				let slideh = isVertical ? slide.parentNode : slide;
 
-			// Now that we know which the horizontal slide is, get its index
-			h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
+				// Select all horizontal slides
+				let horizontalSlides = getHorizontalSlides();
 
-			// Assume we're not vertical
-			v = undefined;
+				// Now that we know which the horizontal slide is, get its index
+				h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
 
-			// If this is a vertical slide, grab the vertical index
-			if( isVertical ) {
-				v = Math.max( Util.queryAll( slide.parentNode, 'section' ).indexOf( slide ), 0 );
+				// Assume we're not vertical
+				v = undefined;
+
+				// If this is a vertical slide, grab the vertical index
+				if( isVertical ) {
+					v = Math.max( Util.queryAll( slide.parentNode, 'section' ).indexOf( slide ), 0 );
+				}
 			}
 		}
 
@@ -2176,11 +2395,7 @@ export default function( revealElement, options ) {
 
 		if( currentSlide && config.autoSlide !== false ) {
 
-			let fragment = currentSlide.querySelector( '.current-fragment' );
-
-			// When the slide first appears there is no "current" fragment so
-			// we look for a data-autoslide timing on the first fragment
-			if( !fragment ) fragment = currentSlide.querySelector( '.fragment' );
+			let fragment = currentSlide.querySelector( '.current-fragment[data-autoslide]' );
 
 			let fragmentAutoSlide = fragment ? fragment.getAttribute( 'data-autoslide' ) : null;
 			let parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
@@ -2284,6 +2499,9 @@ export default function( revealElement, options ) {
 
 		navigationHistory.hasNavigatedHorizontally = true;
 
+		// Scroll view navigation is handled independently
+		if( scrollView.isActive() ) return scrollView.prev();
+
 		// Reverse for RTL
 		if( config.rtl ) {
 			if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().left ) {
@@ -2301,6 +2519,9 @@ export default function( revealElement, options ) {
 
 		navigationHistory.hasNavigatedHorizontally = true;
 
+		// Scroll view navigation is handled independently
+		if( scrollView.isActive() ) return scrollView.next();
+
 		// Reverse for RTL
 		if( config.rtl ) {
 			if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().right ) {
@@ -2316,6 +2537,9 @@ export default function( revealElement, options ) {
 
 	function navigateUp({skipFragments=false}={}) {
 
+		// Scroll view navigation is handled independently
+		if( scrollView.isActive() ) return scrollView.prev();
+
 		// Prioritize hiding fragments
 		if( ( overview.isActive() || skipFragments || fragments.prev() === false ) && availableRoutes().up ) {
 			slide( indexh, indexv - 1 );
@@ -2327,6 +2551,9 @@ export default function( revealElement, options ) {
 
 		navigationHistory.hasNavigatedVertically = true;
 
+		// Scroll view navigation is handled independently
+		if( scrollView.isActive() ) return scrollView.next();
+
 		// Prioritize revealing fragments
 		if( ( overview.isActive() || skipFragments || fragments.next() === false ) && availableRoutes().down ) {
 			slide( indexh, indexv + 1 );
@@ -2342,6 +2569,9 @@ export default function( revealElement, options ) {
 	 */
 	function navigatePrev({skipFragments=false}={}) {
 
+		// Scroll view navigation is handled independently
+		if( scrollView.isActive() ) return scrollView.prev();
+
 		// Prioritize revealing fragments
 		if( skipFragments || fragments.prev() === false ) {
 			if( availableRoutes().up ) {
@@ -2381,6 +2611,9 @@ export default function( revealElement, options ) {
 		navigationHistory.hasNavigatedHorizontally = true;
 		navigationHistory.hasNavigatedVertically = true;
 
+		// Scroll view navigation is handled independently
+		if( scrollView.isActive() ) return scrollView.next();
+
 		// Prioritize revealing fragments
 		if( skipFragments || fragments.next() === false ) {
 
@@ -2509,7 +2742,6 @@ export default function( revealElement, options ) {
 	function onWindowResize( event ) {
 
 		layout();
-
 	}
 
 	/**
@@ -2652,17 +2884,24 @@ export default function( revealElement, options ) {
 		// Toggles the overview mode on/off
 		toggleOverview: overview.toggle.bind( overview ),
 
+		// Toggles the scroll view on/off
+		toggleScrollView: scrollView.toggle.bind( scrollView ),
+
 		// Toggles the "black screen" mode on/off
 		togglePause,
 
 		// Toggles the auto slide mode on/off
 		toggleAutoSlide,
 
+		// Toggles visibility of the jump-to-slide UI
+		toggleJumpToSlide,
+
 		// Slide navigation checks
 		isFirstSlide,
 		isLastSlide,
 		isLastVerticalSlide,
 		isVerticalSlide,
+		isVerticalStack,
 
 		// State checks
 		isPaused,
@@ -2670,7 +2909,9 @@ export default function( revealElement, options ) {
 		isSpeakerNotes: notes.isSpeakerNotesWindow.bind( notes ),
 		isOverview: overview.isActive.bind( overview ),
 		isFocused: focus.isFocused.bind( focus ),
-		isPrintingPDF: print.isPrintingPDF.bind( print ),
+
+		isScrollView: scrollView.isActive.bind( scrollView ),
+		isPrintView: printView.isActive.bind( printView ),
 
 		// Checks if reveal.js has been loaded and is ready for use
 		isReady: () => ready,
@@ -2679,6 +2920,10 @@ export default function( revealElement, options ) {
 		loadSlide: slideContent.load.bind( slideContent ),
 		unloadSlide: slideContent.unload.bind( slideContent ),
 
+		// Media playback
+		startEmbeddedContent: () => slideContent.startEmbeddedContent( currentSlide ),
+		stopEmbeddedContent: () => slideContent.stopEmbeddedContent( currentSlide, { unloadIframes: false } ),
+
 		// Preview management
 		showPreview,
 		hidePreview: closeOverlay,
@@ -2739,6 +2984,8 @@ export default function( revealElement, options ) {
 		hasNavigatedHorizontally: () => navigationHistory.hasNavigatedHorizontally,
 		hasNavigatedVertically: () => navigationHistory.hasNavigatedVertically,
 
+		shouldAutoAnimateBetween,
+
 		// Adds/removes a custom key binding
 		addKeyBinding: keyboard.addKeyBinding.bind( keyboard ),
 		removeKeyBinding: keyboard.removeKeyBinding.bind( keyboard ),
@@ -2750,6 +2997,7 @@ export default function( revealElement, options ) {
 		registerKeyboardShortcut: keyboard.registerKeyboardShortcut.bind( keyboard ),
 
 		getComputedSlideSize,
+		setCurrentScrollPage,
 
 		// Returns the current scale of the presentation content
 		getScale: () => scale,
@@ -2786,13 +3034,14 @@ export default function( revealElement, options ) {
 		getStatusText,
 
 		// Controllers
-		print,
 		focus,
+		scroll: scrollView,
 		progress,
 		controls,
 		location,
 		overview,
 		fragments,
+		backgrounds,
 		slideContent,
 		slideNumber,
 

+ 7 - 1
js/utils/constants.js

@@ -7,4 +7,10 @@ export const VERTICAL_SLIDES_SELECTOR = '.slides>section.present>section';
 export const POST_MESSAGE_METHOD_BLACKLIST = /registerPlugin|registerKeyboardShortcut|addKeyBinding|addEventListener|showPreview/;
 
 // Regex for retrieving the fragment style from a class attribute
-export const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/;
+export const FRAGMENT_STYLE_REGEX = /fade-(down|up|right|left|out|in-then-out|in-then-semi-out)|semi-fade-out|current-visible|shrink|grow/;
+
+// Slide number formats
+export const SLIDE_NUMBER_FORMAT_HORIZONTAL_DOT_VERTICAL = 'h.v';
+export const SLIDE_NUMBER_FORMAT_HORIZONTAL_SLASH_VERTICAL = 'h/v';
+export const SLIDE_NUMBER_FORMAT_CURRENT = 'c';
+export const SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL = 'c/t';

+ 16 - 0
js/utils/util.js

@@ -294,4 +294,20 @@ const fileExtensionToMimeMap = {
  */
 export const getMimeTypeFromFile = ( filename='' ) => {
 	return fileExtensionToMimeMap[filename.split('.').pop()]
+}
+
+/**
+ * Encodes a string for RFC3986-compliant URL format.
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encoding_for_rfc3986
+ *
+ * @param {string} url
+ */
+export const encodeRFC3986URI = ( url='' ) => {
+	return encodeURI(url)
+	  .replace(/%5B/g, "[")
+	  .replace(/%5D/g, "]")
+	  .replace(
+		/[!'()*]/g,
+		(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
+	  );
 }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 418 - 328
package-lock.json


+ 26 - 22
package.json

@@ -1,6 +1,6 @@
 {
   "name": "reveal.js",
-  "version": "4.3.1",
+  "version": "5.0.4",
   "description": "The HTML Presentation Framework",
   "homepage": "https://revealjs.com",
   "subdomain": "revealjs",
@@ -22,7 +22,7 @@
     "url": "git://github.com/hakimel/reveal.js.git"
   },
   "engines": {
-    "node": ">=10.0.0"
+    "node": ">=18.0.0"
   },
   "keywords": [
     "reveal",
@@ -30,33 +30,37 @@
     "presentation"
   ],
   "devDependencies": {
-    "@babel/core": "^7.14.3",
-    "@babel/eslint-parser": "^7.14.3",
-    "@babel/preset-env": "^7.14.2",
-    "@rollup/plugin-babel": "^5.3.0",
-    "@rollup/plugin-commonjs": "^19.0.0",
-    "@rollup/plugin-node-resolve": "^13.0.0",
-    "babel-plugin-transform-html-import-to-string": "0.0.1",
+    "@babel/core": "^7.23.2",
+    "@babel/eslint-parser": "^7.22.15",
+    "@babel/preset-env": "^7.23.2",
+    "@rollup/plugin-babel": "^6.0.4",
+    "@rollup/plugin-commonjs": "^25.0.7",
+    "@rollup/plugin-node-resolve": "^15.2.3",
+    "@rollup/plugin-terser": "^0.4.4",
+    "babel-plugin-transform-html-import-to-string": "2.0.0",
     "colors": "^1.4.0",
-    "core-js": "^3.12.1",
-    "fitty": "^2.3.0",
-    "glob": "^7.1.7",
+    "core-js": "^3.33.1",
+    "fitty": "^2.3.7",
+    "glob": "^10.3.10",
     "gulp": "^4.0.2",
     "gulp-autoprefixer": "^8.0.0",
-    "gulp-clean-css": "^4.2.0",
+    "gulp-clean-css": "^4.3.0",
     "gulp-connect": "^5.7.0",
     "gulp-eslint": "^6.0.0",
     "gulp-header": "^2.0.9",
     "gulp-tap": "^2.0.0",
-    "gulp-zip": "^4.2.0",
-    "highlight.js": "^10.0.3",
-    "marked": "^4.0.12",
-    "node-qunit-puppeteer": "^2.1.0",
-    "qunit": "^2.17.2",
-    "rollup": "^2.48.0",
-    "rollup-plugin-terser": "^7.0.2",
-    "sass": "^1.39.2",
-    "yargs": "^15.1.0"
+    "gulp-zip": "^5.1.0",
+    "highlight.js": "^11.9.0",
+    "marked": "^4.3.0",
+    "node-qunit-puppeteer": "^2.1.2",
+    "qunit": "^2.20.0",
+    "rollup": "^4.1.5",
+    "sass": "^1.69.5",
+    "yargs": "^17.7.2"
+  },
+  "overrides": {
+    "chokidar": "3.5.3",
+    "glob-parent": "6.0.2"
   },
   "browserslist": "> 2%, not dead",
   "eslintConfig": {

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/highlight/highlight.esm.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/highlight/highlight.js


+ 1 - 1
plugin/highlight/plugin.js

@@ -138,7 +138,7 @@ const Plugin = {
 
 					// Scroll highlights into view as we step through them
 					fragmentBlock.addEventListener( 'visible', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock, scrollState ) );
-					fragmentBlock.addEventListener( 'hidden', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock.previousSibling, scrollState ) );
+					fragmentBlock.addEventListener( 'hidden', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock.previousElementSibling, scrollState ) );
 
 				} );
 

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/markdown/markdown.esm.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/markdown/markdown.js


+ 64 - 48
plugin/markdown/plugin.js

@@ -7,13 +7,16 @@
 import { marked } from 'marked';
 
 const DEFAULT_SLIDE_SEPARATOR = '\r?\n---\r?\n',
-	  DEFAULT_NOTES_SEPARATOR = 'notes?:',
+	  DEFAULT_VERTICAL_SEPARATOR = null,
+	  DEFAULT_NOTES_SEPARATOR = '^\s*notes?:',
 	  DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
 	  DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
 
 const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
 
-const CODE_LINE_NUMBER_REGEX = /\[([\s\d,|-]*)\]/;
+// match an optional line number offset and highlight line numbers
+// [<line numbers>] or [<offset>: <line numbers>]
+const CODE_LINE_NUMBER_REGEX = /\[\s*((\d*):)?\s*([\s\d,|-]*)\]/;
 
 const HTML_ESCAPE_MAP = {
   '&': '&amp;',
@@ -35,22 +38,22 @@ const Plugin = () => {
 	function getMarkdownFromSlide( section ) {
 
 		// look for a <script> or <textarea data-template> wrapper
-		var template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
+		const template = section.querySelector( '[data-template]' ) || section.querySelector( 'script' );
 
 		// strip leading whitespace so it isn't evaluated as code
-		var text = ( template || section ).textContent;
+		let text = ( template || section ).textContent;
 
 		// restore script end tags
 		text = text.replace( new RegExp( SCRIPT_END_PLACEHOLDER, 'g' ), '</script>' );
 
-		var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
+		const leadingWs = text.match( /^\n?(\s*)/ )[1].length,
 			leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
 
 		if( leadingTabs > 0 ) {
-			text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
+			text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}(.*)','g'), function(m, p1) { return '\n' + p1 ; } );
 		}
 		else if( leadingWs > 1 ) {
-			text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
+			text = text.replace( new RegExp('\\n? {' + leadingWs + '}(.*)', 'g'), function(m, p1) { return '\n' + p1 ; } );
 		}
 
 		return text;
@@ -65,11 +68,11 @@ const Plugin = () => {
 	 */
 	function getForwardedAttributes( section ) {
 
-		var attributes = section.attributes;
-		var result = [];
+		const attributes = section.attributes;
+		const result = [];
 
-		for( var i = 0, len = attributes.length; i < len; i++ ) {
-			var name = attributes[i].name,
+		for( let i = 0, len = attributes.length; i < len; i++ ) {
+			const name = attributes[i].name,
 				value = attributes[i].value;
 
 			// disregard attributes that are used for markdown loading/parsing
@@ -92,10 +95,12 @@ const Plugin = () => {
 	 * values for what's not defined.
 	 */
 	function getSlidifyOptions( options ) {
+		const markdownConfig = deck?.getConfig?.().markdown;
 
 		options = options || {};
-		options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
-		options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
+		options.separator = options.separator || markdownConfig?.separator || DEFAULT_SLIDE_SEPARATOR;
+		options.verticalSeparator = options.verticalSeparator || markdownConfig?.verticalSeparator || DEFAULT_VERTICAL_SEPARATOR;
+		options.notesSeparator = options.notesSeparator || markdownConfig?.notesSeparator || DEFAULT_NOTES_SEPARATOR;
 		options.attributes = options.attributes || '';
 
 		return options;
@@ -109,7 +114,7 @@ const Plugin = () => {
 
 		options = getSlidifyOptions( options );
 
-		var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
+		const notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
 
 		if( notesMatch.length === 2 ) {
 			content = notesMatch[0] + '<aside class="notes">' + marked(notesMatch[1].trim()) + '</aside>';
@@ -131,10 +136,10 @@ const Plugin = () => {
 
 		options = getSlidifyOptions( options );
 
-		var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
+		const separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
 			horizontalSeparatorRegex = new RegExp( options.separator );
 
-		var matches,
+		let matches,
 			lastIndex = 0,
 			isHorizontal,
 			wasHorizontal = true,
@@ -143,7 +148,7 @@ const Plugin = () => {
 
 		// iterate until all blocks between separators are stacked up
 		while( matches = separatorRegex.exec( markdown ) ) {
-			var notes = null;
+			const notes = null;
 
 			// determine direction (horizontal by default)
 			isHorizontal = horizontalSeparatorRegex.test( matches[0] );
@@ -172,10 +177,10 @@ const Plugin = () => {
 		// add the remaining slide
 		( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
 
-		var markdownSections = '';
+		let markdownSections = '';
 
 		// flatten the hierarchical stack, and insert <section data-markdown> tags
-		for( var i = 0, len = sectionStack.length; i < len; i++ ) {
+		for( let i = 0, len = sectionStack.length; i < len; i++ ) {
 			// vertical
 			if( sectionStack[i] instanceof Array ) {
 				markdownSections += '<section '+ options.attributes +'>';
@@ -204,7 +209,7 @@ const Plugin = () => {
 
 		return new Promise( function( resolve ) {
 
-			var externalPromises = [];
+			const externalPromises = [];
 
 			[].slice.call( scope.querySelectorAll( 'section[data-markdown]:not([data-markdown-parsed])') ).forEach( function( section, i ) {
 
@@ -257,13 +262,13 @@ const Plugin = () => {
 
 		return new Promise( function( resolve, reject ) {
 
-			var xhr = new XMLHttpRequest(),
+			const xhr = new XMLHttpRequest(),
 				url = section.getAttribute( 'data-markdown' );
 
-			var datacharset = section.getAttribute( 'data-charset' );
+			const datacharset = section.getAttribute( 'data-charset' );
 
 			// see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
-			if( datacharset != null && datacharset != '' ) {
+			if( datacharset !== null && datacharset !== '' ) {
 				xhr.overrideMimeType( 'text/html; charset=' + datacharset );
 			}
 
@@ -308,17 +313,17 @@ const Plugin = () => {
 	 */
 	function addAttributeInElement( node, elementTarget, separator ) {
 
-		var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
-		var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"]+?)\"|(data-[^\"= ]+?)(?=[\" ])", 'mg' );
-		var nodeValue = node.nodeValue;
-		var matches,
+		const markdownClassesInElementsRegex = new RegExp( separator, 'mg' );
+		const markdownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"]+?)\"|(data-[^\"= ]+?)(?=[\" ])", 'mg' );
+		let nodeValue = node.nodeValue;
+		let matches,
 			matchesClass;
-		if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
+		if( matches = markdownClassesInElementsRegex.exec( nodeValue ) ) {
 
-			var classes = matches[1];
-			nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
+			const classes = matches[1];
+			nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( markdownClassesInElementsRegex.lastIndex );
 			node.nodeValue = nodeValue;
-			while( matchesClass = mardownClassRegex.exec( classes ) ) {
+			while( matchesClass = markdownClassRegex.exec( classes ) ) {
 				if( matchesClass[2] ) {
 					elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
 				} else {
@@ -336,34 +341,34 @@ const Plugin = () => {
 	 */
 	function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
 
-		if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
-			var previousParentElement = element;
-			for( var i = 0; i < element.childNodes.length; i++ ) {
-				var childElement = element.childNodes[i];
+		if ( element !== null && element.childNodes !== undefined && element.childNodes.length > 0 ) {
+			let previousParentElement = element;
+			for( let i = 0; i < element.childNodes.length; i++ ) {
+				const childElement = element.childNodes[i];
 				if ( i > 0 ) {
-					var j = i - 1;
+					let j = i - 1;
 					while ( j >= 0 ) {
-						var aPreviousChildElement = element.childNodes[j];
-						if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
+						const aPreviousChildElement = element.childNodes[j];
+						if ( typeof aPreviousChildElement.setAttribute === 'function' && aPreviousChildElement.tagName !== "BR" ) {
 							previousParentElement = aPreviousChildElement;
 							break;
 						}
 						j = j - 1;
 					}
 				}
-				var parentSection = section;
-				if( childElement.nodeName ==  "section" ) {
+				let parentSection = section;
+				if( childElement.nodeName ===  "section" ) {
 					parentSection = childElement ;
 					previousParentElement = childElement ;
 				}
-				if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
+				if ( typeof childElement.setAttribute === 'function' || childElement.nodeType === Node.COMMENT_NODE ) {
 					addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
 				}
 			}
 		}
 
-		if ( element.nodeType == Node.COMMENT_NODE ) {
-			if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
+		if ( element.nodeType === Node.COMMENT_NODE ) {
+			if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) === false ) {
 				addAttributeInElement( element, section, separatorSectionAttributes );
 			}
 		}
@@ -375,14 +380,14 @@ const Plugin = () => {
 	 */
 	function convertSlides() {
 
-		var sections = deck.getRevealElement().querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
+		const sections = deck.getRevealElement().querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
 
 		[].slice.call( sections ).forEach( function( section ) {
 
 			section.setAttribute( 'data-markdown-parsed', true )
 
-			var notes = section.querySelector( 'aside.notes' );
-			var markdown = getMarkdownFromSlide( section );
+			const notes = section.querySelector( 'aside.notes' );
+			const markdown = getMarkdownFromSlide( section );
 
 			section.innerHTML = marked( markdown );
 			addAttributes( 	section, section, null, section.getAttribute( 'data-element-attributes' ) ||
@@ -429,14 +434,23 @@ const Plugin = () => {
 				renderer.code = ( code, language ) => {
 
 					// Off by default
+					let lineNumberOffset = '';
 					let lineNumbers = '';
 
 					// Users can opt in to show line numbers and highlight
 					// specific lines.
 					// ```javascript []        show line numbers
 					// ```javascript [1,4-8]   highlights lines 1 and 4-8
+					// optional line number offset:
+					// ```javascript [25: 1,4-8]   start line numbering at 25,
+					//                             highlights lines 1 (numbered as 25) and 4-8 (numbered as 28-32)
 					if( CODE_LINE_NUMBER_REGEX.test( language ) ) {
-						lineNumbers = language.match( CODE_LINE_NUMBER_REGEX )[1].trim();
+						let lineNumberOffsetMatch =  language.match( CODE_LINE_NUMBER_REGEX )[2];
+						if (lineNumberOffsetMatch){
+							lineNumberOffset =  `data-ln-start-from="${lineNumberOffsetMatch.trim()}"`;
+						}
+
+						lineNumbers = language.match( CODE_LINE_NUMBER_REGEX )[3].trim();
 						lineNumbers = `data-line-numbers="${lineNumbers}"`;
 						language = language.replace( CODE_LINE_NUMBER_REGEX, '' ).trim();
 					}
@@ -446,7 +460,9 @@ const Plugin = () => {
 					// highlight.js is able to read it
 					code = escapeForHTML( code );
 
-					return `<pre><code ${lineNumbers} class="${language}">${code}</code></pre>`;
+					// return `<pre><code ${lineNumbers} class="${language}">${code}</code></pre>`;
+
+					return `<pre><code ${lineNumbers} ${lineNumberOffset} class="${language}">${code}</code></pre>`;
 				};
 			}
 

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/notes/notes.esm.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/notes/notes.js


+ 7 - 3
plugin/notes/plugin.js

@@ -146,9 +146,13 @@ const Plugin = () => {
 		}
 
 		// Look for notes defined in an aside element
-		if( notesElements ) {
-			messageData.notes = Array.from(notesElements).map( notesElement => notesElement.innerHTML ).join( '\n' );
-			messageData.markdown = typeof notesElements[0].getAttribute( 'data-markdown' ) === 'string';
+		if( notesElements && notesElements.length ) {
+			// Ignore notes inside of fragments since those are shown
+			// individually when stepping through fragments
+			notesElements = Array.from( notesElements ).filter( notesElement => notesElement.closest( '.fragment' ) === null );
+
+			messageData.notes = notesElements.map( notesElement => notesElement.innerHTML ).join( '\n' );
+			messageData.markdown = notesElements[0] && typeof notesElements[0].getAttribute( 'data-markdown' ) === 'string';
 		}
 
 		speakerWindow.postMessage( JSON.stringify( messageData ), '*' );

+ 9 - 2
plugin/notes/speaker-view.html

@@ -383,6 +383,13 @@
 
 				window.addEventListener( 'message', function( event ) {
 
+					// Validate the origin of all messages to avoid parsing messages
+					// that aren't meant for us. Ignore when running off file:// so
+					// that the speaker view continues to work without a web server.
+					if( window.location.origin !== event.origin && window.location.origin !== 'file://' ) {
+						return
+					}
+
 					clearTimeout( connectionTimeout );
 					connectionStatus.style.display = 'none';
 
@@ -533,8 +540,8 @@
 
 					var urlSeparator = /\?/.test(data.url) ? '&' : '?';
 					var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
-					var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
-					var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
+					var currentURL = data.url + urlSeparator + params + '&scrollActivationWidth=false&postMessageEvents=true' + hash;
+					var upcomingURL = data.url + urlSeparator + params + '&scrollActivationWidth=false&controls=false' + hash;
 
 					currentSlide = document.createElement( 'iframe' );
 					currentSlide.setAttribute( 'width', 1280 );

+ 1 - 1
plugin/search/plugin.js

@@ -137,7 +137,7 @@ const Plugin = () => {
 
 		this.setRegex = function(input)
 		{
-			input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
+			input = input.trim();
 			matchRegex = new RegExp("(" + input + ")","i");
 		}
 

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/search/search.esm.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
plugin/search/search.js


+ 1 - 0
test/test-auto-animate.html

@@ -65,6 +65,7 @@
 		<script src="../dist/reveal.js"></script>
 		<script>
 
+			QUnit.config.testTimeout = 30000;
 			QUnit.config.reorder = false;
 
 			const slides = Array.prototype.map.call( document.querySelectorAll( '.slides section' ), slide => {

+ 1 - 0
test/test-dependencies-async.html

@@ -31,6 +31,7 @@
 			var	externalScriptSequence = '';
 			var scriptCount = 0;
 
+			QUnit.config.testTimeout = 30000;
 			QUnit.config.autostart = false;
 			QUnit.module( 'Async Dependencies' );
 

+ 5 - 1
test/test-dependencies.html

@@ -30,6 +30,10 @@
 		<script>
 			window.externalScriptSequence = '';
 
+			QUnit.config.testTimeout = 30000;
+			QUnit.config.autostart = false;
+			QUnit.module( 'Dependencies' );
+
 			Reveal.initialize({
 				dependencies: [
 					{ src: 'assets/external-script-a.js' },
@@ -38,7 +42,7 @@
 				]
 			}).then( () => {
 
-				QUnit.module( 'Dependencies' );
+				QUnit.start();
 
 				QUnit.test( 'Load synchronous scripts', function( assert ) {
 					assert.strictEqual( window.externalScriptSequence, 'ABC', 'Loaded and executed in order' );

+ 5 - 1
test/test-grid-navigation.html

@@ -40,9 +40,13 @@
 
 		<script src="../dist/reveal.js"></script>
 		<script>
+			QUnit.config.testTimeout = 30000;
+			QUnit.config.autostart = false;
+			QUnit.module( 'Grid Navigation' );
+
 			Reveal.initialize().then( () => {
 
-				QUnit.module( 'Grid Navigation' );
+				QUnit.start();
 
 				QUnit.test( 'Disabled', function( assert ) {
 					Reveal.right();

+ 2 - 0
test/test-iframe-backgrounds.html

@@ -34,6 +34,8 @@
 		<script src="../dist/reveal.js"></script>
 		<script>
 
+			QUnit.config.testTimeout = 30000;
+
 			Reveal.initialize({ viewDistance: 3 }).then( () => {
 
 				function getIframe( index ) {

+ 2 - 0
test/test-iframes.html

@@ -34,6 +34,8 @@
 		<script src="../dist/reveal.js"></script>
 		<script>
 
+			QUnit.config.testTimeout = 30000;
+
 			Reveal.initialize({ viewDistance: 2 }).then( () => {
 
 				var defaultIframe = document.querySelector( '.default-iframe' ),

+ 36 - 0
test/test-markdown.html

@@ -274,6 +274,13 @@
 						```
 					</script>
 				</section>
+				<section data-markdown class="with-offset">
+					<script type="text/template">
+						```[123:]
+						code
+						```
+					</script>
+				</section>
 				<section data-markdown class="with-line-highlights-and-lanugage">
 					<script type="text/template">
 						```javascript [1,2,3]
@@ -281,6 +288,20 @@
 						```
 					</script>
 				</section>
+				<section data-markdown class="with-line-highlights-offset-and-lanugage">
+					<script type="text/template">
+						```javascript [456: 3,4,5]
+						code
+						```
+					</script>
+				</section>
+				<section data-markdown class="with-line-offset-and-lanugage">
+					<script type="text/template">
+						```javascript [756:]
+						code
+						```
+					</script>
+				</section>
 				<section data-markdown class="with-code-in-fragment">
 					<script type="text/template">
 					```js
@@ -298,6 +319,8 @@
 			import Markdown from '../plugin/markdown/markdown.esm.js'
 			import Highlight from '../plugin/highlight/highlight.esm.js'
 
+			QUnit.config.testTimeout = 30000;
+
 			let deck1 = new Reveal( document.querySelector( '.deck1' ), { plugins: [ Markdown ] })
 			deck1.addEventListener( 'ready', function() {
 
@@ -458,9 +481,22 @@
 					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-line-highlights .hljs[data-line-numbers="1,2,3"]' ).length, 1 );
 				});
 
+				QUnit.test( '```[234: ] line offset only', function( assert ) {
+					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-offset .hljs[data-ln-start-from="123"]' ).length, 1 );
+				});
+
 				QUnit.test( '```javascript [1,2,3] enables line highlights and sets language', function( assert ) {
 					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-line-highlights-and-lanugage .hljs.javascript[data-line-numbers="1,2,3"]' ).length, 1 );
 				});
+
+				QUnit.test( '```javascript [123: 3,4,5] add line offset and enables line highlights and sets language', function( assert ) {
+					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-line-highlights-offset-and-lanugage .hljs.javascript[data-line-numbers="3,4,5"]' ).length, 1 );
+					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-line-highlights-offset-and-lanugage .hljs.javascript[data-ln-start-from="456"]' ).length, 1 );
+				});
+
+				QUnit.test( '```javascript [756:] add line offset and sets no line highlights and sets language', function( assert ) {
+					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-line-offset-and-lanugage .hljs.javascript[data-ln-start-from="756"]' ).length, 1 );
+				});
 				
 				QUnit.test( '```block should allow custom fragment', function( assert ) {
 					assert.strictEqual( deck6.getRevealElement().querySelectorAll( '.with-code-in-fragment pre.fragment' ).length, 1 );

+ 1 - 0
test/test-multiple-instances-es5.html

@@ -40,6 +40,7 @@
 		<script src="../plugin/zoom/zoom.js"></script>
 		<script>
 
+			QUnit.config.testTimeout = 30000;
 			QUnit.module( 'Multiple reveal.js instances' );
 
 			let r1 = new Reveal( document.querySelector( '.deck1 .reveal' ), {

+ 1 - 0
test/test-multiple-instances.html

@@ -41,6 +41,7 @@
 			import Reveal from '../dist/reveal.esm.js';
 			import Zoom from '../plugin/zoom/zoom.esm.js';
 
+			QUnit.config.testTimeout = 30000;
 			QUnit.module( 'Multiple reveal.js instances' );
 
 			let r1 = new Reveal( document.querySelector( '.deck1 .reveal' ), {

+ 5 - 0
test/test-pdf.html

@@ -75,8 +75,13 @@
 
 		<script src="../dist/reveal.js"></script>
 		<script>
+			QUnit.config.testTimeout = 30000;
+			QUnit.config.autostart = false;
+
 			Reveal.initialize({ pdf: true }).then( function() {
 
+				QUnit.start();
+
 				// Only one test for now, we're mainly ensuring that there
 				// are no execution errors when running PDF mode
 

+ 1 - 0
test/test-plugins.html

@@ -29,6 +29,7 @@
 		<script src="../dist/reveal.js"></script>
 		<script>
 
+			QUnit.config.testTimeout = 30000;
 			QUnit.module( 'Plugins' );
 
 			var initCounter = { PluginB: 0, PluginC: 0, PluginD: 0 };

+ 115 - 0
test/test-scroll.html

@@ -0,0 +1,115 @@
+<!doctype html>
+<html lang="en">
+
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Test Scroll View</title>
+
+		<link rel="stylesheet" href="../dist/reveal.css">
+		<link rel="stylesheet" href="../node_modules/qunit/qunit/qunit.css">
+		<script src="../node_modules/qunit/qunit/qunit.js"></script>
+	</head>
+
+	<body style="overflow: auto;">
+
+		<div id="qunit"></div>
+		<div id="qunit-fixture"></div>
+
+		<div class="reveal" style="opacity: 0; pointer-events: none;">
+
+			<div class="slides">
+
+				<section>
+					<h1>slide 1</h1>
+				</section>
+
+				<section>
+					<h1>slide 2</h1>
+				</section>
+
+				<section>
+					<h1>slide 3</h1>
+					<p class="fragment">fragment 1</p>
+					<p class="fragment">fragment 2</p>
+					<p class="fragment">fragment 3</p>
+				</section>
+
+				<section>
+					<h1>slide 4</h1>
+				</section>
+
+			</div>
+
+		</div>
+
+		<script src="../dist/reveal.js"></script>
+		<script>
+
+			QUnit.config.testTimeout = 30000;
+			QUnit.config.reorder = false;
+
+			function getScrollHeight() {
+				return Reveal.getViewportElement().scrollHeight;
+			}
+
+			function getViewportHeight() {
+				return Reveal.getViewportElement().offsetHeight;
+			}
+
+			Reveal.initialize({ view: 'scroll' }).then( async () => {
+
+				QUnit.module( 'Scroll View' );
+
+				QUnit.test( 'Activates', assert => {
+					assert.ok( getScrollHeight() > getViewportHeight(), 'Is overflowing' );
+				});
+
+				QUnit.test( 'Can be toggled via API', assert => {
+					Reveal.toggleScrollView( false );
+					assert.ok( getScrollHeight() <= getViewportHeight(), 'Is not overflowing' );
+					Reveal.toggleScrollView( true );
+					assert.ok( getScrollHeight() > getViewportHeight(), 'Is overflowing' );
+				});
+
+				QUnit.test( 'Changes present slide when scrolling', assert => {
+					assert.timeout( 200 );
+					assert.expect( 2 );
+
+					const slides = document.querySelectorAll( '.reveal .slides section' );
+
+					assert.ok( slides[0].classList.contains( 'present' ), 'First slide is present' );
+					Reveal.getViewportElement().scrollTop = getViewportHeight() * 1;
+
+					return new Promise( resolve => {
+						setTimeout(() => {
+							assert.ok( slides[1].classList.contains( 'present' ), 'Second slide is present' );
+							resolve();
+						}, 100);
+					} );
+				});
+
+				QUnit.test( 'Fires slideschanged event when scrolling', assert => {
+					assert.timeout( 200 );
+					assert.expect( 2 );
+
+					const slides = document.querySelectorAll( '.reveal .slides section' );
+
+					return new Promise( resolve => {
+						let callback = ( event ) => {
+								Reveal.off( 'slidechanged', callback );
+								assert.ok( true, 'slidechanged event fired' );
+								assert.ok( event.currentSlide.classList.contains( 'present' ), 'slidechanged provides reference to currentSlide' );
+								resolve();
+							}
+
+							Reveal.on( 'slidechanged', callback );
+							Reveal.getViewportElement().scrollTop = getViewportHeight() * 2;
+					});
+				});
+
+			} );
+		</script>
+
+	</body>
+</html>

+ 65 - 67
test/test-state.html

@@ -39,97 +39,95 @@
 		<script src="../dist/reveal.js"></script>
 		<script>
 
-			Reveal.initialize().then( function() {
-				console.log(Reveal);
+			Reveal.initialize();
 
-				QUnit.module( 'State' );
+			QUnit.module( 'State' );
 
-				QUnit.test( 'Fire events when changing slide', function( assert ) {
-					assert.expect( 2 );
-					var state1 = assert.async();
-					var state2 = assert.async();
+			QUnit.test( 'Fire events when changing slide', function( assert ) {
+				assert.expect( 2 );
+				var state1 = assert.async();
+				var state2 = assert.async();
 
-					var _onState1 = function( event ) {
-						assert.ok( true, 'state1 fired' );
-						state1();
-					}
+				var _onState1 = function( event ) {
+					assert.ok( true, 'state1 fired' );
+					state1();
+				}
 
-					var _onState2 = function( event ) {
-						assert.ok( true, 'state2 fired' );
-						state2();
-					}
+				var _onState2 = function( event ) {
+					assert.ok( true, 'state2 fired' );
+					state2();
+				}
 
-					Reveal.on( 'state1', _onState1 );
-					Reveal.on( 'state2', _onState2 );
+				Reveal.on( 'state1', _onState1 );
+				Reveal.on( 'state2', _onState2 );
 
-					Reveal.slide( 1 );
-					Reveal.slide( 3 );
+				Reveal.slide( 1 );
+				Reveal.slide( 3 );
 
-					Reveal.off( 'state1', _onState1 );
-					Reveal.off( 'state2', _onState2 );
-				});
+				Reveal.off( 'state1', _onState1 );
+				Reveal.off( 'state2', _onState2 );
+			});
 
-				QUnit.test( 'Fire state events for vertical slides', function( assert ) {
-					assert.expect( 2 );
-					var done = assert.async( 2 );
+			QUnit.test( 'Fire state events for vertical slides', function( assert ) {
+				assert.expect( 2 );
+				var done = assert.async( 2 );
 
-					var _onState1 = function( event ) {
-						assert.ok( true, 'state1 fired' );
-						done();
-					}
+				var _onState1 = function( event ) {
+					assert.ok( true, 'state1 fired' );
+					done();
+				}
 
-					var _onState3 = function( event ) {
-						assert.ok( true, 'state3 fired' );
-						done();
-					}
+				var _onState3 = function( event ) {
+					assert.ok( true, 'state3 fired' );
+					done();
+				}
 
-					Reveal.on( 'state1', _onState1 );
-					Reveal.on( 'state3', _onState3 );
+				Reveal.on( 'state1', _onState1 );
+				Reveal.on( 'state3', _onState3 );
 
-					Reveal.slide( 0 );
-					Reveal.slide( 4, 1 );
-					Reveal.slide( 4, 2 );
+				Reveal.slide( 0 );
+				Reveal.slide( 4, 1 );
+				Reveal.slide( 4, 2 );
 
-					Reveal.off( 'state1', _onState1 );
-					Reveal.off( 'state3', _onState3 );
-				});
+				Reveal.off( 'state1', _onState1 );
+				Reveal.off( 'state3', _onState3 );
+			});
 
-				QUnit.test( 'No events if state remains unchanged', function( assert ) {
-					var stateChanges = 0;
+			QUnit.test( 'No events if state remains unchanged', function( assert ) {
+				var stateChanges = 0;
 
-					var _onEvent = function( event ) {
-						stateChanges += 1;
-					}
+				var _onEvent = function( event ) {
+					stateChanges += 1;
+				}
 
-					Reveal.on( 'state1', _onEvent );
+				Reveal.on( 'state1', _onEvent );
 
-					Reveal.slide( 0 );      // no state
-					Reveal.slide( 1 );      // state1
-					Reveal.slide( 2 );      // state1
-					Reveal.prev();          // state1
-					Reveal.next();          // state1
-					Reveal.slide( 4, 1 );   // state1
-					Reveal.slide( 0 );      // no state
+				Reveal.slide( 0 );      // no state
+				Reveal.slide( 1 );      // state1
+				Reveal.slide( 2 );      // state1
+				Reveal.prev();          // state1
+				Reveal.next();          // state1
+				Reveal.slide( 4, 1 );   // state1
+				Reveal.slide( 0 );      // no state
 
-					Reveal.off( 'state1', _onEvent );
+				Reveal.off( 'state1', _onEvent );
 
-					assert.strictEqual( stateChanges, 1, 'no event was fired when going to slide with same state' );
-				});
+				assert.strictEqual( stateChanges, 1, 'no event was fired when going to slide with same state' );
+			});
 
-				QUnit.test( 'Event order', function( assert ) {
-					var _onEvent = function( event ) {
-						assert.ok( Reveal.getCurrentSlide() == document.querySelector( '#slide2' ), 'correct current slide immediately after state event' );
-					}
+			QUnit.test( 'Event order', function( assert ) {
+				var _onEvent = function( event ) {
+					assert.ok( Reveal.getCurrentSlide() == document.querySelector( '#slide2' ), 'correct current slide immediately after state event' );
+				}
 
-					Reveal.on( 'state1', _onEvent );
+				Reveal.on( 'state1', _onEvent );
 
-					Reveal.slide( 0 );
-					Reveal.slide( 1 );
+				Reveal.slide( 0 );
+				Reveal.slide( 1 );
 
-					Reveal.off( 'state1', _onEvent );
-				});
+				Reveal.off( 'state1', _onEvent );
+			});
 
-			} );
 		</script>
 
 	</body>

+ 2 - 0
test/test.html

@@ -83,6 +83,8 @@
 
 		<script src="../dist/reveal.js"></script>
 		<script>
+			QUnit.config.testTimeout = 30000;
+
 			window.location.hash = '';
 
 			Reveal.configure({maxScale: 1.11});

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor