Update to reveal.js 4.6.1

Change-Id: I566737c50b93dd8c094ad3c3fa59865260b3b4ba
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/LICENSE b/inst/reveal.js-4.6.1/plugin/chalkboard/LICENSE
new file mode 100644
index 0000000..0eb3683
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2021 Asvin Goel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/README.md b/inst/reveal.js-4.6.1/plugin/chalkboard/README.md
new file mode 100644
index 0000000..8adefe5
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/README.md
@@ -0,0 +1,163 @@
+# Chalkboard
+
+With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:
+
+- you can make notes directly on the slides, e.g. to comment on certain aspects,
+- you can open a chalkboard or whiteboard on which you can make notes.
+
+The main use case in mind when implementing the plugin is classroom usage in which you may want to explain some course content and quickly need to make some notes.
+
+The plugin records all drawings made so that they can be play backed using the `autoSlide` feature or the `audio-slideshow` plugin.
+
+
+[Check out the demo](https://rajgoel.github.io/reveal.js-demos/?topic=chalkboard)
+
+## Setup
+
+To use the plugin include
+```html
+<!-- Font awesome is required for the chalkboard plugin -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
+<!-- Custom controls plugin is used to for opening and closing annotation modes. -->
+<script src="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/customcontrols/plugin.js"></script>
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/customcontrols/style.css">
+<!-- Chalkboard plugin -->
+<script src="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/chalkboard/plugin.js"></script>
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/chalkboard/style.css">
+```
+to the header of your presentation and configure reveal.js and the plugin by
+
+```js
+Reveal.initialize({
+  customcontrols: {
+    controls: [
+      { icon: '<i class="fa fa-pen-square"></i>',
+        title: 'Toggle chalkboard (B)',
+        action: 'RevealChalkboard.toggleChalkboard();'
+      },
+      { icon: '<i class="fa fa-pen"></i>',
+        title: 'Toggle notes canvas (C)',
+        action: 'RevealChalkboard.toggleNotesCanvas();'
+      }
+    ]
+  },
+  chalkboard: {
+    // add configuration here
+  },
+  // ...
+  plugins: [ RevealChalkboard, RevealCustomControls ],
+  // ...
+});
+```
+
+In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that `font-awesome` is available. The easiest way is to include
+```
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-plugins/menu/font-awesome/css/fontawesome.css">
+```
+to the ```head``` section of you HTML-file.
+
+## Usage
+
+### Mouse or touch
+- Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard
+- Click on the color picker at the left to change the color (the color picker is only visible if the notes canvas or chalkboard is active)
+- Click on the up/down arrows on the left to the switch among multiple chalkboardd (the up/down arrows are only available for the chlakboard)
+- Click the left mouse button and drag to write on notes canvas or chalkboard
+- Click the right mouse button and drag to wipe away previous drawings
+- Touch and move to write on notes canvas or chalkboard
+
+### Keyboard
+- Press the 'BACKSPACE' key to delete all chalkboard drawings
+- Press the 'DEL' key to clear the notes canvas or chalkboard
+- Press the 'c' key to toggle the notes canvas
+- Press the 'b' key to toggle the chalkboard
+- Press the 'd' key to download drawings
+- Press the 'x' key to cycle colors forward
+- Press the 'y' key to cycle colors backward
+
+## Playback
+
+If the `autoSlide` feature is set or if the `audio-slideshow` plugin is used, pre-recorded chalkboard drawings can be played. The slideshow plays back the user interaction with the chalkboard in the same way as it was conducted when recording the data.
+
+## Multiplexing
+
+The plugin supports multiplexing via the [`multiplex` plugin](https://github.com/reveal/multiplex) or the [`seminar` plugin](https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar).
+
+## PDF-Export
+
+If the slideshow is opened in [print mode](https://revealjs.com/pdf-export/), the chalkboard drawings in the session storage (see `storage` option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see `src` option) are included in the PDF-file. Each drawing on the chalkboard is added after the slide that was shown when opening the chalkboard. Drawings on the notes canvas are not included in the PDF-file.
+
+
+## Configuration
+
+The plugin has several configuration options:
+
+- ```boardmarkerWidth```: an integer, the drawing width of the boardmarker; larger values draw thicker lines.
+- ```chalkWidth```: an integer, the drawing width of the chalk; larger values draw thicker lines.
+- ```chalkEffect```: a float in the range ```[0.0, 1.0]```, the intesity of the chalk effect on the chalk board. Full effect (default) ```1.0```, no effect ```0.0```.
+- ```storage```: Optional variable name for session storage of drawings.
+- ```src```: Optional filename for pre-recorded drawings.
+- ```readOnly```: Configuation option allowing to prevent changes to existing drawings. If set to ```true``` no changes can be made, if set to false ```false``` changes can be made, if unset or set to ```undefined``` no changes to the drawings can be made after returning to a slide or fragment for which drawings had been recorded before. In any case the recorded drawings for a slide or fragment can be cleared by pressing the 'DEL' key (i.e. by using the ```RevealChalkboard.clear()``` function).
+- ```transition```: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.
+- ```theme```: Can be set to either ```"chalkboard"``` or ```"whiteboard"```.
+
+The following configuration options allow to change the appearance of the notes canvas and the chalkboard. All of these options require two values, the first gives the value for the notes canvas, the second for the chalkboard.
+
+- ```background```: The first value expects a (semi-)transparent color which is used to provide visual feedback that the notes canvas is enabled, the second value expects a filename to a background image for the chalkboard.
+- ```grid```: By default whiteboard and chalkboard themes include a grid pattern on the background. This pattern can be modified by setting the color, the distance between lines, and the line width, e.g. ```{ color: 'rgb(127,127,255,0.1)', distance: 40, width: 2}```. Alternatively, the grid can be removed by setting the value to ```false```.
+- ```eraser```: An image path and radius for the eraser.
+- ```boardmarkers```: A list of boardmarkers with given color and cursor.
+- ```chalks```: A list of chalks with given color and cursor.
+- ```rememberColor```: Whether to remember the last selected color for the slide canvas or the board.
+
+All of the configurations are optional and the default values shown below are used if the options are not provided.
+
+```javascript
+Reveal.initialize({
+	// ...
+    chalkboard: {
+        boardmarkerWidth: 3,
+        chalkWidth: 7,
+        chalkEffect: 1.0,
+        storage: null,
+        src: null,
+        readOnly: undefined,
+        transition: 800,
+        theme: "chalkboard",
+        background: [ 'rgba(127,127,127,.1)' , path + 'img/blackboard.png' ],
+        grid: { color: 'rgb(50,50,10,0.5)', distance: 80, width: 2},
+        eraser: { src: path + 'img/sponge.png', radius: 20},
+        boardmarkers : [
+                { color: 'rgba(100,100,100,1)', cursor: 'url(' + path + 'img/boardmarker-black.png), auto'},
+                { color: 'rgba(30,144,255, 1)', cursor: 'url(' + path + 'img/boardmarker-blue.png), auto'},
+                { color: 'rgba(220,20,60,1)', cursor: 'url(' + path + 'img/boardmarker-red.png), auto'},
+                { color: 'rgba(50,205,50,1)', cursor: 'url(' + path + 'img/boardmarker-green.png), auto'},
+                { color: 'rgba(255,140,0,1)', cursor: 'url(' + path + 'img/boardmarker-orange.png), auto'},
+                { color: 'rgba(150,0,20150,1)', cursor: 'url(' + path + 'img/boardmarker-purple.png), auto'},
+                { color: 'rgba(255,220,0,1)', cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto'}
+        ],
+        chalks: [
+                { color: 'rgba(255,255,255,0.5)', cursor: 'url(' + path + 'img/chalk-white.png), auto'},
+                { color: 'rgba(96, 154, 244, 0.5)', cursor: 'url(' + path + 'img/chalk-blue.png), auto'},
+                { color: 'rgba(237, 20, 28, 0.5)', cursor: 'url(' + path + 'img/chalk-red.png), auto'},
+                { color: 'rgba(20, 237, 28, 0.5)', cursor: 'url(' + path + 'img/chalk-green.png), auto'},
+                { color: 'rgba(220, 133, 41, 0.5)', cursor: 'url(' + path + 'img/chalk-orange.png), auto'},
+                { color: 'rgba(220,0,220,0.5)', cursor: 'url(' + path + 'img/chalk-purple.png), auto'},
+                { color: 'rgba(255,220,0,0.5)', cursor: 'url(' + path + 'img/chalk-yellow.png), auto'}
+        ]
+    },
+    // ...
+
+});
+```
+
+## Credits
+
+The chalkboard effect is based on [Chalkboard](https://github.com/mmoustafa/Chalkboard) by Mohamed Moustafa.
+
+## License
+
+MIT licensed
+
+Copyright (C) 2023 Asvin Goel
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/VERSION b/inst/reveal.js-4.6.1/plugin/chalkboard/VERSION
new file mode 100644
index 0000000..cf78d5b
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/VERSION
@@ -0,0 +1 @@
+4.2.4
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/blackboard.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/blackboard.png
new file mode 100644
index 0000000..50a2f64
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/blackboard.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-black.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-black.png
new file mode 100644
index 0000000..170b520
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-black.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-blue.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-blue.png
new file mode 100644
index 0000000..32f3ff8
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-blue.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-green.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-green.png
new file mode 100644
index 0000000..28a4221
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-green.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-orange.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-orange.png
new file mode 100644
index 0000000..7e8c2d2
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-orange.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-purple.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-purple.png
new file mode 100644
index 0000000..637066c
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-purple.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-red.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-red.png
new file mode 100644
index 0000000..713d6cd
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-red.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-yellow.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-yellow.png
new file mode 100644
index 0000000..23e87f5
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/boardmarker-yellow.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-blue.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-blue.png
new file mode 100644
index 0000000..f70c299
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-blue.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-green.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-green.png
new file mode 100644
index 0000000..39f3b20
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-green.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-orange.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-orange.png
new file mode 100644
index 0000000..488c847
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-orange.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-purple.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-purple.png
new file mode 100644
index 0000000..5bd29fb
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-purple.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-red.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-red.png
new file mode 100644
index 0000000..18d4dc7
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-red.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-white.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-white.png
new file mode 100644
index 0000000..fed89a3
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-white.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-yellow.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-yellow.png
new file mode 100644
index 0000000..0186bec
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/chalk-yellow.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/sponge.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/sponge.png
new file mode 100644
index 0000000..cbfb269
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/sponge.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/img/whiteboard.png b/inst/reveal.js-4.6.1/plugin/chalkboard/img/whiteboard.png
new file mode 100644
index 0000000..dbf570a
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/img/whiteboard.png
Binary files differ
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/plugin.js b/inst/reveal.js-4.6.1/plugin/chalkboard/plugin.js
new file mode 100644
index 0000000..44882d2
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/plugin.js
@@ -0,0 +1,1964 @@
+/*****************************************************************
+ ** Author: Asvin Goel, goel@telematique.eu
+ **
+ ** A plugin for reveal.js adding a chalkboard.
+ **
+ ** Version: 2.3.2
+ **
+ ** License: MIT license (see LICENSE.md)
+ **
+ ** Credits:
+ ** Chalkboard effect by Mohamed Moustafa https://github.com/mmoustafa/Chalkboard
+ ** Multi color support initially added by Kurt Rinnert https://github.com/rinnert
+ ** Compatibility with reveal.js v4 by Hakim El Hattab https://github.com/hakimel
+ ******************************************************************/
+
+"use strict";
+
+window.RevealChalkboard = window.RevealChalkboard || {
+	id: 'RevealChalkboard',
+	init: function ( deck ) {
+		initChalkboard.call(this, deck );
+	},
+	configure: function ( config ) {
+		configure( config );
+	},
+	toggleNotesCanvas: function () {
+		toggleNotesCanvas();
+	},
+	toggleChalkboard: function () {
+		toggleChalkboard();
+	},
+	colorIndex: function () {
+		colorIndex();
+	},
+	colorNext: function () {
+		colorNext();
+	},
+	colorPrev: function () {
+		colorPrev();
+	},
+	clear: function () {
+		clear();
+	},
+	reset: function () {
+		reset();
+	},
+	resetAll: function () {
+		resetAll();
+	},
+	updateStorage: function () {
+		updateStorage();
+	},
+	getData: function () {
+		return getData();
+	},
+	download: function () {
+		download();
+	},
+};
+
+function scriptPath() {
+	// obtain plugin path from the script element
+	var src;
+	if ( document.currentScript ) {
+		src = document.currentScript.src;
+	} else {
+		var sel = document.querySelector( 'script[src$="/chalkboard/plugin.js"]' )
+		if ( sel ) {
+			src = sel.src;
+		}
+	}
+	var path = ( src === undefined ) ? "" : src.slice( 0, src.lastIndexOf( "/" ) + 1 );
+//console.log("Path: " + path);
+	return path;
+}
+var path = scriptPath();
+
+const initChalkboard = function ( Reveal ) {
+//console.warn(path);
+	/* Feature detection for passive event handling*/
+	var passiveSupported = false;
+
+	try {
+		window.addEventListener( 'test', null, Object.defineProperty( {}, 'passive', {
+			get: function () {
+				passiveSupported = true;
+			}
+		} ) );
+	} catch ( err ) {}
+
+
+/*****************************************************************
+ ** Configuration
+ ******************************************************************/
+	var background, pens, draw, color;
+	var grid = false;
+	var boardmarkerWidth = 3;
+	var chalkWidth = 7;
+	var chalkEffect = 1.0;
+	var rememberColor = [ true, false ];
+	var eraser = {
+		src: path + 'img/sponge.png',
+		radius: 20
+	};
+	var boardmarkers = [ {
+			color: 'rgba(100,100,100,1)',
+			cursor: 'url(' + path + 'img/boardmarker-black.png), auto'
+		},
+		{
+			color: 'rgba(30,144,255, 1)',
+			cursor: 'url(' + path + 'img/boardmarker-blue.png), auto'
+		},
+		{
+			color: 'rgba(220,20,60,1)',
+			cursor: 'url(' + path + 'img/boardmarker-red.png), auto'
+		},
+		{
+			color: 'rgba(50,205,50,1)',
+			cursor: 'url(' + path + 'img/boardmarker-green.png), auto'
+		},
+		{
+			color: 'rgba(255,140,0,1)',
+			cursor: 'url(' + path + 'img/boardmarker-orange.png), auto'
+		},
+		{
+			color: 'rgba(150,0,20150,1)',
+			cursor: 'url(' + path + 'img/boardmarker-purple.png), auto'
+		},
+		{
+			color: 'rgba(255,220,0,1)',
+			cursor: 'url(' + path + 'img/boardmarker-yellow.png), auto'
+		}
+	];
+	var chalks = [ {
+			color: 'rgba(255,255,255,0.5)',
+			cursor: 'url(' + path + 'img/chalk-white.png), auto'
+		},
+		{
+			color: 'rgba(96, 154, 244, 0.5)',
+			cursor: 'url(' + path + 'img/chalk-blue.png), auto'
+		},
+		{
+			color: 'rgba(237, 20, 28, 0.5)',
+			cursor: 'url(' + path + 'img/chalk-red.png), auto'
+		},
+		{
+			color: 'rgba(20, 237, 28, 0.5)',
+			cursor: 'url(' + path + 'img/chalk-green.png), auto'
+		},
+		{
+			color: 'rgba(220, 133, 41, 0.5)',
+			cursor: 'url(' + path + 'img/chalk-orange.png), auto'
+		},
+		{
+			color: 'rgba(220,0,220,0.5)',
+			cursor: 'url(' + path + 'img/chalk-purple.png), auto'
+		},
+		{
+			color: 'rgba(255,220,0,0.5)',
+			cursor: 'url(' + path + 'img/chalk-yellow.png), auto'
+		}
+	];
+
+  var sponge = 		{
+		cursor: 'url(' + path + 'img/sponge.png), auto'
+	}
+
+
+	var keyBindings = {
+		toggleNotesCanvas: {
+			keyCode: 67,
+			key: 'C',
+			description: 'Toggle notes canvas'
+		},
+		toggleChalkboard: {
+			keyCode: 66,
+			key: 'B',
+			description: 'Toggle chalkboard'
+		},
+		clear: {
+			keyCode: 46,
+			key: 'DEL',
+			description: 'Clear drawings on slide'
+		},
+/*
+		reset: {
+			keyCode: 173,
+			key: '-',
+			description: 'Reset drawings on slide'
+		},
+*/
+		resetAll: {
+			keyCode: 8,
+			key: 'BACKSPACE',
+			description: 'Reset all drawings'
+		},
+		colorNext: {
+			keyCode: 88,
+			key: 'X',
+			description: 'Next color'
+		},
+		colorPrev: {
+			keyCode: 89,
+			key: 'Y',
+			description: 'Previous color'
+		},
+		download: {
+			keyCode: 68,
+			key: 'D',
+			description: 'Download drawings'
+		}
+	};
+
+
+	var theme = 'chalkboard';
+	var color = [ 0, 0 ];
+	var toggleChalkboardButton = false;
+	var toggleNotesButton = false;
+	var colorButtons = true;
+	var boardHandle = true;
+	var transition = 800;
+
+	var readOnly = false;
+	var messageType = 'broadcast';
+
+	var config = configure( Reveal.getConfig().chalkboard || {} );
+	if ( config.keyBindings ) {
+		for ( var key in config.keyBindings ) {
+			keyBindings[ key ] = config.keyBindings[ key ];
+		};
+	}
+
+	function configure( config ) {
+
+		if ( config.boardmarkerWidth || config.penWidth ) boardmarkerWidth = config.boardmarkerWidth || config.penWidth;
+		if ( config.chalkWidth ) chalkWidth = config.chalkWidth;
+		if ( config.chalkEffect ) chalkEffect = config.chalkEffect;
+		if ( config.rememberColor ) rememberColor = config.rememberColor;
+		if ( config.eraser ) eraser = config.eraser;
+		if ( config.boardmarkers ) boardmarkers = config.boardmarkers;
+		if ( config.chalks ) chalks = config.chalks;
+
+		if ( config.theme ) theme = config.theme;
+		switch ( theme ) {
+		case 'whiteboard':
+			background = [ 'rgba(127,127,127,.1)', path + 'img/whiteboard.png' ];
+			draw = [ drawWithBoardmarker, drawWithBoardmarker ];
+			pens = [ boardmarkers, boardmarkers ];
+			grid = {
+				color: 'rgb(127,127,255,0.1)',
+				distance: 40,
+				width: 2
+			};
+			break;
+		case 'chalkboard':
+		default:
+			background = [ 'rgba(127,127,127,.1)', path + 'img/blackboard.png' ];
+			draw = [ drawWithBoardmarker, drawWithChalk ];
+			pens = [ boardmarkers, chalks ];
+			grid = {
+				color: 'rgb(50,50,10,0.5)',
+				distance: 80,
+				width: 2
+			};
+		}
+
+		if ( config.background ) background = config.background;
+		if ( config.grid != undefined ) grid = config.grid;
+
+		if ( config.toggleChalkboardButton != undefined ) toggleChalkboardButton = config.toggleChalkboardButton;
+		if ( config.toggleNotesButton != undefined ) toggleNotesButton = config.toggleNotesButton;
+		if ( config.colorButtons != undefined ) colorButtons = config.colorButtons;
+		if ( config.boardHandle != undefined ) boardHandle = config.boardHandle;
+		if ( config.transition ) transition = config.transition;
+
+		if ( config.readOnly != undefined ) readOnly = config.readOnly;
+		if ( config.messageType ) messageType = config.messageType;
+
+		if ( drawingCanvas && ( config.theme || config.background || config.grid ) ) {
+			var canvas = document.getElementById( drawingCanvas[ 1 ].id );
+			canvas.style.background = 'url("' + background[ 1 ] + '") repeat';
+			clearCanvas( 1 );
+			drawGrid();
+		}
+
+		return config;
+	}
+/*****************************************************************
+ ** Setup
+ ******************************************************************/
+
+	function whenReady( callback ) {
+		// wait for markdown to be parsed and code to be highlighted
+		if ( !document.querySelector( 'section[data-markdown]:not([data-markdown-parsed])' ) 
+			   && !document.querySelector( '[data-load]:not([data-loaded])') 
+		     && !document.querySelector( 'code[data-line-numbers*="|"]') 	
+		) {
+			callback();
+		} else {
+			console.log( "Wait for external sources to be loaded and code to be highlighted" );
+			setTimeout( whenReady, 500, callback )
+		}
+	}
+
+	function whenLoaded( callback ) {
+		// wait for drawings to be loaded and markdown to be parsed
+		if ( loaded !== null ) {
+			callback();
+		} else {
+			console.log( "Wait for drawings to be loaded" );
+			setTimeout( whenLoaded, 500, callback )
+		}
+	}
+
+	var drawingCanvas = [ {
+		id: 'notescanvas'
+	}, {
+		id: 'chalkboard'
+	} ];
+	setupDrawingCanvas( 0 );
+	setupDrawingCanvas( 1 );
+
+	var mode = 0; // 0: notes canvas, 1: chalkboard
+	var board = 0; // board index (only for chalkboard)
+
+	var mouseX = 0;
+	var mouseY = 0;
+	var lastX = null;
+	var lastY = null;
+
+	var drawing = false;
+	var erasing = false;
+
+	var slideStart = Date.now();
+	var slideIndices = {
+		h: 0,
+		v: 0
+	};
+
+	var timeouts = [
+		[],
+		[]
+	];
+	var slidechangeTimeout = null;
+	var updateStorageTimeout = null;
+	var playback = false;
+
+  function changeCursor( element, tool ) {
+    element.style.cursor = tool.cursor;
+    var palette = document.querySelector('.palette[data-mode="' + mode + '"]');
+    if ( palette ) {
+      palette.style.cursor = tool.cursor;
+    }
+  }
+
+	function createPalette( colors, length ) {
+		if ( length === true || length > colors.length ) {
+			length = colors.length;
+		}
+		var palette = document.createElement( 'div' );
+		palette.classList.add( 'palette' );
+		var list = document.createElement( 'ul' );
+		// color pickers
+		for ( var i = 0; i < length; i++ ) {
+			var colorButton = document.createElement( 'li' );
+			colorButton.setAttribute( 'data-color', i );
+			colorButton.innerHTML = '<i class="fa fa-square"></i>';
+			colorButton.style.color = colors[ i ].color;
+			colorButton.addEventListener( 'click', function ( e ) {
+				var element = e.target;
+				while ( !element.hasAttribute( 'data-color' ) ) {
+					element = element.parentElement;
+				}
+				colorIndex( parseInt( element.getAttribute( 'data-color' ) ) );
+			} );
+			colorButton.addEventListener( 'touchstart', function ( e ) {
+				var element = e.target;
+				while ( !element.hasAttribute( 'data-color' ) ) {
+					element = element.parentElement;
+				}
+				colorIndex( parseInt( element.getAttribute( 'data-color' ) ) );
+			} );
+			list.appendChild( colorButton );
+		}
+    // eraser
+    var eraserButton = document.createElement( 'li' );
+		eraserButton.setAttribute( 'data-eraser', 'true' );
+		var spongeImg = document.createElement( 'img' );
+		spongeImg.src = eraser.src;
+    spongeImg.height = "24";
+    spongeImg.width = "24";
+    spongeImg.style.marginTop = '10px';
+    spongeImg.style.marginRight = '0';
+    spongeImg.style.marginBottom = '0';
+    spongeImg.style.marginLeft = '0';
+		eraserButton.appendChild(spongeImg);
+		eraserButton.addEventListener( 'click', function ( e ) {
+			colorIndex( -1 );
+		} );
+		eraserButton.addEventListener( 'touchstart', function ( e ) {
+			colorIndex( -1 );
+		} );
+		list.appendChild( eraserButton );
+
+		palette.appendChild( list );
+		return palette;
+	};
+
+	function switchBoard( boardIdx ) {
+		selectBoard( boardIdx, true );
+		// broadcast
+		var message = new CustomEvent( messageType );
+			message.content = {
+			sender: 'chalkboard-plugin',
+			type: 'selectboard',
+			timestamp: Date.now() - slideStart,
+			mode,
+			board
+		};
+		document.dispatchEvent( message );	
+	}
+
+	function setupDrawingCanvas( id ) {
+		var container = document.createElement( 'div' );
+		container.id = drawingCanvas[ id ].id;
+		container.classList.add( 'overlay' );
+		container.setAttribute( 'data-prevent-swipe', 'true' );
+		container.oncontextmenu = function () {
+			return false;
+		}
+
+    changeCursor( container, pens[ id ][ color[ id ] ] );
+
+		drawingCanvas[ id ].width = window.innerWidth;
+		drawingCanvas[ id ].height = window.innerHeight;
+		drawingCanvas[ id ].scale = 1;
+		drawingCanvas[ id ].xOffset = 0;
+		drawingCanvas[ id ].yOffset = 0;
+
+		if ( id == "0" ) {
+			container.style.background = 'rgba(0,0,0,0)';
+			container.style.zIndex = 24;
+			container.style.opacity = 1;
+			container.style.visibility = 'visible';
+			container.style.pointerEvents = 'none';
+
+			var slides = document.querySelector( '.slides' );
+			var aspectRatio = Reveal.getConfig().width / Reveal.getConfig().height;
+			if ( drawingCanvas[ id ].width > drawingCanvas[ id ].height * aspectRatio ) {
+				drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - drawingCanvas[ id ].height * aspectRatio ) / 2;
+			} else if ( drawingCanvas[ id ].height > drawingCanvas[ id ].width / aspectRatio ) {
+				drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - drawingCanvas[ id ].width / aspectRatio ) / 2;
+			}
+
+			if ( colorButtons ) {
+				var palette = createPalette( boardmarkers, colorButtons );
+        palette.dataset.mode = id;
+				palette.style.visibility = 'hidden'; // only show palette in drawing mode
+				container.appendChild( palette );
+			}
+		} else {
+			container.style.background = 'url("' + background[ id ] + '") repeat';
+			container.style.zIndex = 26;
+			container.style.opacity = 0;
+			container.style.visibility = 'hidden';
+
+			if ( colorButtons ) {
+				var palette = createPalette( chalks, colorButtons );
+        palette.dataset.mode = id;
+				container.appendChild( palette );
+			}
+			if ( boardHandle ) {
+				var handle = document.createElement( 'div' );
+				handle.classList.add( 'boardhandle' );
+				handle.innerHTML = '<ul><li><a id="previousboard" href="#" title="Previous board"><i class="fas fa-chevron-up"></i></a></li><li><a id="nextboard" href="#" title="Next board"><i class="fas fa-chevron-down"></i></a></li></ul>';
+				handle.querySelector( '#previousboard' ).addEventListener( 'click', function ( e ) {
+					e.preventDefault();
+					switchBoard( board - 1 );
+				} );
+				handle.querySelector( '#nextboard' ).addEventListener( 'click', function ( e ) {
+					e.preventDefault();
+					switchBoard( board + 1 );
+				} );
+				handle.querySelector( '#previousboard' ).addEventListener( 'touchstart', function ( e ) {
+					e.preventDefault();
+					switchBoard( board - 1 );
+				} );
+				handle.querySelector( '#nextboard' ).addEventListener( 'touchstart', function ( e ) {
+					e.preventDefault();
+					switchBoard( board + 1 );
+				} );
+
+				container.appendChild( handle );
+			}
+		}
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = drawingCanvas[ id ].width;
+		canvas.height = drawingCanvas[ id ].height;
+		canvas.setAttribute( 'data-chalkboard', id );
+    changeCursor( canvas, pens[ id ][ color[ id ] ] );
+		container.appendChild( canvas );
+		drawingCanvas[ id ].canvas = canvas;
+
+		drawingCanvas[ id ].context = canvas.getContext( '2d' );
+
+		setupCanvasEvents( container );
+
+		document.querySelector( '.reveal' ).appendChild( container );
+		drawingCanvas[ id ].container = container;
+	}
+
+
+/*****************************************************************
+ ** Storage
+ ******************************************************************/
+
+	var storage = [ {
+			width: Reveal.getConfig().width,
+			height: Reveal.getConfig().height,
+			data: []
+		},
+		{
+			width: Reveal.getConfig().width,
+			height: Reveal.getConfig().height,
+			data: []
+		}
+	];
+
+	var loaded = null;
+
+	if ( config.storage ) {
+		// Get chalkboard drawings from session storage
+		loaded = initStorage( sessionStorage.getItem( config.storage ) );
+	}
+
+	if ( !loaded && config.src != null ) {
+		// Get chalkboard drawings from the given file
+		loadData( config.src );
+	}
+
+	/**
+	 * Initialize storage.
+	 */
+	function initStorage( json ) {
+		var success = false;
+		try {
+			var data = JSON.parse( json );
+			for ( var id = 0; id < data.length; id++ ) {
+				if ( drawingCanvas[ id ].width != data[ id ].width || drawingCanvas[ id ].height != data[ id ].height ) {
+					drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / data[ id ].width, drawingCanvas[ id ].height / data[ id ].height );
+					drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - data[ id ].width * drawingCanvas[ id ].scale ) / 2;
+					drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - data[ id ].height * drawingCanvas[ id ].scale ) / 2;
+				}
+				if ( config.readOnly ) {
+					drawingCanvas[ id ].container.style.cursor = 'default';
+					drawingCanvas[ id ].canvas.style.cursor = 'default';
+				}
+			}
+			success = true;
+			storage = data;
+		} catch ( err ) {
+			console.warn( "Cannot initialise storage!" );
+		}
+		return success;
+	}
+
+
+	/**
+	 * Load data.
+	 */
+	function loadData( filename ) {
+		var xhr = new XMLHttpRequest();
+		xhr.onload = function () {
+			if ( xhr.readyState === 4 && xhr.status != 404 ) {
+				loaded = initStorage( xhr.responseText );
+				updateStorage();
+				console.log( "Drawings loaded from file" );
+			} else {
+				config.readOnly = undefined;
+				readOnly = undefined;
+				console.warn( 'Failed to get file ' + filename + '. ReadyState: ' + xhr.readyState + ', Status: ' + xhr.status );
+				loaded = false;
+			}
+		};
+
+		xhr.open( 'GET', filename, true );
+		try {
+			xhr.send();
+		} catch ( error ) {
+			config.readOnly = undefined;
+			readOnly = undefined;
+			console.warn( 'Failed to get file ' + filename + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + error );
+			loaded = false;
+		}
+	}
+
+
+	function storageChanged( now ) {
+		if ( !now ) {
+			// create or update timer
+			if ( updateStorageTimeout ) {
+				clearTimeout( updateStorageTimeout );
+			}
+			updateStorageTimeout = setTimeout( storageChanged, 1000, true);
+		}
+		else {
+// console.log("Update storage", updateStorageTimeout,  Date.now());
+			updateStorage();
+			updateStorageTimeout = null;
+		}
+	}
+
+	function updateStorage() {
+		var json = JSON.stringify( storage )
+		if ( config.storage ) {
+			sessionStorage.setItem( config.storage, json )
+		}
+		return json;
+	}
+
+	function recordEvent( event ) {
+//console.log(event);
+		event.time = Date.now() - slideStart;
+		if ( mode == 1 ) event.board = board;
+		var slideData = getSlideData();
+		var i = slideData.events.length;
+		while ( i > 0 && event.time < slideData.events[ i - 1 ].time ) {
+			i--;
+		}
+		slideData.events.splice( i, 0, event );
+		slideData.duration = Math.max( slideData.duration, Date.now() - slideStart ) + 1;
+
+		storageChanged();
+	}
+
+	/**
+	 * Get data as json string.
+	 */
+	function getData() {
+		// cleanup slide data without events
+		for ( var id = 0; id < 2; id++ ) {
+			for ( var i = storage[ id ].data.length - 1; i >= 0; i-- ) {
+				if ( storage[ id ].data[ i ].events.length == 0 ) {
+					storage[ id ].data.splice( i, 1 );
+				}
+			}
+		}
+
+		return updateStorage();
+	}
+
+	/**
+	 * Download data.
+	 */
+	function downloadData() {
+		var a = document.createElement( 'a' );
+		document.body.appendChild( a );
+		try {
+			a.download = 'chalkboard.json';
+			var blob = new Blob( [ getData() ], {
+				type: 'application/json'
+			} );
+			a.href = window.URL.createObjectURL( blob );
+		} catch ( error ) {
+			a.innerHTML += ' (' + error + ')';
+		}
+		a.click();
+		document.body.removeChild( a );
+	}
+
+	/**
+	 * Returns data object for the slide with the given indices.
+	 */
+	function getSlideData( indices, id ) {
+		if ( id == undefined ) id = mode;
+		if ( !indices ) indices = slideIndices;
+		var data;
+		for ( var i = 0; i < storage[ id ].data.length; i++ ) {
+			if ( storage[ id ].data[ i ].slide.h === indices.h && storage[ id ].data[ i ].slide.v === indices.v && storage[ id ].data[ i ].slide.f === indices.f ) {
+				data = storage[ id ].data[ i ];
+				return data;
+			}
+		}
+		var page = Number( Reveal.getCurrentSlide().getAttribute('data-pdf-page-number') ); 
+//console.log( indices, Reveal.getCurrentSlide() );
+		storage[ id ].data.push( {
+			slide: indices,
+			page,
+			events: [],
+			duration: 0
+		} );
+		data = storage[ id ].data[ storage[ id ].data.length - 1 ];
+		return data;
+	}
+
+	/**
+	 * Returns maximum duration of slide playback for both modes
+	 */
+	function getSlideDuration( indices ) {
+		if ( !indices ) indices = slideIndices;
+		var duration = 0;
+		for ( var id = 0; id < 2; id++ ) {
+			for ( var i = 0; i < storage[ id ].data.length; i++ ) {
+				if ( storage[ id ].data[ i ].slide.h === indices.h && storage[ id ].data[ i ].slide.v === indices.v && storage[ id ].data[ i ].slide.f === indices.f ) {
+					duration = Math.max( duration, storage[ id ].data[ i ].duration );
+					break;
+				}
+			}
+		}
+//console.log( duration );
+		return duration;
+	}
+
+/*****************************************************************
+ ** Print
+ ******************************************************************/
+	var printMode = ( /print-pdf/gi ).test( window.location.search );
+//console.log("createPrintout" + printMode)
+
+	function addPageNumbers() {
+		// determine page number for printouts with fragments serialised
+		var slides = Reveal.getSlides();
+		var page = 0;
+		for ( var i=0; i < slides.length; i++) {
+			slides[i].setAttribute('data-pdf-page-number',page.toString());
+			// add number of fragments without fragment indices
+			var count = slides[i].querySelectorAll('.fragment:not([data-fragment-index])').length;
+			var fragments = slides[i].querySelectorAll('.fragment[data-fragment-index]');
+			for ( var j=0; j < fragments.length; j++) {
+				// increasenumber of fragments by highest fragment index (which start at 0)
+				if ( Number(fragments[j].getAttribute('data-fragment-index')) + 1 > count ) {
+					count = Number(fragments[j].getAttribute('data-fragment-index')) + 1;
+				}
+			}
+			page += count + 1;
+		}
+	}
+
+	function createPrintout() {
+		//console.warn(Reveal.getTotalSlides(),Reveal.getSlidesElement());
+		if ( storage[ 1 ].data.length == 0 ) return;
+		console.log( 'Create printout(s) for ' + storage[ 1 ].data.length + " slides" );
+		drawingCanvas[ 0 ].container.style.opacity = 0; // do not print notes canvas
+		drawingCanvas[ 0 ].container.style.visibility = 'hidden';
+
+		var patImg = new Image();
+		patImg.onload = function () {
+			var slides = Reveal.getSlides();
+//console.log(slides);
+			for ( var i = storage[ 1 ].data.length - 1; i >= 0; i-- ) {
+				console.log( 'Create printout for slide ' + storage[ 1 ].data[ i ].slide.h + '.' + storage[ 1 ].data[ i ].slide.v );
+				var slideData = getSlideData( storage[ 1 ].data[ i ].slide, 1 );
+				var drawings = createDrawings( slideData, patImg );
+				addDrawings( slides[storage[ 1 ].data[ i ].page], drawings );
+
+			}
+//			Reveal.sync();
+		};
+		patImg.src = background[ 1 ];
+	}
+
+
+	function cloneCanvas( oldCanvas ) {
+		//create a new canvas
+		var newCanvas = document.createElement( 'canvas' );
+		var context = newCanvas.getContext( '2d' );
+		//set dimensions
+		newCanvas.width = oldCanvas.width;
+		newCanvas.height = oldCanvas.height;
+		//apply the old canvas to the new one
+		context.drawImage( oldCanvas, 0, 0 );
+		//return the new canvas
+		return newCanvas;
+	}
+
+	function getCanvas( template, container, board ) {
+		var idx = container.findIndex( element => element.board === board );
+		if ( idx === -1 ) {
+			var canvas = cloneCanvas( template );
+			if ( !container.length ) {
+				idx = 0;
+				container.push( {
+					board,
+					canvas
+				} );
+			} else if ( board < container[ 0 ].board ) {
+				idx = 0;
+				container.unshift( {
+					board,
+					canvas
+				} );
+			} else if ( board > container[ container.length - 1 ].board ) {
+				idx = container.length;
+				container.push( {
+					board,
+					canvas
+				} );
+			}
+		}
+
+		return container[ idx ].canvas;
+	}
+
+	function createDrawings( slideData, patImg ) {
+		var width = Reveal.getConfig().width;
+		var height = Reveal.getConfig().height;
+		var scale = 1;
+		var xOffset = 0;
+		var yOffset = 0;
+		if ( width != storage[ 1 ].width || height != storage[ 1 ].height ) {
+			scale = Math.min( width / storage[ 1 ].width, height / storage[ 1 ].height );
+			xOffset = ( width - storage[ 1 ].width * scale ) / 2;
+			yOffset = ( height - storage[ 1 ].height * scale ) / 2;
+		}
+		mode = 1;
+		board = 0;
+//		console.log( 'Create printout(s) for slide ', slideData );
+
+		var drawings = [];
+		var template = document.createElement( 'canvas' );
+		template.width = width;
+		template.height = height;
+
+		var imgCtx = template.getContext( '2d' );
+		imgCtx.fillStyle = imgCtx.createPattern( patImg, 'repeat' );
+		imgCtx.rect( 0, 0, width, height );
+		imgCtx.fill();
+
+		for ( var j = 0; j < slideData.events.length; j++ ) {
+			switch ( slideData.events[ j ].type ) {
+			case 'draw':
+				draw[ 1 ]( getCanvas( template, drawings, board ).getContext( '2d' ),
+					xOffset + slideData.events[ j ].x1 * scale,
+					yOffset + slideData.events[ j ].y1 * scale,
+					xOffset + slideData.events[ j ].x2 * scale,
+					yOffset + slideData.events[ j ].y2 * scale,
+					yOffset + slideData.events[ j ].color
+				);
+				break;
+			case 'erase':
+				eraseWithSponge( getCanvas( template, drawings, board ).getContext( '2d' ),
+					xOffset + slideData.events[ j ].x * scale,
+					yOffset + slideData.events[ j ].y * scale
+				);
+				break;
+			case 'selectboard':
+				selectBoard( slideData.events[ j ].board );
+				break;
+			case 'clear':
+				getCanvas( template, drawings, board ).getContext( '2d' ).clearRect( 0, 0, width, height );
+				getCanvas( template, drawings, board ).getContext( '2d' ).fill();
+				break;
+			default:
+				break;
+			}
+		}
+
+		drawings = drawings.sort( ( a, b ) => a.board > b.board && 1 || -1 );
+
+		mode = 0;
+
+		return drawings;
+	}
+
+	function addDrawings( slide, drawings ) {
+		var parent = slide.parentElement.parentElement;
+		var nextSlide = slide.parentElement.nextElementSibling;
+
+		for ( var i = 0; i < drawings.length; i++ ) {
+			var newPDFPage = document.createElement( 'div' );
+			newPDFPage.classList.add( 'pdf-page' );
+			newPDFPage.style.height = Reveal.getConfig().height;
+			newPDFPage.append( drawings[ i ].canvas );
+//console.log("Add drawing", newPDFPage);
+			if ( nextSlide != null ) {
+				parent.insertBefore( newPDFPage, nextSlide );
+			} else {
+				parent.append( newPDFPage );
+			}
+		}
+	}
+
+	/*****************************************************************
+	 ** Drawings
+	 ******************************************************************/
+
+	function drawWithBoardmarker( context, fromX, fromY, toX, toY, colorIdx ) {
+		if ( colorIdx == undefined ) colorIdx = color[ mode ];
+		context.lineWidth = boardmarkerWidth;
+		context.lineCap = 'round';
+		context.strokeStyle = boardmarkers[ colorIdx ].color;
+		context.beginPath();
+		context.moveTo( fromX, fromY );
+		context.lineTo( toX, toY );
+		context.stroke();
+	}
+
+	function drawWithChalk( context, fromX, fromY, toX, toY, colorIdx ) {
+		if ( colorIdx == undefined ) colorIdx = color[ mode ];
+		var brushDiameter = chalkWidth;
+		context.lineWidth = brushDiameter;
+		context.lineCap = 'round';
+		context.fillStyle = chalks[ colorIdx ].color; // 'rgba(255,255,255,0.5)';
+		context.strokeStyle = chalks[ colorIdx ].color;
+
+		var opacity = 1.0;
+		context.strokeStyle = context.strokeStyle.replace( /[\d\.]+\)$/g, opacity + ')' );
+		context.beginPath();
+		context.moveTo( fromX, fromY );
+		context.lineTo( toX, toY );
+		context.stroke();
+		// Chalk Effect
+		var length = Math.round( Math.sqrt( Math.pow( toX - fromX, 2 ) + Math.pow( toY - fromY, 2 ) ) / ( 5 / brushDiameter ) );
+		var xUnit = ( toX - fromX ) / length;
+		var yUnit = ( toY - fromY ) / length;
+		for ( var i = 0; i < length; i++ ) {
+			if ( chalkEffect > ( Math.random() * 0.9 ) ) {
+				var xCurrent = fromX + ( i * xUnit );
+				var yCurrent = fromY + ( i * yUnit );
+				var xRandom = xCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
+				var yRandom = yCurrent + ( Math.random() - 0.5 ) * brushDiameter * 1.2;
+				context.clearRect( xRandom, yRandom, Math.random() * 2 + 2, Math.random() + 1 );
+			}
+		}
+	}
+ 
+	function eraseWithSponge( context, x, y ) {
+		context.save();
+		context.beginPath();
+		context.arc( x + eraser.radius, y + eraser.radius, eraser.radius, 0, 2 * Math.PI, false );
+		context.clip();
+		context.clearRect( x - 1, y - 1, eraser.radius * 2 + 2, eraser.radius * 2 + 2 );
+		context.restore();
+		if ( mode == 1 && grid ) {
+			redrawGrid( x + eraser.radius, y + eraser.radius, eraser.radius );
+		}
+	}
+
+
+	/**
+	 * Show an overlay for the chalkboard.
+	 */
+	function showChalkboard() {
+//console.log("showChalkboard");
+		drawingCanvas[ 1 ].container.style.opacity = 1;
+		drawingCanvas[ 1 ].container.style.visibility = 'visible';
+		mode = 1;
+	}
+
+
+	/**
+	 * Closes open chalkboard.
+	 */
+	function closeChalkboard() {
+		drawingCanvas[ 1 ].container.style.opacity = 0;
+		drawingCanvas[ 1 ].container.style.visibility = 'hidden';
+		lastX = null;
+		lastY = null;
+		mode = 0;
+	}
+
+	/**
+	 * Clear current canvas.
+	 */
+	function clearCanvas( id ) {
+		if ( id == 0 ) clearTimeout( slidechangeTimeout );
+		drawingCanvas[ id ].context.clearRect( 0, 0, drawingCanvas[ id ].width, drawingCanvas[ id ].height );
+		if ( id == 1 && grid ) drawGrid();
+	}
+
+	/**
+	 * Draw grid on background
+	 */
+	function drawGrid() {
+		var context = drawingCanvas[ 1 ].context;
+
+		drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
+		drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
+		drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
+
+		var scale = drawingCanvas[ 1 ].scale;
+		var xOffset = drawingCanvas[ 1 ].xOffset;
+		var yOffset = drawingCanvas[ 1 ].yOffset;
+
+		var distance = grid.distance * scale;
+
+		var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
+		for ( var x = fromX; x < drawingCanvas[ 1 ].width; x += distance ) {
+			context.beginPath();
+			context.lineWidth = grid.width * scale;
+			context.lineCap = 'round';
+			context.fillStyle = grid.color;
+			context.strokeStyle = grid.color;
+			context.moveTo( x, 0 );
+			context.lineTo( x, drawingCanvas[ 1 ].height );
+			context.stroke();
+		}
+		var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
+
+		for ( var y = fromY; y < drawingCanvas[ 1 ].height; y += distance ) {
+			context.beginPath();
+			context.lineWidth = grid.width * scale;
+			context.lineCap = 'round';
+			context.fillStyle = grid.color;
+			context.strokeStyle = grid.color;
+			context.moveTo( 0, y );
+			context.lineTo( drawingCanvas[ 1 ].width, y );
+			context.stroke();
+		}
+	}
+
+	function redrawGrid( centerX, centerY, diameter ) {
+		var context = drawingCanvas[ 1 ].context;
+
+		drawingCanvas[ 1 ].scale = Math.min( drawingCanvas[ 1 ].width / storage[ 1 ].width, drawingCanvas[ 1 ].height / storage[ 1 ].height );
+		drawingCanvas[ 1 ].xOffset = ( drawingCanvas[ 1 ].width - storage[ 1 ].width * drawingCanvas[ 1 ].scale ) / 2;
+		drawingCanvas[ 1 ].yOffset = ( drawingCanvas[ 1 ].height - storage[ 1 ].height * drawingCanvas[ 1 ].scale ) / 2;
+
+		var scale = drawingCanvas[ 1 ].scale;
+		var xOffset = drawingCanvas[ 1 ].xOffset;
+		var yOffset = drawingCanvas[ 1 ].yOffset;
+
+		var distance = grid.distance * scale;
+
+		var fromX = drawingCanvas[ 1 ].width / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].width - distance ) / 2 / distance ) * distance;
+
+		for ( var x = fromX + distance * Math.ceil( ( centerX - diameter - fromX ) / distance ); x <= fromX + distance * Math.floor( ( centerX + diameter - fromX ) / distance ); x += distance ) {
+			context.beginPath();
+			context.lineWidth = grid.width * scale;
+			context.lineCap = 'round';
+			context.fillStyle = grid.color;
+			context.strokeStyle = grid.color;
+			context.moveTo( x, centerY - Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
+			context.lineTo( x, centerY + Math.sqrt( diameter * diameter - ( centerX - x ) * ( centerX - x ) ) );
+			context.stroke();
+		}
+		var fromY = drawingCanvas[ 1 ].height / 2 - distance / 2 - Math.floor( ( drawingCanvas[ 1 ].height - distance ) / 2 / distance ) * distance;
+		for ( var y = fromY + distance * Math.ceil( ( centerY - diameter - fromY ) / distance ); y <= fromY + distance * Math.floor( ( centerY + diameter - fromY ) / distance ); y += distance ) {
+			context.beginPath();
+			context.lineWidth = grid.width * scale;
+			context.lineCap = 'round';
+			context.fillStyle = grid.color;
+			context.strokeStyle = grid.color;
+			context.moveTo( centerX - Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
+			context.lineTo( centerX + Math.sqrt( diameter * diameter - ( centerY - y ) * ( centerY - y ) ), y );
+			context.stroke();
+		}
+	}
+
+	/**
+	 * Set the  color
+	 */
+	function setColor( index, record ) {    
+ 		// protect against out of bounds (this could happen when
+  	// replaying events recorded with different color settings).
+    if ( index >= pens[ mode ].length ) index = 0;
+
+	  color[ mode ] = index;
+
+    if ( color[ mode ] < 0 ) {
+      // use eraser
+      changeCursor( drawingCanvas[ mode ].canvas, sponge );
+    }
+    else {
+      changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
+    }
+	}
+
+	/**
+	 * Set the  board
+	 */
+	function selectBoard( boardIdx, record ) {
+//console.log("Set board",boardIdx);
+		if ( board == boardIdx ) return;
+
+		board = boardIdx;
+		redrawChalkboard( boardIdx );
+		if ( record ) {
+			recordEvent( { type: 'selectboard' } );
+		}
+	}
+
+	function redrawChalkboard( boardIdx ) {
+		clearCanvas( 1 );
+		var slideData = getSlideData( slideIndices, 1 );
+		var index = 0;
+		var play = ( boardIdx == 0 );
+		while ( index < slideData.events.length && slideData.events[ index ].time < Date.now() - slideStart ) {
+			if ( boardIdx == slideData.events[ index ].board ) {
+				playEvent( 1, slideData.events[ index ], Date.now() - slideStart );
+			}
+
+			index++;
+		}
+	}
+
+
+	/**
+	 * Forward cycle color
+	 */
+	function cycleColorNext() {
+		color[ mode ] = ( color[ mode ] + 1 ) % pens[ mode ].length;
+		return color[ mode ];
+	}
+
+	/**
+	 * Backward cycle color
+	 */
+	function cycleColorPrev() {
+		color[ mode ] = ( color[ mode ] + ( pens[ mode ].length - 1 ) ) % pens[ mode ].length;
+		return color[ mode ];
+	}
+
+/*****************************************************************
+ ** Broadcast
+ ******************************************************************/
+
+	var eventQueue = [];
+
+	document.addEventListener( 'received', function ( message ) {
+		if ( message.content && message.content.sender == 'chalkboard-plugin' ) {
+			// add message to queue
+			eventQueue.push( message );
+			console.log( JSON.stringify( message ) );
+		}
+		if ( eventQueue.length == 1 ) processQueue();
+	} );
+
+	function processQueue() {
+		// take first message from queue
+		var message = eventQueue.shift();
+
+		// synchronize time with seminar host
+		slideStart = Date.now() - message.content.timestamp;
+		// set status
+		if ( mode < message.content.mode ) {
+			// open chalkboard
+			showChalkboard();
+		} else if ( mode > message.content.mode ) {
+			// close chalkboard
+			closeChalkboard();
+		}
+		if ( board != message.content.board ) {
+			board = message.content.board;
+			redrawChalkboard( board );
+		};
+
+		switch ( message.content.type ) {
+		case 'showChalkboard':
+			showChalkboard();
+			break;
+		case 'closeChalkboard':
+			closeChalkboard();
+			break;
+		case 'erase':
+			erasePoint( message.content.x, message.content.y );
+			break;
+		case 'draw':
+			drawSegment( message.content.fromX, message.content.fromY, message.content.toX, message.content.toY, message.content.color );
+			break;
+		case 'clear':
+			clearSlide();
+			break;
+		case 'selectboard':
+			selectBoard( message.content.board, true );
+			break;
+		case 'resetSlide':
+			resetSlideDrawings();
+			break;
+		case 'init':
+			storage = message.content.storage;
+			for ( var id = 0; id < 2; id++ ) {
+				drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
+				drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
+				drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
+			}
+			clearCanvas( 0 );
+			clearCanvas( 1 );
+			if ( !playback ) {
+				slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
+			}
+			if ( mode == 1 && message.content.mode == 0 ) {
+				setTimeout( closeChalkboard, transition + 50 );
+			}
+			if ( mode == 0 && message.content.mode == 1 ) {
+				setTimeout( showChalkboard, transition + 50 );
+			}
+			mode = message.content.mode;
+			board = message.content.board;
+			break;
+		default:
+			break;
+		}
+
+		// continue with next message if queued
+		if ( eventQueue.length > 0 ) {
+			processQueue();
+		} else {
+			storageChanged();
+		}
+	}
+
+	document.addEventListener( 'welcome', function ( user ) {
+		// broadcast storage
+		var message = new CustomEvent( messageType );
+		message.content = {
+			sender: 'chalkboard-plugin',
+			recipient: user.id,
+			type: 'init',
+			timestamp: Date.now() - slideStart,
+			storage: storage,
+			mode,
+			board
+		};
+		document.dispatchEvent( message );
+	} );
+
+	/*****************************************************************
+	 ** Playback
+	 ******************************************************************/
+
+	document.addEventListener( 'seekplayback', function ( event ) {
+//console.log('event seekplayback ' + event.timestamp);
+		stopPlayback();
+		if ( !playback || event.timestamp == 0 ) {
+			// in other cases startplayback fires after seeked
+			startPlayback( event.timestamp );
+		}
+		//console.log('seeked');
+	} );
+
+
+	document.addEventListener( 'startplayback', function ( event ) {
+//console.log('event startplayback ' + event.timestamp);
+		stopPlayback();
+		playback = true;
+		startPlayback( event.timestamp );
+	} );
+
+	document.addEventListener( 'stopplayback', function ( event ) {
+//console.log('event stopplayback ' + (Date.now() - slideStart) );
+		playback = false;
+		stopPlayback();
+	} );
+
+	document.addEventListener( 'startrecording', function ( event ) {
+//console.log('event startrecording ' + event.timestamp);
+		startRecording();
+	} );
+
+
+	function startRecording() {
+		resetSlide( true );
+		slideStart = Date.now();
+	}
+
+	function startPlayback( timestamp, finalMode ) {
+//console.log("playback " + timestamp );
+		slideStart = Date.now() - timestamp;
+		closeChalkboard();
+		mode = 0;
+		board = 0;
+		for ( var id = 0; id < 2; id++ ) {
+			clearCanvas( id );
+			var slideData = getSlideData( slideIndices, id );
+//console.log( timestamp +" / " + JSON.stringify(slideData));
+			var index = 0;
+			while ( index < slideData.events.length && slideData.events[ index ].time < ( Date.now() - slideStart ) ) {
+				playEvent( id, slideData.events[ index ], timestamp );
+				index++;
+			}
+
+			while ( playback && index < slideData.events.length ) {
+				timeouts[ id ].push( setTimeout( playEvent, slideData.events[ index ].time - ( Date.now() - slideStart ), id, slideData.events[ index ], timestamp ) );
+				index++;
+			}
+		}
+//console.log("Mode: " + finalMode + "/" + mode );
+		if ( finalMode != undefined ) {
+			mode = finalMode;
+		}
+		if ( mode == 1 ) showChalkboard();
+//console.log("playback (ok)");
+
+	};
+
+	function stopPlayback() {
+//console.log("stopPlayback");
+//console.log("Timeouts: " + timeouts[0].length + "/"+ timeouts[1].length);
+		for ( var id = 0; id < 2; id++ ) {
+			for ( var i = 0; i < timeouts[ id ].length; i++ ) {
+				clearTimeout( timeouts[ id ][ i ] );
+			}
+			timeouts[ id ] = [];
+		}
+	};
+
+	function playEvent( id, event, timestamp ) {
+//console.log( timestamp +" / " + JSON.stringify(event));
+//console.log( id + ": " + timestamp +" / " +  event.time +" / " + event.type +" / " + mode );
+		switch ( event.type ) {
+		case 'open':
+			if ( timestamp <= event.time ) {
+				showChalkboard();
+			} else {
+				mode = 1;
+			}
+
+			break;
+		case 'close':
+			if ( timestamp < event.time ) {
+				closeChalkboard();
+			} else {
+				mode = 0;
+			}
+			break;
+		case 'clear':
+			clearCanvas( id );
+			break;
+		case 'selectboard':
+			selectBoard( event.board );
+			break;
+		case 'draw':
+			drawLine( id, event, timestamp );
+			break;
+		case 'erase':
+			eraseCircle( id, event, timestamp );
+			break;
+		}
+	};
+
+	function drawLine( id, event, timestamp ) {
+		var ctx = drawingCanvas[ id ].context;
+		var scale = drawingCanvas[ id ].scale;
+		var xOffset = drawingCanvas[ id ].xOffset;
+		var yOffset = drawingCanvas[ id ].yOffset;
+		draw[ id ]( ctx, xOffset + event.x1 * scale, yOffset + event.y1 * scale, xOffset + event.x2 * scale, yOffset + event.y2 * scale, event.color );
+	};
+
+	function eraseCircle( id, event, timestamp ) {
+		var ctx = drawingCanvas[ id ].context;
+		var scale = drawingCanvas[ id ].scale;
+		var xOffset = drawingCanvas[ id ].xOffset;
+		var yOffset = drawingCanvas[ id ].yOffset;
+
+		eraseWithSponge( ctx, xOffset + event.x * scale, yOffset + event.y * scale );
+	};
+
+	function startErasing( x, y ) {
+		drawing = false;
+		erasing = true;
+		erasePoint( x, y );
+	}
+
+	function erasePoint( x, y ) {
+		var ctx = drawingCanvas[ mode ].context;
+		var scale = drawingCanvas[ mode ].scale;
+		var xOffset = drawingCanvas[ mode ].xOffset;
+		var yOffset = drawingCanvas[ mode ].yOffset;
+
+		recordEvent( {
+			type: 'erase',
+			x,
+			y
+		} );
+
+		if (
+			x * scale + xOffset > 0 &&
+			y * scale + yOffset > 0 &&
+			x * scale + xOffset < drawingCanvas[ mode ].width &&
+			y * scale + yOffset < drawingCanvas[ mode ].height
+		) {
+			eraseWithSponge( ctx, x * scale + xOffset, y * scale + yOffset );
+		}
+	}
+
+	function stopErasing() {
+		erasing = false;
+	}
+
+	function startDrawing( x, y ) {
+		drawing = true;
+
+		var ctx = drawingCanvas[ mode ].context;
+		var scale = drawingCanvas[ mode ].scale;
+		var xOffset = drawingCanvas[ mode ].xOffset;
+		var yOffset = drawingCanvas[ mode ].yOffset;
+		lastX = x * scale + xOffset;
+		lastY = y * scale + yOffset;
+	}
+
+	function drawSegment( fromX, fromY, toX, toY, colorIdx ) {
+		var ctx = drawingCanvas[ mode ].context;
+		var scale = drawingCanvas[ mode ].scale;
+		var xOffset = drawingCanvas[ mode ].xOffset;
+		var yOffset = drawingCanvas[ mode ].yOffset;
+
+		recordEvent( {
+			type: 'draw',
+			color: colorIdx,
+			x1: fromX,
+			y1: fromY,
+			x2: toX,
+			y2: toY
+		} );
+
+		if (
+			fromX * scale + xOffset > 0 &&
+			fromY * scale + yOffset > 0 &&
+			fromX * scale + xOffset < drawingCanvas[ mode ].width &&
+			fromY * scale + yOffset < drawingCanvas[ mode ].height &&
+			toX * scale + xOffset > 0 &&
+			toY * scale + yOffset > 0 &&
+			toX * scale + xOffset < drawingCanvas[ mode ].width &&
+			toY * scale + yOffset < drawingCanvas[ mode ].height
+		) {
+			draw[ mode ]( ctx, fromX * scale + xOffset, fromY * scale + yOffset, toX * scale + xOffset, toY * scale + yOffset, colorIdx );
+		}
+	}
+
+	function stopDrawing() {
+		drawing = false;
+	}
+
+
+/*****************************************************************
+ ** User interface
+ ******************************************************************/
+
+	function setupCanvasEvents( canvas ) {
+// TODO: check all touchevents
+		canvas.addEventListener( 'touchstart', function ( evt ) {
+			evt.preventDefault();
+//console.log("Touch start");
+			if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
+				var scale = drawingCanvas[ mode ].scale;
+				var xOffset = drawingCanvas[ mode ].xOffset;
+				var yOffset = drawingCanvas[ mode ].yOffset;
+
+				var touch = evt.touches[ 0 ];
+				mouseX = touch.pageX;
+				mouseY = touch.pageY;
+        if ( color[ mode ]  < 0 ) {
+          startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale);
+        }
+        else {
+  				startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+        }
+			}
+		}, passiveSupported ? {
+			passive: false
+		} : false );
+
+		canvas.addEventListener( 'touchmove', function ( evt ) {
+			evt.preventDefault();
+//console.log("Touch move");
+			if ( drawing || erasing ) {
+				var scale = drawingCanvas[ mode ].scale;
+				var xOffset = drawingCanvas[ mode ].xOffset;
+				var yOffset = drawingCanvas[ mode ].yOffset;
+
+				var touch = evt.touches[ 0 ];
+				mouseX = touch.pageX;
+				mouseY = touch.pageY;
+
+				if ( drawing ) {
+					drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
+					// broadcast
+					var message = new CustomEvent( messageType );
+					message.content = {
+						sender: 'chalkboard-plugin',
+						type: 'draw',
+						timestamp: Date.now() - slideStart,
+						mode,
+						board,
+						fromX: ( lastX - xOffset ) / scale,
+						fromY: ( lastY - yOffset ) / scale,
+						toX: ( mouseX - xOffset ) / scale,
+						toY: ( mouseY - yOffset ) / scale,
+						color: color[ mode ]
+					};
+					document.dispatchEvent( message );
+
+					lastX = mouseX;
+					lastY = mouseY;
+				} else {
+					erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+					// broadcast
+					var message = new CustomEvent( messageType );
+					message.content = {
+						sender: 'chalkboard-plugin',
+						type: 'erase',
+						timestamp: Date.now() - slideStart,
+						mode,
+						board,
+						x: ( mouseX - xOffset ) / scale,
+						y: ( mouseY - yOffset ) / scale
+					};
+					document.dispatchEvent( message );
+				}
+
+			}
+		}, false );
+
+
+		canvas.addEventListener( 'touchend', function ( evt ) {
+			evt.preventDefault();
+			stopDrawing();
+			stopErasing();
+		}, false );
+
+		canvas.addEventListener( 'mousedown', function ( evt ) {
+			evt.preventDefault();
+			if ( !readOnly && evt.target.getAttribute( 'data-chalkboard' ) == mode ) {
+//console.log( "mousedown: " + evt.button );
+				var scale = drawingCanvas[ mode ].scale;
+				var xOffset = drawingCanvas[ mode ].xOffset;
+				var yOffset = drawingCanvas[ mode ].yOffset;
+
+				mouseX = evt.pageX;
+				mouseY = evt.pageY;
+
+				if ( color[ mode ]  < 0 || evt.button == 2 || evt.button == 1 ) {
+          if ( color[ mode ]  >= 0 ) {
+            // show sponge
+            changeCursor( drawingCanvas[ mode ].canvas, sponge );
+          }
+					startErasing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+					// broadcast
+					var message = new CustomEvent( messageType );
+					message.content = {
+						sender: 'chalkboard-plugin',
+						type: 'erase',
+						timestamp: Date.now() - slideStart,
+						mode,
+						board,
+						x: ( mouseX - xOffset ) / scale,
+						y: ( mouseY - yOffset ) / scale
+					};
+					document.dispatchEvent( message );
+				} else {
+					startDrawing( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+				}
+			}
+		} );
+
+		canvas.addEventListener( 'mousemove', function ( evt ) {
+			evt.preventDefault();
+//console.log("Mouse move");
+
+			var scale = drawingCanvas[ mode ].scale;
+			var xOffset = drawingCanvas[ mode ].xOffset;
+			var yOffset = drawingCanvas[ mode ].yOffset;
+
+			mouseX = evt.pageX;
+			mouseY = evt.pageY;
+
+			if ( drawing || erasing ) {
+				var scale = drawingCanvas[ mode ].scale;
+				var xOffset = drawingCanvas[ mode ].xOffset;
+				var yOffset = drawingCanvas[ mode ].yOffset;
+
+				mouseX = evt.pageX;
+				mouseY = evt.pageY;
+
+				if ( drawing ) {
+					drawSegment( ( lastX - xOffset ) / scale, ( lastY - yOffset ) / scale, ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale, color[ mode ] );
+					// broadcast
+					var message = new CustomEvent( messageType );
+					message.content = {
+						sender: 'chalkboard-plugin',
+						type: 'draw',
+						timestamp: Date.now() - slideStart,
+						mode,
+						board,
+						fromX: ( lastX - xOffset ) / scale,
+						fromY: ( lastY - yOffset ) / scale,
+						toX: ( mouseX - xOffset ) / scale,
+						toY: ( mouseY - yOffset ) / scale,
+						color: color[ mode ]
+					};
+					document.dispatchEvent( message );
+
+					lastX = mouseX;
+					lastY = mouseY;
+				} else {
+					erasePoint( ( mouseX - xOffset ) / scale, ( mouseY - yOffset ) / scale );
+					// broadcast
+					var message = new CustomEvent( messageType );
+					message.content = {
+						sender: 'chalkboard-plugin',
+						type: 'erase',
+						timestamp: Date.now() - slideStart,
+						mode,
+						board,
+						x: ( mouseX - xOffset ) / scale,
+						y: ( mouseY - yOffset ) / scale
+					};
+					document.dispatchEvent( message );
+				}
+
+			}
+		} );
+
+
+		canvas.addEventListener( 'mouseup', function ( evt ) {
+			evt.preventDefault();
+      if ( color[ mode ] >= 0 ) {
+        changeCursor( drawingCanvas[ mode ].canvas, pens[ mode ][ color[ mode ] ] );
+      }
+			if ( drawing || erasing ) {
+				stopDrawing();
+				stopErasing();
+			}
+		} );
+	}
+
+	function resize() {
+//console.log("resize");
+		// Resize the canvas and draw everything again
+		var timestamp = Date.now() - slideStart;
+		if ( !playback ) {
+			timestamp = getSlideDuration();
+		}
+
+//console.log( drawingCanvas[0].scale + "/" + drawingCanvas[0].xOffset + "/" +drawingCanvas[0].yOffset );
+		for ( var id = 0; id < 2; id++ ) {
+			drawingCanvas[ id ].width = window.innerWidth;
+			drawingCanvas[ id ].height = window.innerHeight;
+			drawingCanvas[ id ].canvas.width = drawingCanvas[ id ].width;
+			drawingCanvas[ id ].canvas.height = drawingCanvas[ id ].height;
+			drawingCanvas[ id ].context.canvas.width = drawingCanvas[ id ].width;
+			drawingCanvas[ id ].context.canvas.height = drawingCanvas[ id ].height;
+
+			drawingCanvas[ id ].scale = Math.min( drawingCanvas[ id ].width / storage[ id ].width, drawingCanvas[ id ].height / storage[ id ].height );
+			drawingCanvas[ id ].xOffset = ( drawingCanvas[ id ].width - storage[ id ].width * drawingCanvas[ id ].scale ) / 2;
+			drawingCanvas[ id ].yOffset = ( drawingCanvas[ id ].height - storage[ id ].height * drawingCanvas[ id ].scale ) / 2;
+//console.log( drawingCanvas[id].scale + "/" + drawingCanvas[id].xOffset + "/" +drawingCanvas[id].yOffset );
+		}
+//console.log( window.innerWidth + "/" + window.innerHeight);
+		startPlayback( timestamp, mode, true );
+	}
+
+	Reveal.addEventListener( 'pdf-ready', function ( evt ) {
+//		console.log( "Create printouts when ready" );
+		whenLoaded( createPrintout );
+	});
+
+	Reveal.addEventListener( 'ready', function ( evt ) {
+//console.log('ready');
+		if ( !printMode ) {
+			window.addEventListener( 'resize', resize );
+
+			slideStart = Date.now() - getSlideDuration();
+			slideIndices = Reveal.getIndices();
+			if ( !playback ) {
+				startPlayback( getSlideDuration(), 0 );
+			}
+			if ( Reveal.isAutoSliding() ) {
+				var event = new CustomEvent( 'startplayback' );
+				event.timestamp = 0;
+				document.dispatchEvent( event );
+			}
+			updateStorage();
+			whenReady( addPageNumbers );
+		}
+	} );
+	Reveal.addEventListener( 'slidechanged', function ( evt ) {
+//		clearTimeout( slidechangeTimeout );
+//console.log('slidechanged');
+		if ( !printMode ) {
+			slideStart = Date.now() - getSlideDuration();
+			slideIndices = Reveal.getIndices();
+			closeChalkboard();
+			board = 0;
+			clearCanvas( 0 );
+			clearCanvas( 1 );
+			if ( !playback ) {
+				slidechangeTimeout = setTimeout( startPlayback, transition, getSlideDuration(), 0 );
+			}
+			if ( Reveal.isAutoSliding() ) {
+				var event = new CustomEvent( 'startplayback' );
+				event.timestamp = 0;
+				document.dispatchEvent( event );
+			}
+		}
+	} );
+	Reveal.addEventListener( 'fragmentshown', function ( evt ) {
+//		clearTimeout( slidechangeTimeout );
+//console.log('fragmentshown');
+		if ( !printMode ) {
+			slideStart = Date.now() - getSlideDuration();
+			slideIndices = Reveal.getIndices();
+			closeChalkboard();
+			board = 0;
+			clearCanvas( 0 );
+			clearCanvas( 1 );
+			if ( Reveal.isAutoSliding() ) {
+				var event = new CustomEvent( 'startplayback' );
+				event.timestamp = 0;
+				document.dispatchEvent( event );
+			} else if ( !playback ) {
+				startPlayback( getSlideDuration(), 0 );
+//				closeChalkboard();
+			}
+		}
+	} );
+	Reveal.addEventListener( 'fragmenthidden', function ( evt ) {
+//		clearTimeout( slidechangeTimeout );
+//console.log('fragmenthidden');
+		if ( !printMode ) {
+			slideStart = Date.now() - getSlideDuration();
+			slideIndices = Reveal.getIndices();
+			closeChalkboard();
+			board = 0;
+			clearCanvas( 0 );
+			clearCanvas( 1 );
+			if ( Reveal.isAutoSliding() ) {
+				document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
+			} else if ( !playback ) {
+				startPlayback( getSlideDuration() );
+				closeChalkboard();
+			}
+		}
+	} );
+
+	Reveal.addEventListener( 'autoslideresumed', function ( evt ) {
+//console.log('autoslideresumed');
+		var event = new CustomEvent( 'startplayback' );
+		event.timestamp = 0;
+		document.dispatchEvent( event );
+	} );
+	Reveal.addEventListener( 'autoslidepaused', function ( evt ) {
+//console.log('autoslidepaused');
+		document.dispatchEvent( new CustomEvent( 'stopplayback' ) );
+
+		// advance to end of slide
+//		closeChalkboard();
+		startPlayback( getSlideDuration(), 0 );
+	} );
+
+	function toggleNotesCanvas() {
+		if ( !readOnly ) {
+			if ( mode == 1 ) {
+				toggleChalkboard();
+				notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
+				notescanvas.style.pointerEvents = 'auto';
+			}
+			else {
+				if ( notescanvas.style.pointerEvents != 'none' ) {
+					// hide notes canvas
+					if ( colorButtons ) {
+						notescanvas.querySelector( '.palette' ).style.visibility = 'hidden';
+					}
+					notescanvas.style.background = 'rgba(0,0,0,0)';
+					notescanvas.style.pointerEvents = 'none';
+				}
+				else {
+					// show notes canvas
+					if ( colorButtons ) {
+						notescanvas.querySelector( '.palette' ).style.visibility = 'visible';
+					}
+					notescanvas.style.background = background[ 0 ]; //'rgba(255,0,0,0.5)';
+					notescanvas.style.pointerEvents = 'auto';
+
+					var idx = 0;
+					if ( color[ mode ] ) {
+						idx = color[ mode ];
+					}
+
+					setColor( idx, true );
+				}
+			}
+		}
+	};
+
+	function toggleChalkboard() {
+//console.log("toggleChalkboard " + mode);
+		if ( mode == 1 ) {
+			if ( !readOnly ) {
+				recordEvent( { type: 'close' } );
+			}
+			closeChalkboard();
+
+			// broadcast
+			var message = new CustomEvent( messageType );
+			message.content = {
+				sender: 'chalkboard-plugin',
+				type: 'closeChalkboard',
+				timestamp: Date.now() - slideStart,
+				mode: 0,
+				board
+			};
+			document.dispatchEvent( message );
+
+
+		} else {
+			showChalkboard();
+			if ( !readOnly ) {
+				recordEvent( { type: 'open' } );
+				// broadcast
+				var message = new CustomEvent( messageType );
+				message.content = {
+					sender: 'chalkboard-plugin',
+					type: 'showChalkboard',
+					timestamp: Date.now() - slideStart,
+					mode: 1,
+					board
+				};
+				document.dispatchEvent( message );
+
+				var idx = 0;
+
+				if ( rememberColor[ mode ] ) {
+					idx = color[ mode ];
+				}
+
+				setColor( idx, true );
+			}
+		}
+	};
+
+	function clearSlide() {
+		recordEvent( { type: 'clear' } );
+		clearCanvas( mode );
+	}
+
+	function clear() {
+		if ( !readOnly ) {
+			clearSlide();
+			// broadcast
+			var message = new CustomEvent( messageType );
+			message.content = {
+				sender: 'chalkboard-plugin',
+				type: 'clear',
+				timestamp: Date.now() - slideStart,
+				mode,
+				board
+			};
+			document.dispatchEvent( message );
+		}
+	};
+
+	function colorIndex( idx ) {
+		if ( !readOnly ) {
+			setColor( idx, true );
+		}
+	}
+
+	function colorNext() {
+		if ( !readOnly ) {
+			let idx = cycleColorNext();
+			setColor( idx, true );
+		}
+	}
+
+	function colorPrev() {
+		if ( !readOnly ) {
+			let idx = cycleColorPrev();
+			setColor( idx, true );
+		}
+	}
+
+	function resetSlideDrawings() {
+		slideStart = Date.now();
+		closeChalkboard();
+
+		clearCanvas( 0 );
+		clearCanvas( 1 );
+
+		mode = 1;
+		var slideData = getSlideData();
+		slideData.duration = 0;
+		slideData.events = [];
+		mode = 0;
+		var slideData = getSlideData();
+		slideData.duration = 0;
+		slideData.events = [];
+
+		updateStorage();
+	}
+
+	function resetSlide( force ) {
+		var ok = force || confirm( "Please confirm to delete chalkboard drawings on this slide!" );
+		if ( ok ) {
+//console.log("resetSlide ");
+			stopPlayback();
+			resetSlideDrawings();
+			// broadcast
+			var message = new CustomEvent( messageType );
+			message.content = {
+				sender: 'chalkboard-plugin',
+				type: 'resetSlide',
+				timestamp: Date.now() - slideStart,
+				mode,
+				board
+			};
+			document.dispatchEvent( message );
+		}
+	};
+
+	function resetStorage( force ) {
+		var ok = force || confirm( "Please confirm to delete all chalkboard drawings!" );
+		if ( ok ) {
+			stopPlayback();
+			slideStart = Date.now();
+			clearCanvas( 0 );
+			clearCanvas( 1 );
+			if ( mode == 1 ) {
+				closeChalkboard();
+			}
+
+			storage = [ {
+					width: Reveal.getConfig().width,
+					height: Reveal.getConfig().height,
+					data: []
+				},
+				{
+					width: Reveal.getConfig().width,
+					height: Reveal.getConfig().height,
+					data: []
+				}
+			];
+
+			if ( config.storage ) {
+				sessionStorage.setItem( config.storage, null )
+			}
+			// broadcast
+			var message = new CustomEvent( messageType );
+			message.content = {
+				sender: 'chalkboard-plugin',
+				type: 'init',
+				timestamp: Date.now() - slideStart,
+				storage,
+				mode,
+				board
+			};
+			document.dispatchEvent( message );
+		}
+	};
+
+	this.toggleNotesCanvas = toggleNotesCanvas;
+	this.toggleChalkboard = toggleChalkboard;
+	this.colorIndex = colorIndex;
+	this.colorNext = colorNext;
+	this.colorPrev = colorPrev;
+	this.clear = clear;
+	this.reset = resetSlide;
+	this.resetAll = resetStorage;
+	this.download = downloadData;
+	this.updateStorage = updateStorage;
+	this.getData = getData;
+	this.configure = configure;
+
+
+	for ( var key in keyBindings ) {
+		if ( keyBindings[ key ] ) {
+			Reveal.addKeyBinding( keyBindings[ key ], RevealChalkboard[ key ] );
+		}
+	};
+
+	return this;
+};
diff --git a/inst/reveal.js-4.6.1/plugin/chalkboard/style.css b/inst/reveal.js-4.6.1/plugin/chalkboard/style.css
new file mode 100644
index 0000000..44a0ae6
--- /dev/null
+++ b/inst/reveal.js-4.6.1/plugin/chalkboard/style.css
@@ -0,0 +1,44 @@
+div.palette, div.boardhandle {
+  position: absolute;
+/*
+  height: 260px;
+  margin: -130px 0 0 0px;
+*/  
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 24px;
+  border-radius: 10px;
+  border-top:    4px solid #222; 
+  border-right:  4px solid #222; 
+  border-bottom: 4px solid #222;
+  background: black;
+  transition: transform 0.3s;
+}
+
+div.palette  {
+  left: -10px;
+  padding-left:10px;
+}
+
+div.boardhandle {
+  right: -10px;
+  padding-right:10px;
+}
+
+div.palette > ul,
+div.boardhandle > ul {
+  list-style-type: none;
+  margin: 0;
+  padding: 0;
+}
+
+div.palette > ul > li,
+div.boardhandle > ul > li {
+  margin: 10px;
+}
+
+@media print {
+	div.palette, div.boardhandle { 
+		display: none!important; 
+	}
+}