<!DOCTYPE html>

<meta charset="utf8">
<title>DZSlides</title>

<div id="present">
 <iframe></iframe>
</div>

<div id="future">
 <iframe></iframe>
</div>

<div id="slideidx" onclick="Dz.setCursor(prompt('Go to slide...','1'));">?</div>
<div id="nextslideidx">?</div>

<div id="notes">
 <p id="content"></p>
</div>

<div id="slidecount">?</div>
<button id="popup-button" onclick="Dz.popup()">Pop-up</button>

<div id="time" onclick="Dz.resetClock()">
 <span id="hours">00</span>:<span id="minutes">00</span>:<span id="seconds">00</span>
</div>

<style>
 html, body {
   height: 100%;
   color: white;
 }

 body {
   font-family: sans-serif;
   overflow: hidden;
 }

 #present, #future {
   bottom: 234px;
   position: absolute;
   top: 0;
   z-index: 5;
 }

 #present {
   left: 0;
   right: 50%;
   border-right: 4px solid #555;
 }

 #future {
   left: 50%;
   right: 0;
   border-left: 4px solid #555;
 }

 #notes {
   background: #EEE;
   border-top: 8px solid #555;
   bottom: 0;
   color: #444;
   font-size: 30px;
   height: 226px;
   left: 0;
   padding: 0 250px 0 20px;
   position: absolute;
   right: 0;
   overflow: auto;
 }

 #time {
   background: #888;
   border-top-left-radius: 10px;
   border-top-right-radius: 10px;
   bottom: 0;
   font-size: 40px;
   font-weight: bold;
   height: 52px;
   line-height: 52px;
   right: 30px;
   position: absolute;
   text-align: center;
   width: 200px;
   cursor: pointer;
 }

 iframe {
   border: none;
   height: 100%;
   pointer-events: none;
   width: 100%;
 }

 #slideidx,
 #nextslideidx,
 #slidecount {
   background: #888;
   font-size: 40px;
   font-weight: bold;
   height: 52px;
   line-height: 52px;
   margin: 0;
   position: absolute;
   text-align: center;
   z-index: 10;
 }

 #slideidx,
 #nextslideidx {
   border-bottom: 4px solid #555;
   bottom: 230px;
   padding: 0 10px;
 }

 #slideidx {
   border-right: 4px solid #555;
   border-top-left-radius: 10px;
   cursor: pointer;
   right: 50%;
 }

 #nextslideidx {
   border-left: 4px solid #555;
   border-top-right-radius: 10px;
   left: 50%;
 }

 #slidecount {
   border-bottom-left-radius: 10px;
   border-bottom-right-radius: 10px;
   border-top: 4px solid #555;
   bottom: 174px;
   width: 200px;
   right: 30px;
 }

 #popup-button {
   bottom: 88px;
   background: #888;
   border: none;
   border-radius: 10px;
   color: #FFF;
   cursor: pointer;
   height: 52px;
   font-size: 30px;
   font-weight: bold;
   position: absolute;
   right: 30px;
   width: 200px;
   z-index: 10;
 }
</style>

