var Typer = function(element) {
     this.element = element;
     var delim = element.dataset.delim || ",";
     var words = element.dataset.words || "override these,sample typing";
     this.words = words.split(delim).filter((v) => v); // non empty words
     this.delay = element.dataset.delay || 200;
     this.loop = element.dataset.loop || "true";
     if (this.loop === "false" ) { this.loop = 1 }
     this.deleteDelay = element.dataset.deletedelay || element.dataset.deleteDelay || 800;
   
     this.progress = { word: 0, char: 0, building: true, looped: 0 };
     this.typing = true;
   
     var colors = element.dataset.colors || "black";
     this.colors = colors.split(",");
     this.element.style.color = this.colors[0];
     this.colorIndex = 0;
   
     this.doTyping();
   };
   
   Typer.prototype.start = function() {
     if (!this.typing) {
       this.typing = true;
       this.doTyping();
     }
   };
   Typer.prototype.stop = function() {
     this.typing = false;
   };
   Typer.prototype.doTyping = function() {
     var e = this.element;
     var p = this.progress;
     var w = p.word;
     var c = p.char;
     var currentDisplay = [...this.words[w]].slice(0, c).join("");
     var atWordEnd;
     if (this.cursor) {
       this.cursor.element.style.opacity = "1";
       this.cursor.on = true;
       clearInterval(this.cursor.interval);
       this.cursor.interval = setInterval(() => this.cursor.updateBlinkState(), 400);
     }
   
     e.innerHTML = currentDisplay;
   
     if (p.building) {
       atWordEnd = p.char === this.words[w].length;
       if (atWordEnd) {
         p.building = false;
       } else {
         p.char += 1;
       }
     } else {
       if (p.char === 0) {
         p.building = true;
         p.word = (p.word + 1) % this.words.length;
         this.colorIndex = (this.colorIndex + 1) % this.colors.length;
         this.element.style.color = this.colors[this.colorIndex];
       } else {
         p.char -= 1;
       }
     }
   
     if (p.word === this.words.length - 1) {
       p.looped += 1;
     }
   
     if (!p.building && this.loop <= p.looped){
       this.typing = false;
     }
   
     setTimeout(() => {
       if (this.typing) { this.doTyping() };
     }, atWordEnd ? this.deleteDelay : this.delay);
   };
   
   var Cursor = function(element) {
     this.element = element;
     this.cursorDisplay = element.dataset.cursordisplay || element.dataset.cursorDisplay || "_";
     element.innerHTML = this.cursorDisplay;
     this.on = true;
     element.style.transition = "all 0.1s";
     this.interval = setInterval(() => this.updateBlinkState(), 400);
   }
   Cursor.prototype.updateBlinkState = function() {
     if (this.on) {
       this.element.style.opacity = "0";
       this.on = false;
     } else {
       this.element.style.opacity = "1";
       this.on = true;
     }
   }
   
   function TyperSetup() {
     var typers = {};
     for (let e of document.getElementsByClassName("typer")) {
       typers[e.id] = new Typer(e);
     }
     for (let e of document.getElementsByClassName("typer-stop")) {
       let owner = typers[e.dataset.owner];
       e.onclick = () => owner.stop();
     }
     for (let e of document.getElementsByClassName("typer-start")) {
       let owner = typers[e.dataset.owner];
       e.onclick = () => owner.start();
     }
     for (let e of document.getElementsByClassName("cursor")) {
       let t = new Cursor(e);
       t.owner = typers[e.dataset.owner];
       t.owner.cursor = t;
     }
   }
   
   TyperSetup();
   