<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Slides | Nikhil Kaza</title>
    <link>https://nkaza.github.io/slides/</link>
      <atom:link href="https://nkaza.github.io/slides/index.xml" rel="self" type="application/rss+xml" />
    <description>Slides</description>
    <generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><copyright>© 2018-2025 Nikhil Kaza</copyright>
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/advancedspatial_with_sf/advanced_spatial_with_sf_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/advancedspatial_with_sf/advanced_spatial_with_sf_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/advancedspatial_with_sf/advanced_spatial_with_sf_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/advancedspatial_with_sf/advanced_spatial_with_sf_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day1_1/intro2r_day1_1_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day1_1/intro2r_day1_1_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day1_1/intro2r_day1_1_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day1_1/intro2r_day1_1_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day1_2/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day1_2/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day1_2/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day1_2/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day1_2/intro2r_day1_2_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day2_1/visual_ggplot_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day2_1/visual_ggplot_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day2_1/visual_ggplot_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day2_1/visual_ggplot_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day2_2/programminginr_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day2_2/programminginr_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/bootcamp_day2_2/programminginr_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/bootcamp_day2_2/programminginr_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/census_geo/census_geo_v2_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/census_geo/census_geo_v2_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/ggplot2/visual_ggplot_tmap_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/ggplot2/visual_ggplot_tmap_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/ggplot2/visual_ggplot_tmap_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/ggplot2/visual_ggplot_tmap_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/intro2r/intro2r_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/intro2r/intro2r_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/intro2r/intro2r_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/intro2r/intro2r_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/spatial_with_sf/spatial_with_sf_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/spatial_with_sf/spatial_with_sf_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/spatial_with_sf/spatial_with_sf_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/spatial_with_sf/spatial_with_sf_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2_files-cas-cr-mj377z5s-conflicted-copy-2024-08-19/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2_files-cas-cr-mj377z5s-conflicted-copy-2024-08-19/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2_files/libs/revealjs/plugin/notes/speaker-view/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2_files/libs/revealjs/plugin/notes/speaker-view/</guid>
      <description>
