github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/docs/slides/plugin/notes/notes.html (about)

     1  <!doctype html>
     2  <html lang="en">
     3  	<head>
     4  		<meta charset="utf-8">
     5  
     6  		<title>reveal.js - Slide Notes</title>
     7  
     8  		<style>
     9  			body {
    10  				font-family: Helvetica;
    11  				font-size: 18px;
    12  			}
    13  
    14  			#current-slide,
    15  			#upcoming-slide,
    16  			#speaker-controls {
    17  				padding: 6px;
    18  				box-sizing: border-box;
    19  				-moz-box-sizing: border-box;
    20  			}
    21  
    22  			#current-slide iframe,
    23  			#upcoming-slide iframe {
    24  				width: 100%;
    25  				height: 100%;
    26  				border: 1px solid #ddd;
    27  			}
    28  
    29  			#current-slide .label,
    30  			#upcoming-slide .label {
    31  				position: absolute;
    32  				top: 10px;
    33  				left: 10px;
    34  				z-index: 2;
    35  			}
    36  
    37  			.overlay-element {
    38  				height: 34px;
    39  				line-height: 34px;
    40  				padding: 0 10px;
    41  				text-shadow: none;
    42  				background: rgba( 220, 220, 220, 0.8 );
    43  				color: #222;
    44  				font-size: 14px;
    45  			}
    46  
    47  			.overlay-element.interactive:hover {
    48  				background: rgba( 220, 220, 220, 1 );
    49  			}
    50  
    51  			#current-slide {
    52  				position: absolute;
    53  				width: 60%;
    54  				height: 100%;
    55  				top: 0;
    56  				left: 0;
    57  				padding-right: 0;
    58  			}
    59  
    60  			#upcoming-slide {
    61  				position: absolute;
    62  				width: 40%;
    63  				height: 40%;
    64  				right: 0;
    65  				top: 0;
    66  			}
    67  
    68  			/* Speaker controls */
    69  			#speaker-controls {
    70  				position: absolute;
    71  				top: 40%;
    72  				right: 0;
    73  				width: 40%;
    74  				height: 60%;
    75  				overflow: auto;
    76  				font-size: 18px;
    77  			}
    78  
    79  				.speaker-controls-time.hidden,
    80  				.speaker-controls-notes.hidden {
    81  					display: none;
    82  				}
    83  
    84  				.speaker-controls-time .label,
    85  				.speaker-controls-pace .label,
    86  				.speaker-controls-notes .label {
    87  					text-transform: uppercase;
    88  					font-weight: normal;
    89  					font-size: 0.66em;
    90  					color: #666;
    91  					margin: 0;
    92  				}
    93  
    94  				.speaker-controls-time, .speaker-controls-pace {
    95  					border-bottom: 1px solid rgba( 200, 200, 200, 0.5 );
    96  					margin-bottom: 10px;
    97  					padding: 10px 16px;
    98  					padding-bottom: 20px;
    99  					cursor: pointer;
   100  				}
   101  
   102  				.speaker-controls-time .reset-button {
   103  					opacity: 0;
   104  					float: right;
   105  					color: #666;
   106  					text-decoration: none;
   107  				}
   108  				.speaker-controls-time:hover .reset-button {
   109  					opacity: 1;
   110  				}
   111  
   112  				.speaker-controls-time .timer,
   113  				.speaker-controls-time .clock {
   114  					width: 50%;
   115  				}
   116  
   117  				.speaker-controls-time .timer,
   118  				.speaker-controls-time .clock,
   119  				.speaker-controls-time .pacing .hours-value,
   120  				.speaker-controls-time .pacing .minutes-value,
   121  				.speaker-controls-time .pacing .seconds-value {
   122  					font-size: 1.9em;
   123  				}
   124  
   125  				.speaker-controls-time .timer {
   126  					float: left;
   127  				}
   128  
   129  				.speaker-controls-time .clock {
   130  					float: right;
   131  					text-align: right;
   132  				}
   133  
   134  				.speaker-controls-time span.mute {
   135  					opacity: 0.3;
   136  				}
   137  
   138  				.speaker-controls-time .pacing-title {
   139  					margin-top: 5px;
   140  				}
   141  
   142  				.speaker-controls-time .pacing.ahead {
   143  					color: blue;
   144  				}
   145  
   146  				.speaker-controls-time .pacing.on-track {
   147  					color: green;
   148  				}
   149  
   150  				.speaker-controls-time .pacing.behind {
   151  					color: red;
   152  				}
   153  
   154  				.speaker-controls-notes {
   155  					padding: 10px 16px;
   156  				}
   157  
   158  				.speaker-controls-notes .value {
   159  					margin-top: 5px;
   160  					line-height: 1.4;
   161  					font-size: 1.2em;
   162  				}
   163  
   164  			/* Layout selector */
   165  			#speaker-layout {
   166  				position: absolute;
   167  				top: 10px;
   168  				right: 10px;
   169  				color: #222;
   170  				z-index: 10;
   171  			}
   172  				#speaker-layout select {
   173  					position: absolute;
   174  					width: 100%;
   175  					height: 100%;
   176  					top: 0;
   177  					left: 0;
   178  					border: 0;
   179  					box-shadow: 0;
   180  					cursor: pointer;
   181  					opacity: 0;
   182  
   183  					font-size: 1em;
   184  					background-color: transparent;
   185  
   186  					-moz-appearance: none;
   187  					-webkit-appearance: none;
   188  					-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
   189  				}
   190  
   191  				#speaker-layout select:focus {
   192  					outline: none;
   193  					box-shadow: none;
   194  				}
   195  
   196  			.clear {
   197  				clear: both;
   198  			}
   199  
   200  			/* Speaker layout: Wide */
   201  			body[data-speaker-layout="wide"] #current-slide,
   202  			body[data-speaker-layout="wide"] #upcoming-slide {
   203  				width: 50%;
   204  				height: 45%;
   205  				padding: 6px;
   206  			}
   207  
   208  			body[data-speaker-layout="wide"] #current-slide {
   209  				top: 0;
   210  				left: 0;
   211  			}
   212  
   213  			body[data-speaker-layout="wide"] #upcoming-slide {
   214  				top: 0;
   215  				left: 50%;
   216  			}
   217  
   218  			body[data-speaker-layout="wide"] #speaker-controls {
   219  				top: 45%;
   220  				left: 0;
   221  				width: 100%;
   222  				height: 50%;
   223  				font-size: 1.25em;
   224  			}
   225  
   226  			/* Speaker layout: Tall */
   227  			body[data-speaker-layout="tall"] #current-slide,
   228  			body[data-speaker-layout="tall"] #upcoming-slide {
   229  				width: 45%;
   230  				height: 50%;
   231  				padding: 6px;
   232  			}
   233  
   234  			body[data-speaker-layout="tall"] #current-slide {
   235  				top: 0;
   236  				left: 0;
   237  			}
   238  
   239  			body[data-speaker-layout="tall"] #upcoming-slide {
   240  				top: 50%;
   241  				left: 0;
   242  			}
   243  
   244  			body[data-speaker-layout="tall"] #speaker-controls {
   245  				padding-top: 40px;
   246  				top: 0;
   247  				left: 45%;
   248  				width: 55%;
   249  				height: 100%;
   250  				font-size: 1.25em;
   251  			}
   252  
   253  			/* Speaker layout: Notes only */
   254  			body[data-speaker-layout="notes-only"] #current-slide,
   255  			body[data-speaker-layout="notes-only"] #upcoming-slide {
   256  				display: none;
   257  			}
   258  
   259  			body[data-speaker-layout="notes-only"] #speaker-controls {
   260  				padding-top: 40px;
   261  				top: 0;
   262  				left: 0;
   263  				width: 100%;
   264  				height: 100%;
   265  				font-size: 1.25em;
   266  			}
   267  
   268  			@media screen and (max-width: 1080px) {
   269  				body[data-speaker-layout="default"] #speaker-controls {
   270  					font-size: 16px;
   271  				}
   272  			}
   273  
   274  			@media screen and (max-width: 900px) {
   275  				body[data-speaker-layout="default"] #speaker-controls {
   276  					font-size: 14px;
   277  				}
   278  			}
   279  
   280  			@media screen and (max-width: 800px) {
   281  				body[data-speaker-layout="default"] #speaker-controls {
   282  					font-size: 12px;
   283  				}
   284  			}
   285  
   286  		</style>
   287  	</head>
   288  
   289  	<body>
   290  
   291  		<div id="current-slide"></div>
   292  		<div id="upcoming-slide"><span class="overlay-element label">Upcoming</span></div>
   293  		<div id="speaker-controls">
   294  			<div class="speaker-controls-time">
   295  				<h4 class="label">Time <span class="reset-button">Click to Reset</span></h4>
   296  				<div class="clock">
   297  					<span class="clock-value">0:00 AM</span>
   298  				</div>
   299  				<div class="timer">
   300  					<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
   301  				</div>
   302  				<div class="clear"></div>
   303  
   304  				<h4 class="label pacing-title" style="display: none">Pacing – Time to finish current slide</h4>
   305  				<div class="pacing" style="display: none">
   306  					<span class="hours-value">00</span><span class="minutes-value">:00</span><span class="seconds-value">:00</span>
   307  				</div>
   308  			</div>
   309  
   310  			<div class="speaker-controls-notes hidden">
   311  				<h4 class="label">Notes</h4>
   312  				<div class="value"></div>
   313  			</div>
   314  		</div>
   315  		<div id="speaker-layout" class="overlay-element interactive">
   316  			<span class="speaker-layout-label"></span>
   317  			<select class="speaker-layout-dropdown"></select>
   318  		</div>
   319  
   320  		<script src="../../plugin/markdown/marked.js"></script>
   321  		<script>
   322  
   323  			(function() {
   324  
   325  				var notes,
   326  					notesValue,
   327  					currentState,
   328  					currentSlide,
   329  					upcomingSlide,
   330  					layoutLabel,
   331  					layoutDropdown,
   332  					connected = false;
   333  
   334  				var SPEAKER_LAYOUTS = {
   335  					'default': 'Default',
   336  					'wide': 'Wide',
   337  					'tall': 'Tall',
   338  					'notes-only': 'Notes only'
   339  				};
   340  
   341  				setupLayout();
   342  
   343  				window.addEventListener( 'message', function( event ) {
   344  
   345  					var data = JSON.parse( event.data );
   346  
   347  					// The overview mode is only useful to the reveal.js instance
   348  					// where navigation occurs so we don't sync it
   349  					if( data.state ) delete data.state.overview;
   350  
   351  					// Messages sent by the notes plugin inside of the main window
   352  					if( data && data.namespace === 'reveal-notes' ) {
   353  						if( data.type === 'connect' ) {
   354  							handleConnectMessage( data );
   355  						}
   356  						else if( data.type === 'state' ) {
   357  							handleStateMessage( data );
   358  						}
   359  					}
   360  					// Messages sent by the reveal.js inside of the current slide preview
   361  					else if( data && data.namespace === 'reveal' ) {
   362  						if( /ready/.test( data.eventName ) ) {
   363  							// Send a message back to notify that the handshake is complete
   364  							window.opener.postMessage( JSON.stringify({ namespace: 'reveal-notes', type: 'connected'} ), '*' );
   365  						}
   366  						else if( /slidechanged|fragmentshown|fragmenthidden|paused|resumed/.test( data.eventName ) && currentState !== JSON.stringify( data.state ) ) {
   367  
   368  							window.opener.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ]} ), '*' );
   369  
   370  						}
   371  					}
   372  
   373  				} );
   374  
   375  				/**
   376  				 * Called when the main window is trying to establish a
   377  				 * connection.
   378  				 */
   379  				function handleConnectMessage( data ) {
   380  
   381  					if( connected === false ) {
   382  						connected = true;
   383  
   384  						setupIframes( data );
   385  						setupKeyboard();
   386  						setupNotes();
   387  						setupTimer();
   388  					}
   389  
   390  				}
   391  
   392  				/**
   393  				 * Called when the main window sends an updated state.
   394  				 */
   395  				function handleStateMessage( data ) {
   396  
   397  					// Store the most recently set state to avoid circular loops
   398  					// applying the same state
   399  					currentState = JSON.stringify( data.state );
   400  
   401  					// No need for updating the notes in case of fragment changes
   402  					if ( data.notes ) {
   403  						notes.classList.remove( 'hidden' );
   404  						notesValue.style.whiteSpace = data.whitespace;
   405  						if( data.markdown ) {
   406  							notesValue.innerHTML = marked( data.notes );
   407  						}
   408  						else {
   409  							notesValue.innerHTML = data.notes;
   410  						}
   411  					}
   412  					else {
   413  						notes.classList.add( 'hidden' );
   414  					}
   415  
   416  					// Update the note slides
   417  					currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
   418  					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'setState', args: [ data.state ] }), '*' );
   419  					upcomingSlide.contentWindow.postMessage( JSON.stringify({ method: 'next' }), '*' );
   420  
   421  				}
   422  
   423  				// Limit to max one state update per X ms
   424  				handleStateMessage = debounce( handleStateMessage, 200 );
   425  
   426  				/**
   427  				 * Forward keyboard events to the current slide window.
   428  				 * This enables keyboard events to work even if focus
   429  				 * isn't set on the current slide iframe.
   430  				 */
   431  				function setupKeyboard() {
   432  
   433  					document.addEventListener( 'keydown', function( event ) {
   434  						currentSlide.contentWindow.postMessage( JSON.stringify({ method: 'triggerKey', args: [ event.keyCode ] }), '*' );
   435  					} );
   436  
   437  				}
   438  
   439  				/**
   440  				 * Creates the preview iframes.
   441  				 */
   442  				function setupIframes( data ) {
   443  
   444  					var params = [
   445  						'receiver',
   446  						'progress=false',
   447  						'history=false',
   448  						'transition=none',
   449  						'autoSlide=0',
   450  						'backgroundTransition=none'
   451  					].join( '&' );
   452  
   453  					var urlSeparator = /\?/.test(data.url) ? '&' : '?';
   454  					var hash = '#/' + data.state.indexh + '/' + data.state.indexv;
   455  					var currentURL = data.url + urlSeparator + params + '&postMessageEvents=true' + hash;
   456  					var upcomingURL = data.url + urlSeparator + params + '&controls=false' + hash;
   457  
   458  					currentSlide = document.createElement( 'iframe' );
   459  					currentSlide.setAttribute( 'width', 1280 );
   460  					currentSlide.setAttribute( 'height', 1024 );
   461  					currentSlide.setAttribute( 'src', currentURL );
   462  					document.querySelector( '#current-slide' ).appendChild( currentSlide );
   463  
   464  					upcomingSlide = document.createElement( 'iframe' );
   465  					upcomingSlide.setAttribute( 'width', 640 );
   466  					upcomingSlide.setAttribute( 'height', 512 );
   467  					upcomingSlide.setAttribute( 'src', upcomingURL );
   468  					document.querySelector( '#upcoming-slide' ).appendChild( upcomingSlide );
   469  
   470  				}
   471  
   472  				/**
   473  				 * Setup the notes UI.
   474  				 */
   475  				function setupNotes() {
   476  
   477  					notes = document.querySelector( '.speaker-controls-notes' );
   478  					notesValue = document.querySelector( '.speaker-controls-notes .value' );
   479  
   480  				}
   481  
   482  				function getTimings() {
   483  
   484  					var slides = Reveal.getSlides();
   485  					var defaultTiming = Reveal.getConfig().defaultTiming;
   486  					if (defaultTiming == null) {
   487  						return null;
   488  					}
   489  					var timings = [];
   490  					for ( var i in slides ) {
   491  						var slide = slides[i];
   492  						var timing = defaultTiming;
   493  						if( slide.hasAttribute( 'data-timing' )) {
   494  							var t = slide.getAttribute( 'data-timing' );
   495  							timing = parseInt(t);
   496  							if( isNaN(timing) ) {
   497  								console.warn("Could not parse timing '" + t + "' of slide " + i + "; using default of " + defaultTiming);
   498  								timing = defaultTiming;
   499  							}
   500  						}
   501  						timings.push(timing);
   502  					}
   503  					return timings;
   504  
   505  				}
   506  
   507  				/**
   508  				 * Return the number of seconds allocated for presenting
   509  				 * all slides up to and including this one.
   510  				 */
   511  				function getTimeAllocated(timings) {
   512  
   513  					var slides = Reveal.getSlides();
   514  					var allocated = 0;
   515  					var currentSlide = Reveal.getSlidePastCount();
   516  					for (var i in slides.slice(0, currentSlide + 1)) {
   517  						allocated += timings[i];
   518  					}
   519  					return allocated;
   520  
   521  				}
   522  
   523  				/**
   524  				 * Create the timer and clock and start updating them
   525  				 * at an interval.
   526  				 */
   527  				function setupTimer() {
   528  
   529  					var start = new Date(),
   530  					timeEl = document.querySelector( '.speaker-controls-time' ),
   531  					clockEl = timeEl.querySelector( '.clock-value' ),
   532  					hoursEl = timeEl.querySelector( '.hours-value' ),
   533  					minutesEl = timeEl.querySelector( '.minutes-value' ),
   534  					secondsEl = timeEl.querySelector( '.seconds-value' ),
   535  					pacingTitleEl = timeEl.querySelector( '.pacing-title' ),
   536  					pacingEl = timeEl.querySelector( '.pacing' ),
   537  					pacingHoursEl = pacingEl.querySelector( '.hours-value' ),
   538  					pacingMinutesEl = pacingEl.querySelector( '.minutes-value' ),
   539  					pacingSecondsEl = pacingEl.querySelector( '.seconds-value' );
   540  
   541  					var timings = getTimings();
   542  					if (timings !== null) {
   543  						pacingTitleEl.style.removeProperty('display');
   544  						pacingEl.style.removeProperty('display');
   545  					}
   546  
   547  					function _displayTime( hrEl, minEl, secEl, time) {
   548  
   549  						var sign = Math.sign(time) == -1 ? "-" : "";
   550  						time = Math.abs(Math.round(time / 1000));
   551  						var seconds = time % 60;
   552  						var minutes = Math.floor( time / 60 ) % 60 ;
   553  						var hours = Math.floor( time / ( 60 * 60 )) ;
   554  						hrEl.innerHTML = sign + zeroPadInteger( hours );
   555  						if (hours == 0) {
   556  							hrEl.classList.add( 'mute' );
   557  						}
   558  						else {
   559  							hrEl.classList.remove( 'mute' );
   560  						}
   561  						minEl.innerHTML = ':' + zeroPadInteger( minutes );
   562  						if (hours == 0 && minutes == 0) {
   563  							minEl.classList.add( 'mute' );
   564  						}
   565  						else {
   566  							minEl.classList.remove( 'mute' );
   567  						}
   568  						secEl.innerHTML = ':' + zeroPadInteger( seconds );
   569  					}
   570  
   571  					function _updateTimer() {
   572  
   573  						var diff, hours, minutes, seconds,
   574  						now = new Date();
   575  
   576  						diff = now.getTime() - start.getTime();
   577  
   578  						clockEl.innerHTML = now.toLocaleTimeString( 'en-US', { hour12: true, hour: '2-digit', minute:'2-digit' } );
   579  						_displayTime( hoursEl, minutesEl, secondsEl, diff );
   580  						if (timings !== null) {
   581  							_updatePacing(diff);
   582  						}
   583  
   584  					}
   585  
   586  					function _updatePacing(diff) {
   587  
   588  						var slideEndTiming = getTimeAllocated(timings) * 1000;
   589  						var currentSlide = Reveal.getSlidePastCount();
   590  						var currentSlideTiming = timings[currentSlide] * 1000;
   591  						var timeLeftCurrentSlide = slideEndTiming - diff;
   592  						if (timeLeftCurrentSlide < 0) {
   593  							pacingEl.className = 'pacing behind';
   594  						}
   595  						else if (timeLeftCurrentSlide < currentSlideTiming) {
   596  							pacingEl.className = 'pacing on-track';
   597  						}
   598  						else {
   599  							pacingEl.className = 'pacing ahead';
   600  						}
   601  						_displayTime( pacingHoursEl, pacingMinutesEl, pacingSecondsEl, timeLeftCurrentSlide );
   602  
   603  					}
   604  
   605  					// Update once directly
   606  					_updateTimer();
   607  
   608  					// Then update every second
   609  					setInterval( _updateTimer, 1000 );
   610  
   611  					function _resetTimer() {
   612  
   613  						if (timings == null) {
   614  							start = new Date();
   615  						}
   616  						else {
   617  							// Reset timer to beginning of current slide
   618  							var slideEndTiming = getTimeAllocated(timings) * 1000;
   619  							var currentSlide = Reveal.getSlidePastCount();
   620  							var currentSlideTiming = timings[currentSlide] * 1000;
   621  							var previousSlidesTiming = slideEndTiming - currentSlideTiming;
   622  							var now = new Date();
   623  							start = new Date(now.getTime() - previousSlidesTiming);
   624  						}
   625  						_updateTimer();
   626  
   627  					}
   628  
   629  					timeEl.addEventListener( 'click', function() {
   630  						_resetTimer();
   631  						return false;
   632  					} );
   633  
   634  				}
   635  
   636  				/**
   637  				 * Sets up the speaker view layout and layout selector.
   638  				 */
   639  				function setupLayout() {
   640  
   641  					layoutDropdown = document.querySelector( '.speaker-layout-dropdown' );
   642  					layoutLabel = document.querySelector( '.speaker-layout-label' );
   643  
   644  					// Render the list of available layouts
   645  					for( var id in SPEAKER_LAYOUTS ) {
   646  						var option = document.createElement( 'option' );
   647  						option.setAttribute( 'value', id );
   648  						option.textContent = SPEAKER_LAYOUTS[ id ];
   649  						layoutDropdown.appendChild( option );
   650  					}
   651  
   652  					// Monitor the dropdown for changes
   653  					layoutDropdown.addEventListener( 'change', function( event ) {
   654  
   655  						setLayout( layoutDropdown.value );
   656  
   657  					}, false );
   658  
   659  					// Restore any currently persisted layout
   660  					setLayout( getLayout() );
   661  
   662  				}
   663  
   664  				/**
   665  				 * Sets a new speaker view layout. The layout is persisted
   666  				 * in local storage.
   667  				 */
   668  				function setLayout( value ) {
   669  
   670  					var title = SPEAKER_LAYOUTS[ value ];
   671  
   672  					layoutLabel.innerHTML = 'Layout' + ( title ? ( ': ' + title ) : '' );
   673  					layoutDropdown.value = value;
   674  
   675  					document.body.setAttribute( 'data-speaker-layout', value );
   676  
   677  					// Persist locally
   678  					if( supportsLocalStorage() ) {
   679  						window.localStorage.setItem( 'reveal-speaker-layout', value );
   680  					}
   681  
   682  				}
   683  
   684  				/**
   685  				 * Returns the ID of the most recently set speaker layout
   686  				 * or our default layout if none has been set.
   687  				 */
   688  				function getLayout() {
   689  
   690  					if( supportsLocalStorage() ) {
   691  						var layout = window.localStorage.getItem( 'reveal-speaker-layout' );
   692  						if( layout ) {
   693  							return layout;
   694  						}
   695  					}
   696  
   697  					// Default to the first record in the layouts hash
   698  					for( var id in SPEAKER_LAYOUTS ) {
   699  						return id;
   700  					}
   701  
   702  				}
   703  
   704  				function supportsLocalStorage() {
   705  
   706  					try {
   707  						localStorage.setItem('test', 'test');
   708  						localStorage.removeItem('test');
   709  						return true;
   710  					}
   711  					catch( e ) {
   712  						return false;
   713  					}
   714  
   715  				}
   716  
   717  				function zeroPadInteger( num ) {
   718  
   719  					var str = '00' + parseInt( num );
   720  					return str.substring( str.length - 2 );
   721  
   722  				}
   723  
   724  				/**
   725  				 * Limits the frequency at which a function can be called.
   726  				 */
   727  				function debounce( fn, ms ) {
   728  
   729  					var lastTime = 0,
   730  						timeout;
   731  
   732  					return function() {
   733  
   734  						var args = arguments;
   735  						var context = this;
   736  
   737  						clearTimeout( timeout );
   738  
   739  						var timeSinceLastCall = Date.now() - lastTime;
   740  						if( timeSinceLastCall > ms ) {
   741  							fn.apply( context, args );
   742  							lastTime = Date.now();
   743  						}
   744  						else {
   745  							timeout = setTimeout( function() {
   746  								fn.apply( context, args );
   747  								lastTime = Date.now();
   748  							}, ms - timeSinceLastCall );
   749  						}
   750  
   751  					}
   752  
   753  				}
   754  
   755  			})();
   756  
   757  		</script>
   758  	</body>
   759  </html>