<button id="red">Red</button>
<button id="blue">Blue</button>
<button id="yellow">Yellow</button>
<button id="reset">Reset</button>
<button id="backspace">Backspace</button>
<div id="msg">...</div>
<div id="plain">...</div>
<hr>
<div id="encoding">
</div>
body {
  margin: 2em;
}

.chunk {
  border: 1px solid black;
}

.symbol {
  display: inline-flex;
  margin: 1em;
  border: 1px solid black;
  min-width: 5em;
  justify-content: space-evenly;
}
let data = [];
let symbolLength = 3;
let allSymbols = function(symbolLength){
  let result = [];
  if (symbolLength > 4){
    alert("no way Holmes");
    return;
  }
  let i = 0;
  for (i = 0; i < 3**symbolLength; i++){
    result.push(i.toString(3).padStart(symbolLength,"0"));
  }
  return result;
};

let defaultEncoding = function(){
  let encoding = {};
  let universe = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  if (symbolLength > 4){
    alert("no way");
    return;
  }
  if (symbolLength > 3){
    universe = universe + universe.toLowerCase() + " ,0123456789!@#$%^&*()-_+={}.";
  } else if (symbolLength == 3){
    universe = universe + " ";
  }
  let symbols = allSymbols(symbolLength);
  let i = 0;
  for (i = 0; i < symbols.length; i++){
    encoding[symbols[i]] = universe.substr(i,1);
  }
  return encoding;
}

let encoding = defaultEncoding(); //we want "0121":"value" but letterLength

let readEncoding = function(integerArray, startIndex, encoding){
  let theVals = integerArray.slice(startIndex, startIndex+symbolLength);
  let theKey = theVals.join("");
  return encoding[theKey];
}

let fromIntToHeart = function(anInt){
     switch (anInt){
      case 0:
        return "&#x2764;";
      case 1:
        return "&#x1F499;";//"&#x1F49A;";
      case 2:
        return "&#x1F49B;";
    } 
}

let displayData = function(data, encoding){
  $("#msg").html("");
  $("#plain").html("");  
  let newHTML = "";
  let rendered = "";
  let i = 0;
  data.map(intVal=>{
    if (i == 0){
      newHTML += `<span class="chunk">`
    } else if (i % symbolLength == 0){
      newHTML += `</span><span class="chunk">`;
    }
    i += 1;
    if (i % symbolLength == 0){
      rendered += readEncoding(data, i-symbolLength, encoding);
    }
    newHTML += fromIntToHeart(intVal);
  });
  if (data.length == 0){
    newHTML = "...";
  } else {
    newHTML += "</span>";
  }
  $("#msg").html(newHTML);
  $("#plain").html(rendered);
};

$("#red").on("click", function(){
  data.push(0);
  displayData(data, encoding);
});

$("#blue").on("click", function(){
  data.push(1);
  displayData(data, encoding);
});

$("#yellow").on("click", function(){
  data.push(2);
  displayData(data, encoding);
});

$("#reset").on("click", function(){
  data = [];
  displayData(data, encoding);
});

$("#backspace").on("click", function(){
  data.pop();
  displayData(data, encoding);
});

let keyToHTML = function(aString){
  let result = "";
  let i = 0;
  for (i=0; i < aString.length; i++){
    let theVal = aString.substr(i,1);
    result += fromIntToHeart(parseInt(theVal));
  }
  return result;
};

let displayEncoding = function(encoding){
  //TODO pretty and interactive
  $("#encoding").html("");
  let keys = Object.keys(encoding);
  keys.sort();
  keys.map(aKey=>{
    $("#encoding").append(`<div class="symbol"><span class="ekey">${keyToHTML(aKey)}</span><span class="evalue">${encoding[aKey]}</span></div>`);
  });
}

displayEncoding(encoding);

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js