&lt;html lang=&#34;en&#34;&gt;
	&lt;head&gt;
		&lt;meta charset=&#34;utf-8&#34;&gt;

		&lt;title&gt;reveal.js - Speaker View&lt;/title&gt;

		&lt;style&gt;
			body {
				font-family: Helvetica;
				font-size: 18px;
			}

			#current-slide,
			#upcoming-slide,
			#speaker-controls {
				padding: 6px;
				box-sizing: border-box;
				-moz-box-sizing: border-box;
			}

			#current-slide iframe,
			#upcoming-slide iframe {
				width: 100%;
				height: 100%;
				border: 1px solid #ddd;
			}

			#current-slide .label,
			#upcoming-slide .label {
				position: absolute;
				top: 10px;
				left: 10px;
				z-index: 2;
			}

			#connection-status {
				position: absolute;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				z-index: 20;
				padding: 30% 20% 20% 20%;
				font-size: 18px;
				color: #222;
				background: #fff;
				text-align: center;
				box-sizing: border-box;
				line-height: 1.4;
			}

			.overlay-element {
				height: 34px;
				line-height: 34px;
				padding: 0 10px;
				text-shadow: none;
				background: rgba( 220, 220, 220, 0.8 );
				color: #222;
				font-size: 14px;
			}

			.overlay-element.interactive:hover {
				background: rgba( 220, 220, 220, 1 );
			}

			#current-slide {
				position: absolute;
				width: 60%;
				height: 100%;
				top: 0;
				left: 0;
				padding-right: 0;
			}

			#upcoming-slide {
				position: absolute;
				width: 40%;
				height: 40%;
				right: 0;
				top: 0;
			}

			/* Speaker controls */
			#speaker-controls {
				position: absolute;
				top: 40%;
				right: 0;
				width: 40%;
				height: 60%;
				overflow: auto;
				font-size: 18px;
			}

				.speaker-controls-time.hidden,
				.speaker-controls-notes.hidden {
					display: none;
				}

				.speaker-controls-time .label,
				.speaker-controls-pace .label,
				.speaker-controls-notes .label {
					text-transform: uppercase;
					font-weight: normal;
					font-size: 0.66em;
					color: #666;
					margin: 0;
				}

				.speaker-controls-time, .speaker-controls-pace {
					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
					margin-bottom: 10px;
					padding: 10px 16px;
					padding-bottom: 20px;
					cursor: pointer;
				}

				.speaker-controls-time .reset-button {
					opacity: 0;
					float: right;
					color: #666;
					text-decoration: none;
				}
				.speaker-controls-time:hover .reset-button {
					opacity: 1;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock {
					width: 50%;
				}

				.speaker-controls-time .timer,
				.speaker-controls-time .clock,
				.speaker-controls-time .pacing .hours-value,
				.speaker-controls-time .pacing .minutes-value,
				.speaker-controls-time .pacing .seconds-value {
					font-size: 1.9em;
				}

				.speaker-controls-time .timer {
					float: left;
				}

				.speaker-controls-time .clock {
					float: right;
					text-align: right;
				}

				.speaker-controls-time span.mute {
					opacity: 0.3;
				}

				.speaker-controls-time .pacing-title {
					margin-top: 5px;
				}

				.speaker-controls-time .pacing.ahead {
					color: blue;
				}

				.speaker-controls-time .pacing.on-track {
					color: green;
				}

				.speaker-controls-time .pacing.behind {
					color: red;
				}

				.speaker-controls-notes {
					padding: 10px 16px;
				}

				.speaker-controls-notes .value {
					margin-top: 5px;
					line-height: 1.4;
					font-size: 1.2em;
				}

			/* Layout selector */
			#speaker-layout {
				position: absolute;
				top: 10px;
				right: 10px;
				color: #222;
				z-index: 10;
			}
				#speaker-layout select {
					position: absolute;
					width: 100%;
					height: 100%;
					top: 0;
					left: 0;
					border: 0;
					box-shadow: 0;
					cursor: pointer;
					opacity: 0;

					font-size: 1em;
					background-color: transparent;

					-moz-appearance: none;
					-webkit-appearance: none;
					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
				}

				#speaker-layout select:focus {
					outline: none;
					box-shadow: none;
				}

			.clear {
				clear: both;
			}

			/* Speaker layout: Wide */
			body[data-speaker-layout=&#34;wide&#34;] #current-slide,
			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				width: 50%;
				height: 45%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;wide&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;wide&#34;] #upcoming-slide {
				top: 0;
				left: 50%;
			}

			body[data-speaker-layout=&#34;wide&#34;] #speaker-controls {
				top: 45%;
				left: 0;
				width: 100%;
				height: 50%;
				font-size: 1.25em;
			}

			/* Speaker layout: Tall */
			body[data-speaker-layout=&#34;tall&#34;] #current-slide,
			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				width: 45%;
				height: 50%;
				padding: 6px;
			}

			body[data-speaker-layout=&#34;tall&#34;] #current-slide {
				top: 0;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #upcoming-slide {
				top: 50%;
				left: 0;
			}

			body[data-speaker-layout=&#34;tall&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 45%;
				width: 55%;
				height: 100%;
				font-size: 1.25em;
			}

			/* Speaker layout: Notes only */
			body[data-speaker-layout=&#34;notes-only&#34;] #current-slide,
			body[data-speaker-layout=&#34;notes-only&#34;] #upcoming-slide {
				display: none;
			}

			body[data-speaker-layout=&#34;notes-only&#34;] #speaker-controls {
				padding-top: 40px;
				top: 0;
				left: 0;
				width: 100%;
				height: 100%;
				font-size: 1.25em;
			}

			@media screen and (max-width: 1080px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 16px;
				}
			}

			@media screen and (max-width: 900px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 14px;
				}
			}

			@media screen and (max-width: 800px) {
				body[data-speaker-layout=&#34;default&#34;] #speaker-controls {
					font-size: 12px;
				}
			}

		&lt;/style&gt;
	&lt;/head&gt;

	&lt;body&gt;

		&lt;div id=&#34;connection-status&#34;&gt;Loading speaker view...&lt;/div&gt;

		&lt;div id=&#34;current-slide&#34;&gt;&lt;/div&gt;
		&lt;div id=&#34;upcoming-slide&#34;&gt;&lt;span class=&#34;overlay-element label&#34;&gt;Upcoming&lt;/span&gt;&lt;/div&gt;
		&lt;div id=&#34;speaker-controls&#34;&gt;
			&lt;div class=&#34;speaker-controls-time&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Time &lt;span class=&#34;reset-button&#34;&gt;Click to Reset&lt;/span&gt;&lt;/h4&gt;
				&lt;div class=&#34;clock&#34;&gt;
					&lt;span class=&#34;clock-value&#34;&gt;0:00 AM&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;timer&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
				&lt;div class=&#34;clear&#34;&gt;&lt;/div&gt;

				&lt;h4 class=&#34;label pacing-title&#34; style=&#34;display: none&#34;&gt;Pacing – Time to finish current slide&lt;/h4&gt;
				&lt;div class=&#34;pacing&#34; style=&#34;display: none&#34;&gt;
					&lt;span class=&#34;hours-value&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;minutes-value&#34;&gt;:00&lt;/span&gt;&lt;span class=&#34;seconds-value&#34;&gt;:00&lt;/span&gt;
				&lt;/div&gt;
			&lt;/div&gt;

			&lt;div class=&#34;speaker-controls-notes hidden&#34;&gt;
				&lt;h4 class=&#34;label&#34;&gt;Notes&lt;/h4&gt;
				&lt;div class=&#34;value&#34;&gt;&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div id=&#34;speaker-layout&#34; class=&#34;overlay-element interactive&#34;&gt;
			&lt;span class=&#34;speaker-layout-label&#34;&gt;&lt;/span&gt;
			&lt;select class=&#34;speaker-layout-dropdown&#34;&gt;&lt;/select&gt;
		&lt;/div&gt;

		&lt;script&gt;

			(function() {

				var notes,
					notesValue,
					currentState,
					currentSlide,
					upcomingSlide,
					layoutLabel,
					layoutDropdown,
					pendingCalls = {},
					lastRevealApiCallId = 0,
					connected = false,
					whitelistedWindows = [window.opener];

				var SPEAKER_LAYOUTS = {
					&#39;default&#39;: &#39;Default&#39;,
					&#39;wide&#39;: &#39;Wide&#39;,
					&#39;tall&#39;: &#39;Tall&#39;,
					&#39;notes-only&#39;: &#39;Notes only&#39;
				};

				setupLayout();

				var connectionStatus = document.querySelector( &#39;#connection-status&#39; );
				var connectionTimeout = setTimeout( function() {
					connectionStatus.innerHTML = &#39;Error connecting to main window.&lt;br&gt;Please try closing and reopening the speaker view.&#39;;
				}, 5000 );
;
				window.addEventListener( &#39;message&#39;, function( event ) {

					// Validate the origin of this message to prevent XSS
					if( window.location.origin !== event.origin &amp;&amp; whitelistedWindows.indexOf( event.source ) === -1 ) {
						return;
					}

					clearTimeout( connectionTimeout );
					connectionStatus.style.display = &#39;none&#39;;

					var data = JSON.parse( event.data );

					// The overview mode is only useful to the reveal.js instance
					// where navigation occurs so we don&#39;t sync it
					if( data.state ) delete data.state.overview;

					// Messages sent by the notes plugin inside of the main window
					if( data &amp;&amp; data.namespace === &#39;reveal-notes&#39; ) {
						if( data.type === &#39;connect&#39; ) {
							handleConnectMessage( data );
						}
						else if( data.type === &#39;state&#39; ) {
							handleStateMessage( data );
						}
						else if( data.type === &#39;return&#39; ) {
							pendingCalls[data.callId](data.result);
							delete pendingCalls[data.callId];
						}
					}
					// Messages sent by the reveal.js inside of the current slide preview
					else if( data &amp;&amp; data.namespace === &#39;reveal&#39; ) {
						if( /ready/.test( data.eventName ) ) {
							// Send a message back to notify that the handshake is complete
							window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;connected&#39;} ), &#39;*&#39; );
						}
						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) &amp;&amp; currentState !== JSON.stringify( data.state ) ) {

							dispatchStateToMainWindow( data.state );

						}
					}

				} );

				/**
				 * Updates the presentation in the main window to match the state
				 * of the presentation in the notes window.
				 */
				const dispatchStateToMainWindow = debounce(( state ) =&gt; {
					window.opener.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ state ]} ), &#39;*&#39; );
				}, 500);

				/**
				 * Asynchronously calls the Reveal.js API of the main frame.
				 */
				function callRevealApi( methodName, methodArguments, callback ) {

					var callId = ++lastRevealApiCallId;
					pendingCalls[callId] = callback;
					window.opener.postMessage( JSON.stringify( {
						namespace: &#39;reveal-notes&#39;,
						type: &#39;call&#39;,
						callId: callId,
						methodName: methodName,
						arguments: methodArguments
					} ), &#39;*&#39; );

				}

				/**
				 * Called when the main window is trying to establish a
				 * connection.
				 */
				function handleConnectMessage( data ) {

					if( connected === false ) {
						connected = true;

						setupIframes( data );
						setupKeyboard();
						setupNotes();
						setupTimer();
						setupHeartbeat();
					}

				}

				/**
				 * Called when the main window sends an updated state.
				 */
				function handleStateMessage( data ) {

					// Store the most recently set state to avoid circular loops
					// applying the same state
					currentState = JSON.stringify( data.state );

					// No need for updating the notes in case of fragment changes
					if ( data.notes ) {
						notes.classList.remove( &#39;hidden&#39; );
						notesValue.style.whiteSpace = data.whitespace;
						if( data.markdown ) {
							notesValue.innerHTML = marked( data.notes );
						}
						else {
							notesValue.innerHTML = data.notes;
						}
					}
					else {
						notes.classList.add( &#39;hidden&#39; );
					}

					// Update the note slides
					currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;setState&#39;, args: [ data.state ] }), &#39;*&#39; );
					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;next&#39; }), &#39;*&#39; );

				}

				// Limit to max one state update per X ms
				handleStateMessage = debounce( handleStateMessage, 200 );

				/**
				 * Forward keyboard events to the current slide window.
				 * This enables keyboard events to work even if focus
				 * isn&#39;t set on the current slide iframe.
				 *
				 * Block F5 default handling, it reloads and disconnects
				 * the speaker notes window.
				 */
				function setupKeyboard() {

					document.addEventListener( &#39;keydown&#39;, function( event ) {
						if( event.keyCode === 116 || ( event.metaKey &amp;&amp; event.keyCode === 82 ) ) {
							event.preventDefault();
							return false;
						}
						currentSlide.contentWindow.postMessage( JSON.stringify({ method: &#39;triggerKey&#39;, args: [ event.keyCode ] }), &#39;*&#39; );
					} );

				}

				/**
				 * Creates the preview iframes.
				 */
				function setupIframes( data ) {

					var params = [
						&#39;receiver&#39;,
						&#39;progress=false&#39;,
						&#39;history=false&#39;,
						&#39;transition=none&#39;,
						&#39;autoSlide=0&#39;,
						&#39;backgroundTransition=none&#39;
					].join( &#39;&amp;&#39; );

					var urlSeparator = /\?/.test(data.url) ? &#39;&amp;&#39; : &#39;?&#39;;
					var hash = &#39;#/&#39; + data.state.indexh + &#39;/&#39; + data.state.indexv;
					var currentURL = data.url + urlSeparator + params + &#39;&amp;postMessageEvents=true&#39; + hash;
					var upcomingURL = data.url + urlSeparator + params + &#39;&amp;controls=false&#39; + hash;

					currentSlide = document.createElement( &#39;iframe&#39; );
					currentSlide.setAttribute( &#39;width&#39;, 1280 );
					currentSlide.setAttribute( &#39;height&#39;, 1024 );
					currentSlide.setAttribute( &#39;src&#39;, currentURL );
					document.querySelector( &#39;#current-slide&#39; ).appendChild( currentSlide );

					upcomingSlide = document.createElement( &#39;iframe&#39; );
					upcomingSlide.setAttribute( &#39;width&#39;, 640 );
					upcomingSlide.setAttribute( &#39;height&#39;, 512 );
					upcomingSlide.setAttribute( &#39;src&#39;, upcomingURL );
					document.querySelector( &#39;#upcoming-slide&#39; ).appendChild( upcomingSlide );

					whitelistedWindows.push( currentSlide.contentWindow, upcomingSlide.contentWindow );

				}

				/**
				 * Setup the notes UI.
				 */
				function setupNotes() {

					notes = document.querySelector( &#39;.speaker-controls-notes&#39; );
					notesValue = document.querySelector( &#39;.speaker-controls-notes .value&#39; );

				}

				/**
				 * We send out a heartbeat at all times to ensure we can
				 * reconnect with the main presentation window after reloads.
				 */
				function setupHeartbeat() {

					setInterval( () =&gt; {
						window.opener.postMessage( JSON.stringify({ namespace: &#39;reveal-notes&#39;, type: &#39;heartbeat&#39;} ), &#39;*&#39; );
					}, 1000 );

				}

				function getTimings( callback ) {

					callRevealApi( &#39;getSlidesAttributes&#39;, [], function ( slideAttributes ) {
						callRevealApi( &#39;getConfig&#39;, [], function ( config ) {
							var totalTime = config.totalTime;
							var minTimePerSlide = config.minimumTimePerSlide || 0;
							var defaultTiming = config.defaultTiming;
							if ((defaultTiming == null) &amp;&amp; (totalTime == null)) {
								callback(null);
								return;
							}
							// Setting totalTime overrides defaultTiming
							if (totalTime) {
								defaultTiming = 0;
							}
							var timings = [];
							for ( var i in slideAttributes ) {
								var slide = slideAttributes[ i ];
								var timing = defaultTiming;
								if( slide.hasOwnProperty( &#39;data-timing&#39; )) {
									var t = slide[ &#39;data-timing&#39; ];
									timing = parseInt(t);
									if( isNaN(timing) ) {
										console.warn(&#34;Could not parse timing &#39;&#34; + t + &#34;&#39; of slide &#34; + i + &#34;; using default of &#34; + defaultTiming);
										timing = defaultTiming;
									}
								}
								timings.push(timing);
							}
							if ( totalTime ) {
								// After we&#39;ve allocated time to individual slides, we summarize it and
								// subtract it from the total time
								var remainingTime = totalTime - timings.reduce( function(a, b) { return a + b; }, 0 );
								// The remaining time is divided by the number of slides that have 0 seconds
								// allocated at the moment, giving the average time-per-slide on the remaining slides
								var remainingSlides = (timings.filter( function(x) { return x == 0 }) ).length
								var timePerSlide = Math.round( remainingTime / remainingSlides, 0 )
								// And now we replace every zero-value timing with that average
								timings = timings.map( function(x) { return (x==0 ? timePerSlide : x) } );
							}
							var slidesUnderMinimum = timings.filter( function(x) { return (x &lt; minTimePerSlide) } ).length
							if ( slidesUnderMinimum ) {
								message = &#34;The pacing time for &#34; + slidesUnderMinimum + &#34; slide(s) is under the configured minimum of &#34; + minTimePerSlide + &#34; seconds. Check the data-timing attribute on individual slides, or consider increasing the totalTime or minimumTimePerSlide configuration options (or removing some slides).&#34;;
								alert(message);
							}
							callback( timings );
						} );
					} );

				}

				/**
				 * Return the number of seconds allocated for presenting
				 * all slides up to and including this one.
				 */
				function getTimeAllocated( timings, callback ) {

					callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
						var allocated = 0;
						for (var i in timings.slice(0, currentSlide + 1)) {
							allocated += timings[i];
						}
						callback( allocated );
					} );

				}

				/**
				 * Create the timer and clock and start updating them
				 * at an interval.
				 */
				function setupTimer() {

					var start = new Date(),
					timeEl = document.querySelector( &#39;.speaker-controls-time&#39; ),
					clockEl = timeEl.querySelector( &#39;.clock-value&#39; ),
					hoursEl = timeEl.querySelector( &#39;.hours-value&#39; ),
					minutesEl = timeEl.querySelector( &#39;.minutes-value&#39; ),
					secondsEl = timeEl.querySelector( &#39;.seconds-value&#39; ),
					pacingTitleEl = timeEl.querySelector( &#39;.pacing-title&#39; ),
					pacingEl = timeEl.querySelector( &#39;.pacing&#39; ),
					pacingHoursEl = pacingEl.querySelector( &#39;.hours-value&#39; ),
					pacingMinutesEl = pacingEl.querySelector( &#39;.minutes-value&#39; ),
					pacingSecondsEl = pacingEl.querySelector( &#39;.seconds-value&#39; );

					var timings = null;
					getTimings( function ( _timings ) {

						timings = _timings;
						if (_timings !== null) {
							pacingTitleEl.style.removeProperty(&#39;display&#39;);
							pacingEl.style.removeProperty(&#39;display&#39;);
						}

						// Update once directly
						_updateTimer();

						// Then update every second
						setInterval( _updateTimer, 1000 );

					} );


					function _resetTimer() {

						if (timings == null) {
							start = new Date();
							_updateTimer();
						}
						else {
							// Reset timer to beginning of current slide
							getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
								var slideEndTiming = slideEndTimingSeconds * 1000;
								callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
									var currentSlideTiming = timings[currentSlide] * 1000;
									var previousSlidesTiming = slideEndTiming - currentSlideTiming;
									var now = new Date();
									start = new Date(now.getTime() - previousSlidesTiming);
									_updateTimer();
								} );
							} );
						}

					}

					timeEl.addEventListener( &#39;click&#39;, function() {
						_resetTimer();
						return false;
					} );

					function _displayTime( hrEl, minEl, secEl, time) {

						var sign = Math.sign(time) == -1 ? &#34;-&#34; : &#34;&#34;;
						time = Math.abs(Math.round(time / 1000));
						var seconds = time % 60;
						var minutes = Math.floor( time / 60 ) % 60 ;
						var hours = Math.floor( time / ( 60 * 60 )) ;
						hrEl.innerHTML = sign + zeroPadInteger( hours );
						if (hours == 0) {
							hrEl.classList.add( &#39;mute&#39; );
						}
						else {
							hrEl.classList.remove( &#39;mute&#39; );
						}
						minEl.innerHTML = &#39;:&#39; + zeroPadInteger( minutes );
						if (hours == 0 &amp;&amp; minutes == 0) {
							minEl.classList.add( &#39;mute&#39; );
						}
						else {
							minEl.classList.remove( &#39;mute&#39; );
						}
						secEl.innerHTML = &#39;:&#39; + zeroPadInteger( seconds );
					}

					function _updateTimer() {

						var diff, hours, minutes, seconds,
						now = new Date();

						diff = now.getTime() - start.getTime();

						clockEl.innerHTML = now.toLocaleTimeString( &#39;en-US&#39;, { hour12: true, hour: &#39;2-digit&#39;, minute:&#39;2-digit&#39; } );
						_displayTime( hoursEl, minutesEl, secondsEl, diff );
						if (timings !== null) {
							_updatePacing(diff);
						}

					}

					function _updatePacing(diff) {

						getTimeAllocated( timings, function ( slideEndTimingSeconds ) {
							var slideEndTiming = slideEndTimingSeconds * 1000;

							callRevealApi( &#39;getSlidePastCount&#39;, [], function ( currentSlide ) {
								var currentSlideTiming = timings[currentSlide] * 1000;
								var timeLeftCurrentSlide = slideEndTiming - diff;
								if (timeLeftCurrentSlide &lt; 0) {
									pacingEl.className = &#39;pacing behind&#39;;
								}
								else if (timeLeftCurrentSlide &lt; currentSlideTiming) {
									pacingEl.className = &#39;pacing on-track&#39;;
								}
								else {
									pacingEl.className = &#39;pacing ahead&#39;;
								}
								_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
							} );
						} );
					}

				}

				/**
				 * Sets up the speaker view layout and layout selector.
				 */
				function setupLayout() {

					layoutDropdown = document.querySelector( &#39;.speaker-layout-dropdown&#39; );
					layoutLabel = document.querySelector( &#39;.speaker-layout-label&#39; );

					// Render the list of available layouts
					for( var id in SPEAKER_LAYOUTS ) {
						var option = document.createElement( &#39;option&#39; );
						option.setAttribute( &#39;value&#39;, id );
						option.textContent = SPEAKER_LAYOUTS[ id ];
						layoutDropdown.appendChild( option );
					}

					// Monitor the dropdown for changes
					layoutDropdown.addEventListener( &#39;change&#39;, function( event ) {

						setLayout( layoutDropdown.value );

					}, false );

					// Restore any currently persisted layout
					setLayout( getLayout() );

				}

				/**
				 * Sets a new speaker view layout. The layout is persisted
				 * in local storage.
				 */
				function setLayout( value ) {

					var title = SPEAKER_LAYOUTS[ value ];

					layoutLabel.innerHTML = &#39;Layout&#39; + ( title ? ( &#39;: &#39; + title ) : &#39;&#39; );
					layoutDropdown.value = value;

					document.body.setAttribute( &#39;data-speaker-layout&#39;, value );

					// Persist locally
					if( supportsLocalStorage() ) {
						window.localStorage.setItem( &#39;reveal-speaker-layout&#39;, value );
					}

				}

				/**
				 * Returns the ID of the most recently set speaker layout
				 * or our default layout if none has been set.
				 */
				function getLayout() {

					if( supportsLocalStorage() ) {
						var layout = window.localStorage.getItem( &#39;reveal-speaker-layout&#39; );
						if( layout ) {
							return layout;
						}
					}

					// Default to the first record in the layouts hash
					for( var id in SPEAKER_LAYOUTS ) {
						return id;
					}

				}

				function supportsLocalStorage() {

					try {
						localStorage.setItem(&#39;test&#39;, &#39;test&#39;);
						localStorage.removeItem(&#39;test&#39;);
						return true;
					}
					catch( e ) {
						return false;
					}

				}

				function zeroPadInteger( num ) {

					var str = &#39;00&#39; + parseInt( num );
					return str.substring( str.length - 2 );

				}

				/**
				 * Limits the frequency at which a function can be called.
				 */
				function debounce( fn, ms ) {

					var lastTime = 0,
						timeout;

					return function() {

						var args = arguments;
						var context = this;

						clearTimeout( timeout );

						var timeSinceLastCall = Date.now() - lastTime;
						if( timeSinceLastCall &gt; ms ) {
							fn.apply( context, args );
							lastTime = Date.now();
						}
						else {
							timeout = setTimeout( function() {
								fn.apply( context, args );
								lastTime = Date.now();
							}, ms - timeSinceLastCall );
						}

					}

				}

			})();

		&lt;/script&gt;
	&lt;/body&gt;
&lt;/html&gt;</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2_files/libs/revealjs/plugin/reveal-chalkboard/readme/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2_files/libs/revealjs/plugin/reveal-chalkboard/readme/</guid>
      <description>&lt;h1 id=&#34;chalkboard&#34;&gt;Chalkboard&lt;/h1&gt;
&lt;p&gt;With this plugin you can add a chalkboard to reveal.js. The plugin provides two possibilities to include handwritten notes to your presentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can make notes directly on the slides, e.g. to comment on certain aspects,&lt;/li&gt;
&lt;li&gt;you can open a chalkboard or whiteboard on which you can make notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The plugin records all drawings made so that they can be play backed using the &lt;code&gt;autoSlide&lt;/code&gt; feature or the &lt;code&gt;audio-slideshow&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rajgoel.github.io/reveal.js-demos/chalkboard-demo.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Check out the live demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The chalkboard effect is based on &lt;a href=&#34;https://github.com/mmoustafa/Chalkboard&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Chalkboard&lt;/a&gt; by Mohamed Moustafa.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Copy the file &lt;code&gt;plugin.js&lt;/code&gt; and the  &lt;code&gt;img&lt;/code&gt; directory into the plugin folder of your reveal.js presentation, i.e. &lt;code&gt;plugin/chalkboard&lt;/code&gt; and load the plugin as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script src=&amp;quot;plugin/chalkboard/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;plugin/customcontrols/plugin.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
    Reveal.initialize({
        // ...
        plugins: [ RevealChalkboard, RevealCustomControls ],
        // ...
    });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following stylesheet&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/chalkboard/style.css&amp;quot;&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugin/customcontrols/style.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;has to be included to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;p&gt;In order to include buttons for opening and closing the notes canvas or the chalkboard you should make sure that &lt;code&gt;font-awesome&lt;/code&gt; is available. The easiest way is to include&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the &lt;code&gt;head&lt;/code&gt; section of you HTML-file.&lt;/p&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;h3 id=&#34;mouse-or-touch&#34;&gt;Mouse or touch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Click on the pen symbols at the bottom left to toggle the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;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)&lt;/li&gt;
&lt;li&gt;Click the left mouse button and drag to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Click the right mouse button and drag to wipe away previous drawings&lt;/li&gt;
&lt;li&gt;Touch and move to write on notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Touch and hold for half a second, then move to wipe away previous drawings&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;keyboard&#34;&gt;Keyboard&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Press the &amp;lsquo;BACKSPACE&amp;rsquo; key to delete all chalkboard drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;DEL&amp;rsquo; key to clear the notes canvas or chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;c&amp;rsquo; key to toggle the notes canvas&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;b&amp;rsquo; key to toggle the chalkboard&lt;/li&gt;
&lt;li&gt;Press the &amp;rsquo;d&#39; key to download drawings&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;x&amp;rsquo; key to cycle colors forward&lt;/li&gt;
&lt;li&gt;Press the &amp;lsquo;y&amp;rsquo; key to cycle colors backward&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;playback&#34;&gt;Playback&lt;/h2&gt;
&lt;p&gt;If the &lt;code&gt;autoSlide&lt;/code&gt; feature is set or if the &lt;code&gt;audio-slideshow&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;multiplexing&#34;&gt;Multiplexing&lt;/h2&gt;
&lt;p&gt;The plugin supports multiplexing via the &lt;a href=&#34;https://github.com/reveal/multiplex&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;multiplex&lt;/code&gt; plugin&lt;/a&gt; or the &lt;a href=&#34;https://github.com/rajgoel/reveal.js-plugins/tree/master/seminar&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;seminar&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pdf-export&#34;&gt;PDF-Export&lt;/h2&gt;
&lt;p&gt;If the slideshow is opened in &lt;a href=&#34;https://revealjs.com/pdf-export/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;print mode&lt;/a&gt;, the chalkboard drawings in the session storage (see &lt;code&gt;storage&lt;/code&gt; option - print version must be opened in the same tab or window as the original slideshow) or provided in a file (see &lt;code&gt;src&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The plugin has several configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boardmarkerWidth&lt;/code&gt;: an integer, the drawing width of the boardmarker; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkWidth&lt;/code&gt;: an integer, the drawing width of the chalk; larger values draw thicker lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalkEffect&lt;/code&gt;: a float in the range &lt;code&gt;[0.0, 1.0]&lt;/code&gt;, the intesity of the chalk effect on the chalk board. Full effect (default) &lt;code&gt;1.0&lt;/code&gt;, no effect &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt;: Optional variable name for session storage of drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt;: Optional filename for pre-recorded drawings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readOnly&lt;/code&gt;: Configuation option allowing to prevent changes to existing drawings. If set to &lt;code&gt;true&lt;/code&gt; no changes can be made, if set to false &lt;code&gt;false&lt;/code&gt; changes can be made, if unset or set to &lt;code&gt;undefined&lt;/code&gt; 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 &amp;lsquo;DEL&amp;rsquo; key (i.e. by using the &lt;code&gt;RevealChalkboard.clear()&lt;/code&gt; function).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transition&lt;/code&gt;: Gives the duration (in milliseconds) of the transition for a slide change, so that the notes canvas is drawn after the transition is completed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theme&lt;/code&gt;: Can be set to either &lt;code&gt;&amp;quot;chalkboard&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;whiteboard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;background&lt;/code&gt;: 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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grid&lt;/code&gt;: 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. &lt;code&gt;{ color: &#39;rgb(127,127,255,0.1)&#39;, distance: 40, width: 2}&lt;/code&gt;. Alternatively, the grid can be removed by setting the value to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eraser&lt;/code&gt;: An image path and radius for the eraser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boardmarkers&lt;/code&gt;: A list of boardmarkers with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chalks&lt;/code&gt;: A list of chalks with given color and cursor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rememberColor&lt;/code&gt;: Whether to remember the last selected color for the slide canvas or the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of the configurations are optional and the default values shown below are used if the options are not provided.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;Reveal.initialize({
	// ...
    chalkboard: {
        boardmarkerWidth: 3,
        chalkWidth: 7,
        chalkEffect: 1.0,
        storage: null,
        src: null,
        readOnly: undefined,
        transition: 800,
        theme: &amp;quot;chalkboard&amp;quot;,
        background: [ &#39;rgba(127,127,127,.1)&#39; , path + &#39;img/blackboard.png&#39; ],
        grid: { color: &#39;rgb(50,50,10,0.5)&#39;, distance: 80, width: 2},
        eraser: { src: path + &#39;img/sponge.png&#39;, radius: 20},
        boardmarkers : [
                { color: &#39;rgba(100,100,100,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-black.png), auto&#39;},
                { color: &#39;rgba(30,144,255, 1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-blue.png), auto&#39;},
                { color: &#39;rgba(220,20,60,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-red.png), auto&#39;},
                { color: &#39;rgba(50,205,50,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-green.png), auto&#39;},
                { color: &#39;rgba(255,140,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-orange.png), auto&#39;},
                { color: &#39;rgba(150,0,20150,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,1)&#39;, cursor: &#39;url(&#39; + path + &#39;img/boardmarker-yellow.png), auto&#39;}
        ],
        chalks: [
                { color: &#39;rgba(255,255,255,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-white.png), auto&#39;},
                { color: &#39;rgba(96, 154, 244, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-blue.png), auto&#39;},
                { color: &#39;rgba(237, 20, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-red.png), auto&#39;},
                { color: &#39;rgba(20, 237, 28, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-green.png), auto&#39;},
                { color: &#39;rgba(220, 133, 41, 0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-orange.png), auto&#39;},
                { color: &#39;rgba(220,0,220,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-purple.png), auto&#39;},
                { color: &#39;rgba(255,220,0,0.5)&#39;, cursor: &#39;url(&#39; + path + &#39;img/chalk-yellow.png), auto&#39;}
        ]
    },
    customcontrols: {
  		controls: [
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen-square&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle chalkboard (B)&#39;,
  			  action: &#39;RevealChalkboard.toggleChalkboard();&#39;
  			},
  			{ icon: &#39;&amp;lt;i class=&amp;quot;fa fa-pen&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#39;,
  			  title: &#39;Toggle notes canvas (C)&#39;,
  			  action: &#39;RevealChalkboard.toggleNotesCanvas();&#39;
  			}
  		]
    },
    // ...

});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&#34;license&#34;&gt;License&lt;/h2&gt;
&lt;p&gt;MIT licensed&lt;/p&gt;
&lt;p&gt;Copyright (C) 2021 Asvin Goel&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/urbanform_energyconsumption/figs/widgets/leaflet_libs/leaflet-providers/rstudio_install/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/urbanform_energyconsumption/figs/widgets/leaflet_libs/leaflet-providers/rstudio_install/</guid>
      <description>&lt;h1 id=&#34;location&#34;&gt;Location&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;from: github.com/schloerke/leaflet-providers@urlProtocol&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inspiration taken from &lt;a href=&#34;https://github.com/leaflet-extras/leaflet-providers/commit/dea786a3219f9cc824b8e96903a17f46ca9a5afc&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://github.com/leaflet-extras/leaflet-providers/commit/dea786a3219f9cc824b8e96903a17f46ca9a5afc&lt;/a&gt; to use the &amp;lsquo;old&amp;rsquo; relative url protocols and to &amp;lsquo;upgrade&amp;rsquo; them at js runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;notes&#34;&gt;Notes&amp;hellip;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Copy/paste provider information into &lt;code&gt;providers.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;var providers = L.TileLayer.Provider.providers;
JSON.stringify(providers, null, &amp;quot;  &amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;./data-raw/providerNames.R&lt;/code&gt; was re-ran to update to the latest providers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some providers had their protocols turned into &amp;lsquo;//&amp;rsquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This allows browsers to pick the protocol&lt;/li&gt;
&lt;li&gt;To stop files from the protocols staying as files, a ducktape patch was applied to &lt;code&gt;L.TileLayer.prototype.initialize&lt;/code&gt; and &lt;code&gt;L.TileLayer.WMS.prototype.initialize&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title></title>
      <link>https://nkaza.github.io/slides/urbanform_energyconsumption/libs/leaflet-providers/rstudio_install/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/urbanform_energyconsumption/libs/leaflet-providers/rstudio_install/</guid>
      <description>&lt;h1 id=&#34;location&#34;&gt;Location&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;from: github.com/schloerke/leaflet-providers@urlProtocol&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inspiration taken from &lt;a href=&#34;https://github.com/leaflet-extras/leaflet-providers/commit/dea786a3219f9cc824b8e96903a17f46ca9a5afc&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://github.com/leaflet-extras/leaflet-providers/commit/dea786a3219f9cc824b8e96903a17f46ca9a5afc&lt;/a&gt; to use the &amp;lsquo;old&amp;rsquo; relative url protocols and to &amp;lsquo;upgrade&amp;rsquo; them at js runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;notes&#34;&gt;Notes&amp;hellip;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Copy/paste provider information into &lt;code&gt;providers.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;var providers = L.TileLayer.Provider.providers;
JSON.stringify(providers, null, &amp;quot;  &amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;./data-raw/providerNames.R&lt;/code&gt; was re-ran to update to the latest providers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Some providers had their protocols turned into &amp;lsquo;//&amp;rsquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This allows browsers to pick the protocol&lt;/li&gt;
&lt;li&gt;To stop files from the protocols staying as files, a ducktape patch was applied to &lt;code&gt;L.TileLayer.prototype.initialize&lt;/code&gt; and &lt;code&gt;L.TileLayer.WMS.prototype.initialize&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Data &amp; Cities</title>
      <link>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2.knit/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2.knit/</guid>
      <description>&lt;h2 id=&#34;data--cities&#34;&gt;Data &amp;amp; Cities&lt;/h2&gt;
</description>
    </item>
    
    <item>
      <title>Data &amp; Cities</title>
      <link>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/techniques_introduction/introduction_slides_v2/</guid>
      <description>&lt;h2 id=&#34;data--cities&#34;&gt;Data &amp;amp; Cities&lt;/h2&gt;
</description>
    </item>
    
    <item>
      <title>Machine Learning for Urban Analytics</title>
      <link>https://nkaza.github.io/slides/machinelearning/ml_slides/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://nkaza.github.io/slides/machinelearning/ml_slides/</guid>
      <description>
&lt;script src=&#34;https://nkaza.github.io/rmarkdown-libs/header-attrs/header-attrs.js&#34;&gt;&lt;/script&gt;


&lt;p&gt;class: right, bottom&lt;/p&gt;
&lt;div id=&#34;machine-learning-for-urban-analytics&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Machine Learning for Urban Analytics&lt;/h2&gt;
&lt;div id=&#34;nikhil-kaza&#34; class=&#34;section level5&#34;&gt;
&lt;h5&gt;Nikhil Kaza&lt;/h5&gt;
&lt;/div&gt;
&lt;div id=&#34;department-of-city-regional-planning-university-of-north-carolina-at-chapel-hill&#34; class=&#34;section level5&#34;&gt;
&lt;h5&gt;Department of City &amp;amp; Regional Planning &lt;br /&gt; University of North Carolina at Chapel Hill&lt;/h5&gt;
&lt;div id=&#34;updated-2022-01-22&#34; class=&#34;section level6&#34;&gt;
&lt;h6&gt;updated: 2022-01-22&lt;/h6&gt;
&lt;table style=&#34;width:6%;&#34;&gt;
&lt;colgroup&gt;
&lt;col width=&#34;5%&#34; /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;# What is Machine Learning ?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td&gt;&lt;img src=&#34;figs/ML_what.jpg&#34; width=&#34;360&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;the-purpose-of-machine-learning&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;The purpose of Machine Learning&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Mostly for Prediction…
&lt;ul&gt;
&lt;li&gt;Classification (Categories of objects e.g. spam/not spam; median strip /side walk/road, default/ prepayment / Current)&lt;/li&gt;
&lt;li&gt;Regression (Continous variables, e.g. volume of water consumption/ energy use )
&lt;ul&gt;
&lt;li&gt;Not the same as statistical inference such as linear regression.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;### OK. What kinds of prediction?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local governments: Traffic congestion&lt;/li&gt;
&lt;li&gt;Google: What ads to show&lt;/li&gt;
&lt;li&gt;Amazon: What products to buy&lt;/li&gt;
&lt;li&gt;Insurance: Risk based on prior claims&lt;/li&gt;
&lt;li&gt;UNC: Sakai use to identify students in need of intervention.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;different-terms&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Different Terms&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Prediction&lt;/li&gt;
&lt;li&gt;Projection&lt;/li&gt;
&lt;li&gt;Forecast&lt;/li&gt;
&lt;li&gt;Scenarios&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&#34;what-do-you-think-the-differences-are&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;What do you think the differences are?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;figs/sunspots.png&#34; width=&#34;1200&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;the-central-dogma-of-prediction&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;The central dogma of prediction&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;figs/centraldogma.png&#34; width=&#34;678&#34; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;components-of-a-predictor&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Components of a predictor&lt;/h1&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; features -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;center&gt;
&lt;redtext&gt;question&lt;/redtext&gt; -&amp;gt; input data -&amp;gt; features -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start with a general question&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Can I automatically detect emails that are SPAM that are not?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make it concrete&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Can I use quantitative characteristics of the emails to classify them as SPAM/HAM?&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-1&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;center&gt;
question -&amp;gt; &lt;redtext&gt;input data &lt;/redtext&gt; -&amp;gt; features -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;p&gt;&lt;img class=center src=./figs/spamR.png height=&#39;400&#39; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://rss.acs.unt.edu/Rdoc/library/kernlab/html/spam.html&#34;&gt;http://rss.acs.unt.edu/Rdoc/library/kernlab/html/spam.html&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-2&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; &lt;redtext&gt;features&lt;/redtext&gt; -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;
Dear Jeff,&lt;/p&gt;
&lt;p&gt;Can you send me your address so I can send you the invitation?&lt;/p&gt;
&lt;p&gt;Thanks,&lt;/p&gt;
&lt;p&gt;Ben
&lt;/b&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-3&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; &lt;redtext&gt;features&lt;/redtext&gt; -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;/p&gt;
&lt;p&gt;Dear Jeff,&lt;/p&gt;
&lt;p&gt;Can &lt;rt&gt;you&lt;/rt&gt; send me your address so I can send &lt;rt&gt;you&lt;/rt&gt; the invitation?&lt;/p&gt;
&lt;p&gt;Thanks,&lt;/p&gt;
&lt;p&gt;Ben
&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;Frequency of you &lt;span class=&#34;math inline&#34;&gt;\(= 2/17 = 0.118\)&lt;/span&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-4&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; &lt;redtext&gt;features&lt;/redtext&gt; -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;library(kernlab)
data(spam)
str(spam)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## &amp;#39;data.frame&amp;#39;:    4601 obs. of  58 variables:
##  $ make             : num  0 0.21 0.06 0 0 0 0 0 0.15 0.06 ...
##  $ address          : num  0.64 0.28 0 0 0 0 0 0 0 0.12 ...
##  $ all              : num  0.64 0.5 0.71 0 0 0 0 0 0.46 0.77 ...
##  $ num3d            : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ our              : num  0.32 0.14 1.23 0.63 0.63 1.85 1.92 1.88 0.61 0.19 ...
##  $ over             : num  0 0.28 0.19 0 0 0 0 0 0 0.32 ...
##  $ remove           : num  0 0.21 0.19 0.31 0.31 0 0 0 0.3 0.38 ...
##  $ internet         : num  0 0.07 0.12 0.63 0.63 1.85 0 1.88 0 0 ...
##  $ order            : num  0 0 0.64 0.31 0.31 0 0 0 0.92 0.06 ...
##  $ mail             : num  0 0.94 0.25 0.63 0.63 0 0.64 0 0.76 0 ...
##  $ receive          : num  0 0.21 0.38 0.31 0.31 0 0.96 0 0.76 0 ...
##  $ will             : num  0.64 0.79 0.45 0.31 0.31 0 1.28 0 0.92 0.64 ...
##  $ people           : num  0 0.65 0.12 0.31 0.31 0 0 0 0 0.25 ...
##  $ report           : num  0 0.21 0 0 0 0 0 0 0 0 ...
##  $ addresses        : num  0 0.14 1.75 0 0 0 0 0 0 0.12 ...
##  $ free             : num  0.32 0.14 0.06 0.31 0.31 0 0.96 0 0 0 ...
##  $ business         : num  0 0.07 0.06 0 0 0 0 0 0 0 ...
##  $ email            : num  1.29 0.28 1.03 0 0 0 0.32 0 0.15 0.12 ...
##  $ you              : num  1.93 3.47 1.36 3.18 3.18 0 3.85 0 1.23 1.67 ...
##  $ credit           : num  0 0 0.32 0 0 0 0 0 3.53 0.06 ...
##  $ your             : num  0.96 1.59 0.51 0.31 0.31 0 0.64 0 2 0.71 ...
##  $ font             : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ num000           : num  0 0.43 1.16 0 0 0 0 0 0 0.19 ...
##  $ money            : num  0 0.43 0.06 0 0 0 0 0 0.15 0 ...
##  $ hp               : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ hpl              : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ george           : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ num650           : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ lab              : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ labs             : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ telnet           : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ num857           : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ data             : num  0 0 0 0 0 0 0 0 0.15 0 ...
##  $ num415           : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ num85            : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ technology       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ num1999          : num  0 0.07 0 0 0 0 0 0 0 0 ...
##  $ parts            : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ pm               : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ direct           : num  0 0 0.06 0 0 0 0 0 0 0 ...
##  $ cs               : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ meeting          : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ original         : num  0 0 0.12 0 0 0 0 0 0.3 0 ...
##  $ project          : num  0 0 0 0 0 0 0 0 0 0.06 ...
##  $ re               : num  0 0 0.06 0 0 0 0 0 0 0 ...
##  $ edu              : num  0 0 0.06 0 0 0 0 0 0 0 ...
##  $ table            : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ conference       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ charSemicolon    : num  0 0 0.01 0 0 0 0 0 0 0.04 ...
##  $ charRoundbracket : num  0 0.132 0.143 0.137 0.135 0.223 0.054 0.206 0.271 0.03 ...
##  $ charSquarebracket: num  0 0 0 0 0 0 0 0 0 0 ...
##  $ charExclamation  : num  0.778 0.372 0.276 0.137 0.135 0 0.164 0 0.181 0.244 ...
##  $ charDollar       : num  0 0.18 0.184 0 0 0 0.054 0 0.203 0.081 ...
##  $ charHash         : num  0 0.048 0.01 0 0 0 0 0 0.022 0 ...
##  $ capitalAve       : num  3.76 5.11 9.82 3.54 3.54 ...
##  $ capitalLong      : num  61 101 485 40 40 15 4 11 445 43 ...
##  $ capitalTotal     : num  278 1028 2259 191 191 ...
##  $ type             : Factor w/ 2 levels &amp;quot;nonspam&amp;quot;,&amp;quot;spam&amp;quot;: 2 2 2 2 2 2 2 2 2 2 ...&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-5&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;table(spam$type)&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:right;&#34;&gt;
nonspam
&lt;/th&gt;
&lt;th style=&#34;text-align:right;&#34;&gt;
spam
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
2788
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
1813
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-6&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; features -&amp;gt; &lt;redtext&gt;algorithm&lt;/redtext&gt; -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;plot(density(spam$your[spam$type==&amp;quot;nonspam&amp;quot;]),
     col=&amp;quot;blue&amp;quot;,main=&amp;quot;&amp;quot;,xlab=&amp;quot;Frequency of &amp;#39;your&amp;#39;&amp;quot;)
lines(density(spam$your[spam$type==&amp;quot;spam&amp;quot;]),col=&amp;quot;red&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;figs/unnamed-chunk-5-1.png&#34; width=&#34;576&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-7&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; features -&amp;gt; &lt;redtext&gt;algorithm&lt;/redtext&gt; -&amp;gt; parameters -&amp;gt; evaluation
&lt;/center&gt;
&lt;p&gt;&lt;/br&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our algorithm&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Find a value &lt;span class=&#34;math inline&#34;&gt;\(C\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;frequency of ‘your’ &lt;span class=&#34;math inline&#34;&gt;\(&amp;gt;\)&lt;/span&gt; C&lt;/strong&gt; predict “spam”&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-8&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; features -&amp;gt; algorithm -&amp;gt; &lt;redtext&gt;parameters&lt;/redtext&gt; -&amp;gt; evaluation
&lt;/center&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;plot(density(spam$your[spam$type==&amp;quot;nonspam&amp;quot;]),
     col=&amp;quot;blue&amp;quot;,main=&amp;quot;&amp;quot;,xlab=&amp;quot;Frequency of &amp;#39;your&amp;#39;&amp;quot;)
lines(density(spam$your[spam$type==&amp;quot;spam&amp;quot;]),col=&amp;quot;red&amp;quot;)
abline(v=0.5,col=&amp;quot;black&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;figs/unnamed-chunk-6-1.png&#34; width=&#34;576&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;spam-example-9&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;SPAM Example&lt;/h1&gt;
&lt;center&gt;
question -&amp;gt; input data -&amp;gt; features -&amp;gt; algorithm -&amp;gt; parameters -&amp;gt; &lt;redtext&gt;evaluation&lt;/redtext&gt;
&lt;/center&gt;
&lt;pre class=&#34;r&#34;&gt;&lt;code&gt;prediction &amp;lt;- ifelse(spam$your &amp;gt; 0.5,&amp;quot;spam&amp;quot;,&amp;quot;nonspam&amp;quot;)
table(prediction,spam$type)/length(spam$type)&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&#34;text-align:left;&#34;&gt;
&lt;/th&gt;
&lt;th style=&#34;text-align:right;&#34;&gt;
nonspam
&lt;/th&gt;
&lt;th style=&#34;text-align:right;&#34;&gt;
spam
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left;&#34;&gt;
nonspam
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
0.4590306
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
0.1017170
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&#34;text-align:left;&#34;&gt;
spam
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
0.1469246
&lt;/td&gt;
&lt;td style=&#34;text-align:right;&#34;&gt;
0.2923278
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Accuracy &lt;span class=&#34;math inline&#34;&gt;\(\approx 0.459 + 0.292 = 0.751\)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;bike-sharing&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Bike Sharing&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;General Q: Can you predict which stations will need to be restocked with bikes at different times of the day?
&lt;ul&gt;
&lt;li&gt;How can you use neighborhood characterstics (demographics, economics, proxmity to other stations) and time of day, day of the week, season, weather etc. to predict number of open slots on bike stations?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=center src=./figs/ML_process.png height=&#39;350&#39; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;urban-sprawl-environmental-impacts&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Urban Sprawl &amp;amp; Environmental Impacts&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;General Q: Can you predict number of bad air quality days from urban form characteristics?
&lt;ul&gt;
&lt;li&gt;Can urban landscape metrics and other demographic characteristics predict bad air quality days in a year?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=center src=./figs/ML_process.png height=&#39;350&#39; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;urban-form-healthy-behaviours&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Urban Form &amp;amp; Healthy Behaviours&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;General Q: How does urban form characteristics relate to healthy outcomes?
&lt;ul&gt;
&lt;li&gt;How does street density, intersection density, activity density etc. impact residents’ healthy behaviours (healthy food consumption, exercise etc.)?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=center src=./figs/ML_process.png height=&#39;350&#39; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;energy-conservation-mortgage-risks&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Energy Conservation &amp;amp; Mortgage Risks&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;General Q: Should we reward households with conservation proclivities with a break on mortgage interest rates?
&lt;ul&gt;
&lt;li&gt;Is the choice to buy energy star appliances and houses in infill urban areas correlated with lower default/prepayment rate?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=center src=./figs/ML_process.png height=&#39;350&#39; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;mode-choice&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Mode Choice&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;General Q: Can we predict household transportation mode choice?
&lt;ul&gt;
&lt;li&gt;Given the weather, cost of travel, cost of parking etc. what is the likelihood that a household will choose to drive vs. taking public transit.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class=center src=./figs/ML_process.png height=&#39;350&#39; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;experimental-design&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Experimental Design&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/studydesign.png height = &#39;200&#39;/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=center src=./figs/cross_validation.png height=&#39;300&#39; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div id=&#34;k-nearest-neighbor&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;K Nearest Neighbor&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/knnClassification.png height = &#39;400&#39;/&gt;&lt;/p&gt;
&lt;div id=&#34;footnote-httpsen.wikipedia.orgwikik-nearest_neighbors_algorithm&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;.footnote[ &lt;a href=&#34;https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm&#34; class=&#34;uri&#34;&gt;https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm&lt;/a&gt;]&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;logistic-regression-misnomer&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Logistic Regression (Misnomer)&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/logisticregression.png height = &#39;400&#39;/&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;http://dataaspirant.com/2017/03/02/how-logistic-regression-model-works/&#34; class=&#34;uri&#34;&gt;http://dataaspirant.com/2017/03/02/how-logistic-regression-model-works/&lt;/a&gt;]&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;trees&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Trees&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/regressiontree.gif height = &#39;400&#39;/&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;https://www.techemergence.com/what-is-machine-learning/&#34; class=&#34;uri&#34;&gt;https://www.techemergence.com/what-is-machine-learning/&lt;/a&gt;]&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;trees-1&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Trees&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/obamatree.png height = &#39;600&#39;/&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;https://nyti.ms/2QRnQxI&#34; class=&#34;uri&#34;&gt;https://nyti.ms/2QRnQxI&lt;/a&gt;]&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;forests-ensembles&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Forests (Ensembles)&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/forests.png height=400&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;http://www.robots.ox.ac.uk/~az/lectures/ml/lect5.pdf&#34; class=&#34;uri&#34;&gt;http://www.robots.ox.ac.uk/~az/lectures/ml/lect5.pdf&lt;/a&gt;]&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;and-lots-more&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;And lots more…&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/caretmodels.png height=700&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;class: right, bottom, inverse&lt;/p&gt;
&lt;div id=&#34;some-terminology&#34; class=&#34;section level2&#34;&gt;
&lt;h2&gt;Some Terminology&lt;/h2&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&#34;ensembling&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Ensembling&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/ensembling2.png height=350&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;https://quantdare.com/what-is-the-difference-between-bagging-and-boosting/&#34; class=&#34;uri&#34;&gt;https://quantdare.com/what-is-the-difference-between-bagging-and-boosting/&lt;/a&gt;]&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;bootstrap-aggregating-bagging&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Bootstrap aggregating (bagging)&lt;/h1&gt;
&lt;ol style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Resample cases and recalculate predictions&lt;/li&gt;
&lt;li&gt;Average or majority vote&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img class=center src=./figs/bagging.png height=400&gt;&lt;/p&gt;
&lt;p&gt;.footnote[
list()]&lt;/p&gt;
&lt;hr /&gt;
&lt;/div&gt;
&lt;div id=&#34;boosting&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Boosting&lt;/h1&gt;
&lt;ol style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Create a model&lt;/li&gt;
&lt;li&gt;Focus on the errors of the model and create another model&lt;/li&gt;
&lt;li&gt;Continue this process until no improvement occurs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img class=center src=./figs/boosted-trees-process.png height=400&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;https://blog.bigml.com/2017/03/14/introduction-to-boosted-trees/&#34; class=&#34;uri&#34;&gt;https://blog.bigml.com/2017/03/14/introduction-to-boosted-trees/&lt;/a&gt;]&lt;/p&gt;
&lt;table style=&#34;width:6%;&#34;&gt;
&lt;colgroup&gt;
&lt;col width=&#34;5%&#34; /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;# Boosting Explained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td&gt;&lt;img class=center src=./figs/boosting1.png height=500&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;.footnote[&lt;a href=&#34;https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d&#34; class=&#34;uri&#34;&gt;https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d&lt;/a&gt;]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div id=&#34;boosting-explained&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Boosting Explained&lt;/h1&gt;
&lt;p&gt;&lt;img class=center src=./figs/boosting2.png height=500&gt;&lt;/p&gt;
&lt;p&gt;.footnote[&lt;a href=&#34;https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d&#34; class=&#34;uri&#34;&gt;https://medium.com/mlreview/gradient-boosting-from-scratch-1e317ae4587d&lt;/a&gt;]&lt;/p&gt;
&lt;table style=&#34;width:6%;&#34;&gt;
&lt;colgroup&gt;
&lt;col width=&#34;5%&#34; /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;## Basic terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td&gt;In general, &lt;strong&gt;Positive&lt;/strong&gt; = identified and &lt;strong&gt;negative&lt;/strong&gt; = rejected. Therefore:&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;&lt;strong&gt;True positive&lt;/strong&gt; (TP) = correctly identified (e.g. Real buildings identified as buildings by the model.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td&gt;&lt;strong&gt;False positive&lt;/strong&gt; (FP) = incorrectly identified (e.g. Real non-buildings identified as buildings)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;&lt;strong&gt;True negative&lt;/strong&gt; (TN) = correctly rejected (e.g. Real non-buildings identified as non-buildings by the model)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td&gt;&lt;strong&gt;False negative&lt;/strong&gt; (FN) = incorrectly rejected (e.g. Real buildings identified as roads by the model)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;&lt;a href=&#34;http://en.wikipedia.org/wiki/Sensitivity_and_specificity&#34;&gt;http://en.wikipedia.org/wiki/Sensitivity_and_specificity&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div id=&#34;accuracy-metrics&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Accuracy Metrics&lt;/h1&gt;
&lt;ol style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Mean squared error (or root mean squared error)&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Continuous data, sensitive to outliers&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;2&#34; style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Median absolute deviation&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Continuous data, often more robust&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;3&#34; style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Sensitivity (recall): &lt;span class=&#34;math inline&#34;&gt;\(TP/(TP+FN)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;If you want few missed positives (e.g. identify as many buildings as possible, even if you misidentify some non-buildings as buildings)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;4&#34; style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Specificity: &lt;span class=&#34;math inline&#34;&gt;\(TN/(TN+FP)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;If you want few negatives called positives (e.g. identify more buildings correctly, even if you miss some true buildings )&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;5&#34; style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Accuracy &lt;span class=&#34;math inline&#34;&gt;\((TP+TN)/(TP + TN + FP + FN)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Weights false positives/negatives equally&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;6&#34; style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Concordance&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;One example is &lt;a href=&#34;http://en.wikipedia.org/wiki/Cohen%27s_kappa&#34;&gt;kappa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&#34;7&#34; style=&#34;list-style-type: decimal&#34;&gt;
&lt;li&gt;Predictive value of a positive (precision): &lt;span class=&#34;math inline&#34;&gt;\(TP/(TP +FP)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;When the prevalance is low (e.g. identify a rare class of a ‘tent city’ in US cities)&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&#34;width:6%;&#34;&gt;
&lt;colgroup&gt;
&lt;col width=&#34;5%&#34; /&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr class=&#34;odd&#34;&gt;
&lt;td&gt;# Conclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class=&#34;even&#34;&gt;
&lt;td&gt;&lt;img class=center src=./figs/mlconsiderations.jpg height=500&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div id=&#34;practical-advice&#34; class=&#34;section level1&#34;&gt;
&lt;h1&gt;Practical Advice&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Focus on the importance of the problem&lt;/li&gt;
&lt;li&gt;Try simple models first&lt;/li&gt;
&lt;li&gt;Much of machine learning is about trying to create good features (variables); Models are secondary&lt;/li&gt;
&lt;li&gt;Scale the features to have similar values (sale price in millions, sq.ft in 1000s don’t work well)&lt;/li&gt;
&lt;li&gt;Ideally you want these features to be minimally correlated&lt;/li&gt;
&lt;li&gt;Some algorithms requires lots of training data. Focus on creating good labelled data. Share it with others&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>