<script>
 var Dz = {
   views: {},
   notes: null,
   url: null,
   idx: 1
 };

 Dz.init = function() {
   this.clock_start = 0;
   this.startClock();
   this.loadIframes();
 }

 Dz.onkeydown = function(aEvent) {
   // Don't intercept keyboard shortcuts
   if (aEvent.altKey
     || aEvent.ctrlKey
     || aEvent.metaKey
     || aEvent.shiftKey) {
     return;
   }
   if ( aEvent.keyCode == 37 // left arrow
     || aEvent.keyCode == 38 // up arrow
     || aEvent.keyCode == 33 // page up
   ) {
     aEvent.preventDefault();
     this.back();
   }
   if ( aEvent.keyCode == 39 // right arrow
     || aEvent.keyCode == 40 // down arrow
     || aEvent.keyCode == 34 // page down
   ) {
     aEvent.preventDefault();
     this.forward();
   }
   if (aEvent.keyCode == 35) { // end
     aEvent.preventDefault();
     this.goEnd();
   }
   if (aEvent.keyCode == 36) { // home
     aEvent.preventDefault();
     this.goStart();
   }
   if (aEvent.keyCode == 32) { // space
     aEvent.preventDefault();
     this.toggleContent();
   }
 }

 Dz.onmessage = function(aEvent) {
   var argv = aEvent.data.split(" "), argc = argv.length;
   argv.forEach(function(e, i, a) { a[i] = decodeURIComponent(e) });
   if (argv[0] === "CURSOR" && argc === 2) {
     if (aEvent.source === this.views.present) {
       var cursor = argv[1].split(".");
       this.postMsg(this.views.present, "GET_NOTES");
       this.idx = ~~cursor[0];
       this.step = ~~cursor[1];
       $("#slideidx").innerHTML = argv[1];
       this.postMsg(this.views.future, "SET_CURSOR", this.idx + "." + (this.step + 1));
       if (this.views.remote)
         this.postMsg(this.views.remote, "SET_CURSOR", argv[1]);
     } else {
       $("#nextslideidx").innerHTML = +argv[1] < 0 ? "END" : argv[1];
     }
   }
   if (aEvent.source === this.views.present) {
     if (argv[0] === "NOTES" && argc === 2)
       $("#notes > #content").innerHTML = this.notes = argv[1];
     if (argv[0] === "REGISTERED" && argc === 3)
       $("#slidecount").innerHTML = argv[2];
   }
 }

 /* Get url from hash or prompt and store it */

 Dz.getUrl = function() {
   var u = window.location.hash.split("#")[1];
   if (!u) {
     u = window.prompt("What is the URL of the slides?");
     if (u) {
       window.location.hash = u.split("#")[0];
       return u;
     }
     u = "<style>body{background-color:white;color:black}</style>";
     u += "<strong>ERROR:</strong> No URL specified.<br>";
     u += "Try<em>: " + document.location + "#yourslides.html</em>";
     u = "data:text/html," + encodeURIComponent(u);
   }
   return u;
 }

 Dz.loadIframes = function() {
   var present = $("#present iframe");
   var future = $("#future iframe");
   this.url = this.getUrl();
   present.src = future.src = this.url
   present.onload = future.onload = function() {
     var id = this.parentNode.id;
     Dz.views[id] = this.contentWindow;
     if (Dz.views.present && Dz.views.future) {
       Dz.postMsg(Dz.views.present, "REGISTER");
       Dz.postMsg(Dz.views.future, "REGISTER");
     }
   }
 }

 Dz.toggleContent = function() {
   if (this.views.remote)
     this.postMsg(this.views.remote, "TOGGLE_CONTENT");
 }

 Dz.onhashchange = function() {
   this.loadIframe();
 }

 Dz.back = function() {
   this.postMsg(this.views.present, "BACK");
 }

 Dz.forward = function() {
   this.postMsg(this.views.present, "FORWARD");
 }

 Dz.goStart = function() {
   this.postMsg(this.views.present, "START");
 }

 Dz.goEnd = function() {
   this.postMsg(this.views.present, "END");
 }

 Dz.setCursor = function(aCursor) {
   this.postMsg(this.views.present, "SET_CURSOR", aCursor);
 }

 Dz.popup = function() {
   this.views.remote = window.open(this.url + "#" + this.idx, 'slides', 'width=800,height=600,personalbar=0,toolbar=0,scrollbars=1,resizable=1');
 }

 Dz.postMsg = function(aWin, aMsg) { // [arg0, [arg1...]]
   aMsg = [aMsg];
   for (var i = 2; i < arguments.length; i++)
     aMsg.push(encodeURIComponent(arguments[i]));
   aWin.postMessage(aMsg.join(" "), "*");
 }

 Dz.resetClock = function() { // + 3600000, because Date(0) is 01:00:00
   this.clock_start = (new Date()).getTime() + 3600000;
 }

 Dz.startClock = function() {
   var addZero = function(num) {
     return num < 10 ? '0' + num : num;
   }
   setInterval(function() {
     var now = new Date((new Date()).getTime() - this.Dz.clock_start);
     $("#hours").innerHTML   = addZero(now.getHours());
     $("#minutes").innerHTML = addZero(now.getMinutes());
     $("#seconds").innerHTML = addZero(now.getSeconds());
   }, 1000);
 }

 function init() {
   Dz.init();
   window.onkeydown = Dz.onkeydown.bind(Dz);
   window.onhashchange = Dz.loadIframes.bind(Dz);
   window.onmessage = Dz.onmessage.bind(Dz);
 }

 window.onload = init;
</script>


<script> // Helpers
 if (!Function.prototype.bind) {
   Function.prototype.bind = function (oThis) {

     // closest thing possible to the ECMAScript 5 internal IsCallable
     // function
     if (typeof this !== "function")
     throw new TypeError(
       "Function.prototype.bind - what is trying to be fBound is not callable"
     );

     var aArgs = Array.prototype.slice.call(arguments, 1),
         fToBind = this,
         fNOP = function () {},
         fBound = function () {
           return fToBind.apply( this instanceof fNOP ? this : oThis || window,
                  aArgs.concat(Array.prototype.slice.call(arguments)));
         };

     fNOP.prototype = this.prototype;
     fBound.prototype = new fNOP();

     return fBound;
   };
 }

 var $ = (HTMLElement.prototype.$ = function(aQuery) {
   return this.querySelector(aQuery);
 }).bind(document);

 var $$ = (HTMLElement.prototype.$$ = function(aQuery) {
   return this.querySelectorAll(aQuery);
 }).bind(document);

</script>