diff --git a/data/index.html b/data/index.html
index 53830e4ed5bf15a7d443606a8581f9c6ff696fd7..1f75d39586d75e332d3dc78b6ee71723366a10d9 100644
--- a/data/index.html
+++ b/data/index.html
@@ -9,25 +9,66 @@
   <link href="styles.css" rel="stylesheet">
 </head>
 
-<body class="w-100 h-100 d-flex align-items-center">
+<body class="w-100 h-100 d-flex align-items-center" data-bs-theme="dark">
 
   <div class="container justify-content-center">
 
-    <div class="col-lg-6 offset-lg-3">
+    <div class="col-lg-4 offset-lg-4">
+
+      <div class="card">
+        <h5 class="card-header">
+          Matrix of Life <div id="spinner" class="spinner-border spinner-border-sm" role="status">
+            <span class="visually-hidden">Loading...</span>
+          </div>
+        </h5>
+        <div class="card-body text-center">
+      
+          <div class="row mb-3 px-3">
+            <label for="rangeBrightness" class="col-form-label">Brightness</label>
+            <input type="range" class="form-range rangeConfig" id="rangeBrightness" data-name="brightness" min="1" max="100" steps="1">
+          </div>
+          <div class="row mb-3 px-3">
+            <label for="rangeSpeed" class="col-form-label">Speed</label>
+            <input type="range" class="form-range rangeConfig" id="rangeSpeed" data-name="interval" min="1" max="100" steps="10">
+          </div>
+
+        </div>
+
+        <div class="card-footer text-body-secondary py-3">
+          <div class="row">
+            <div class="col-6">
+              <button type="button" class="btn btn-sm btn-primary btnAction" data-action="addGlider">Add Glider</button>
+            </div>
+            <div class="col-6 text-end">
+              <button type="button" class="btn btn-sm btn-outline-secondary me-1" data-bs-toggle="collapse" data-bs-target="#debug">Debug</button>       
+              <button type="button" class="btn btn-sm btn-danger btnAction " data-action="reboot">Reboot</button>
+            </div>
+          </div>
+        </div>    
+
 
-      <div class="row mb-3">
-        <label for="rangeBrightness" class="col-form-label">Brightness</label>
-        <input type="range" class="form-range" id="rangeBrightness" data-name="brightness" min="8" max="255" steps="1">
-      </div>
-      <div class="row mb-3">
-        <label for="rangeSpeed" class="col-form-label">Speed</label>
-        <input type="range" class="form-range" id="rangeSpeed" data-name="interval" min="10" max="1000" steps="10">
       </div>
-      <button type="button" class="btn btn-primary" id="btnGlider">Add Glider</button>
 
-      <pre id="dbg"></pre>
-      <div id="input_div">
-        $<input type="text" value="" id="input_el">
+    </div>
+
+    <div class="col-lg-4 offset-lg-4 mt-3 collapse" id="debug">
+
+      <div class="card">
+        <h5 class="card-header">
+          Debug
+        </h5>
+        <div class="card-body">
+
+          <textarea id="wsMessages" class="form-control mb-2 font-monospace" rows="10"></textarea>
+          <div id="input_div">
+            <div class="input-group">
+              <span class="input-group-text">➡️</span>
+              <input type="text" id="prompt" class="form-control form-control-sm fs-6 font-monospace">
+            </div>
+            
+          </div>
+        
+        </div>
       </div>
 
     </div>
@@ -35,7 +76,8 @@
   </div>
 
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
-  <script src="https://code.jquery.com/jquery-3.7.0.slim.min.js" integrity="sha256-tG5mcZUtJsZvyKAxYLVXrmjKBVLd6VpVccqz/r4ypFE=" crossorigin="anonymous"></script>
+  <script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
+  <script src="jquery.simple.websocket.min.js"></script>
   <script src="main.js"></script>
 </body>
 
diff --git a/data/jquery.simple.websocket.min.js b/data/jquery.simple.websocket.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..7dd55392f0c1457ea69b012eeec08d6dbdc7eaee
--- /dev/null
+++ b/data/jquery.simple.websocket.min.js
@@ -0,0 +1 @@
+!function(a){"object"==typeof module&&"string"===module.exports?module.exports=a(jQuery):a(jQuery)}(function(a){var b=function(b){if(this._isEmpty(b,"url"))throw new Error('Missing argument, example usage: $.simpleWebSocket({ url: "ws://127.0.0.1:3000" }); ');this._opt=b,this._ws=null,this._reConnectTries=60,this._reConnectDeferred=null,this._closeDeferred=null,this._dataType=this._prop(this._opt,"dataType","json"),this._listeners=[],this._onOpen=this._prop(this._opt,"onOpen",null),this._onClose=this._prop(this._opt,"onClose",null),this._onError=this._prop(this._opt,"onError",null);var c=this;return this._api=function(){return{connect:function(){return a.extend(c._api,c._reConnect.apply(c,[]))},isConnected:function(a){return a?(a.apply(this,[c._isConnected.apply(c,[])]),c._api):c._isConnected.apply(c,[])},send:function(b){return a.extend(c._api,c._send.apply(c,[b]))},listen:function(b){return a.extend(c._api,c._listenReconnect.apply(c,[b]))},remove:function(a){return c._remove.apply(c,[a]),c._api},removeAll:function(){return c._removeAll.apply(c,[]),c._api},close:function(){return c._reset.apply(c,[]),a.extend(c._api,c._close.apply(c,[]))},getWsAdapter:function(){return this._ws}}}(),this._api};return b.prototype={_createWebSocket:function(a){var b=null;if(a.protocols)if(void 0===window.MozWebSocket){if(!window.WebSocket)throw new Error("Error, websocket could not be initialized.");b=new WebSocket(a.url,a.protocols)}else b=new MozWebSocket(a.url,a.protocols);else if(void 0===window.MozWebSocket){if(!window.WebSocket)throw new Error("Error, websocket could not be initialized.");b=new WebSocket(a.url)}else b=new MozWebSocket(a.url);return b},_bindSocketEvents:function(b,c){var d=this;a(b).bind("open",c.open).bind("close",c.close).bind("message",function(a){try{if("function"==typeof c.message)if(d._dataType&&"json"===d._dataType.toLowerCase()){var b=JSON.parse(a.originalEvent.data);c.message.call(this,b)}else if(d._dataType&&"xml"===d._dataType.toLowerCase()){var e=new DOMParser,f=e.parseFromString(a.originalEvent.data,"text/xml");c.message.call(this,f)}else c.message.call(this,a.originalEvent.data)}catch(a){"function"==typeof c.error&&c.error.call(this,a)}}).bind("error",function(a){"function"==typeof c.error&&c.error.call(this,a)})},_webSocket:function(a){var b=this._createWebSocket(a);return this._bindSocketEvents(b,a),b},_getSocketEventHandler:function(a){var b=this;return{open:function(c){b._onOpen&&b._onOpen.apply(b,[c]);var d=this;a&&a.resolve(d)},close:function(c){b._closeDeferred&&b._closeDeferred.resolve(),b._onClose&&b._onClose.apply(b,[c]),a&&a.rejectWith(c)},message:function(a){for(var c=0,d=b._listeners.length;c<d;c++)try{b._listeners[c].deferred.notify.apply(b,[a])}catch(a){}},error:function(c){b._ws=null,b._onError&&b._onError.apply(b,[c]);for(var d=0,e=b._listeners.length;d<e;d++)b._listeners[d].deferred.reject.apply(b,[c]);a&&a.rejectWith.apply(b,[c])}}},_connect:function(){var b=a.Deferred();if(this._ws)if(2===this._ws.readyState)this._ws.close();else if(3===this._ws.readyState)this._ws.close();else{if(0===this._ws.readyState)return b.promise();if(1===this._ws.readyState)return b.resolve(this._ws),b.promise()}return this._ws=this._webSocket(a.extend(this._opt,this._getSocketEventHandler(b))),b.promise()},_reset:function(){this._reConnectTries=this._prop(this._opt,"attempts",60),this._reConnectDeferred=a.Deferred()},_close:function(){return this._closeDeferred=a.Deferred(),this._ws&&(this._ws.close(),this._ws=null),this._closeDeferred.promise()},_isConnected:function(){return null!==this._ws&&1===this._ws.readyState},_reConnectTry:function(){var a=this;this._connect().done(function(){a._reConnectDeferred.resolve.apply(a,[a._ws])}).fail(function(b){a._reConnectTries--,a._reConnectTries>0?window.setTimeout(function(){a._reConnect.apply(a,[])},a._prop.apply(a,[a._opt,"timeout",1e4])):a._reConnectDeferred.rejectWith.apply(a,[b])})},_reConnect:function(){var a=this;return null===this._reConnectDeferred?this._reset():"resolved"===this._reConnectDeferred.state()?this._reset():"rejected"===this._reConnectDeferred.state()&&this._reset(),this._ws&&1===this._ws.readyState?this._reConnectDeferred.resolve(this._ws):this._reConnectTry(),a._reConnectDeferred.promise.apply(a,[])},_preparePayload:function(a){return this._opt.dataType&&"text"===this._opt.dataType.toLowerCase()?a:this._opt.dataType&&"xml"===this._opt.dataType.toLowerCase()?a:(this._opt.dataType&&this._opt.dataType.toLowerCase(),JSON.stringify(a))},_send:function(b){var c=this,d=a.Deferred();return function(a){c._reConnect.apply(c,[]).done(function(b){b.send(a),d.resolve.apply(c,[c._api])}).fail(function(a){d.rejectWith.apply(c,[a])})}(this._preparePayload(b)),d.promise()},_indexOfListener:function(a){for(var b=0,c=this._listeners.length;b<c;b++)if(this._listeners[b].listener===a)return b;return-1},_isEmpty:function(a,b){return"string"===a||(null===a||(void 0===b||(null===b||(""===b||(void 0===a[b]||null===a[b])))))},_prop:function(a,b,c){return this._isEmpty(a,b)?c:a[b]},_listen:function(b){var c=this,d=a.Deferred();return c._reConnect.apply(c,[]).done(function(){d.progress(function(){b.apply(this,arguments)}),c._remove.apply(c,[b]),c._listeners.push({deferred:d,listener:b})}).fail(function(a){d.reject(a)}),d.promise()},_listenReconnect:function(b){var c=a.Deferred(),d=this;return this._listen(b).fail(function(){c.notify(arguments),d._listenReconnect.apply(d,[b])}).done(function(){c.resolve()}),c.promise()},_remove:function(a){var b=this._indexOfListener(a);0<=b&&(this._listeners[b].deferred.resolve(),this._listeners.splice(b,1))},_removeAll:function(){for(var a=0,b=this._listeners.length;a<b;a++)this._listeners[a].deferred.resolve();this._listeners=[]}},a.extend({simpleWebSocket:function(a){return new b(a)}}),a.simpleWebSocket});
\ No newline at end of file
diff --git a/data/main.js b/data/main.js
index 8540a25cdb74ff7027db3ec758ffab2ad0c38e9e..6be0cf0578d2f50e990aa0dc0327ba3bee0300d4 100644
--- a/data/main.js
+++ b/data/main.js
@@ -1,70 +1,116 @@
-var ws = null;
-function ge(s) { return document.getElementById(s); }
-function ce(s) { return document.createElement(s); }
-function addMessage(m) {
-    var msg = ce("div");
-    msg.innerText = m;
-    ge("dbg").appendChild(msg);
-}
+var wsUrl = 'ws://' + document.location.host + '/ws'
 
+var ws = null;
 var config = {
     brightness: 0,
     interval: 0,
 };
 
-$(document).ready(function() {
+$(document).ready(function () {
+    uiEnabled(false);
+    $("#wsMessages, #prompt").val("");
     startSocket();
 });
 
-$(document).on('input change', '#rangeBrightness, #rangeSpeed', function() {
-    config[$(this).data('name')] = $(this).val();
+$(document).on('input change', '.rangeConfig', function () {
+    var setting = $(this).data('name');
+    var value = $(this).val();
+    switch (setting) {
+        case 'brightness':
+            value = mapRange(value, 1, 100, 8, 255);
+            break;
+        case 'interval':
+            value = mapRange(value, 1, 100, 1000, 10);
+            break;
+    }
+    config[setting] = value;
     var payload = {
-        config: config
+        'config': config
     };
-    ws.send(JSON.stringify(payload));
+    wsSend(payload);
 });
 
-$('#btnGlider').on('click', function() {
+$('.btnAction').on('click', function () {
+    var action = $(this).data('action');
     var payload = {
-        action: "addGlider"
+        'action': action
     };
-    ws.send(JSON.stringify(payload));
-})
+    wsSend(payload);
+});
+
+$("#prompt").onkeydown = function (e) {
+    if (e.keyCode == 13 && $("#prompt").value != "") {
+        var val = $("#prompt").val();
+        wsSend(val);
+        $("#prompt").val("");
+    }
+}
+
+function uiEnabled(enabled) {
+    if (enabled) {
+        $('#spinner').addClass('invisible');
+        $('.btnAction').prop('disabled', false);
+        $('.rangeConfig').prop('disabled', false);
+    } else {
+        $('#spinner').removeClass('invisible');
+        $('.btnAction').prop('disabled', true);
+        $('.rangeConfig').prop('disabled', true);
+    }
+}
+
+function addMessage(msg) {
+    var txt = $("#wsMessages");
+    txt.val($.trim(txt.val() + "\n" + msg));
+    txt.scrollTop(txt[0].scrollHeight - txt.height());
+}
+
+function wsSend(msg) {
+    uiEnabled(false);
+    var json = JSON.stringify(msg);
+    ws.send(msg).done(function () {
+        addMessage("➡️ " + json);
+        uiEnabled(true);
+    }).fail(function (e) {
+        addMessage("❗️ " + json);
+    });
+}
 
 function startSocket() {
-    ws = new WebSocket('ws://' + document.location.host + '/ws', ['arduino']);
-    ws.onopen = function (e) {
-        addMessage("Connected");
-    };
-    ws.onclose = function (e) {
-        addMessage("Disconnected");
-    };
-    ws.onerror = function (e) {
-        console.log("ws error", e);
-        addMessage("Error");
-    };
-    ws.onmessage = function (e) {
-        var msg = "> " + e.data;
+    ws = $.simpleWebSocket({
+        url: wsUrl,
+        onOpen: function (e) {
+            console.log("WS: connected");
+            addMessage("✅ connected");
+        },
+        onClose: function (e) {
+            uiEnabled(false);
+            console.log("WS: disconnected");
+            addMessage("❌ disconnected");
+        },
+        onError: function (e) {
+            uiEnabled(false);
+            console.log("WS: error");
+            addMessage("❗️ error");
+        },
+    });
+    ws.listen(function (data) {
+        var json = JSON.stringify(data);
+        var msg = "⬅️ " + json;
         addMessage(msg);
-        var data = jQuery.parseJSON(e.data);
-        console.log(data);
-        if(data.config) {
+        if (data.config) {
             loadConfig(data.config);
-        } else {
-            console.log("no config");
+            uiEnabled(true);
         }
-    };
-    ge("input_el").onkeydown = function (e) {
-        if (e.keyCode == 13 && ge("input_el").value != "") {
-            ws.send(ge("input_el").value);
-            ge("input_el").value = "";
-        }
-    }
+    });
 }
 
 function loadConfig(data) {
     config.brightness = data.brightness;
     config.interval = data.interval;
-    $('#rangeBrightness').val(data.brightness);
-    $('#rangeSpeed').val(data.interval);
+    $('#rangeBrightness').val(mapRange(data.brightness, 8, 255, 1, 100));
+    $('#rangeSpeed').val(mapRange(data.interval, 1000, 10, 1, 100));
+}
+
+function mapRange (number, inMin, inMax, outMin, outMax) {
+    return Math.round((number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin);
 }
diff --git a/data/styles.css b/data/styles.css
index 3bee0465785fd6c068be7522eaf6cf89483d3d82..d2502b5c1137a254b0c34acc1221d7bf11488db3 100644
--- a/data/styles.css
+++ b/data/styles.css
@@ -3,22 +3,6 @@ html {
   height: 100%;
 }
 
-#dbg, #input_div, #input_el {
-  font-family: monaco;
-}
-
-#dbg, #input_div {
-  margin:0;
-  padding:0;
-  padding-left:4px;
-}
-
-#input_el {
-  width:98%;
-  background-color: rgba(0,0,0,0);
-  border: 0px;
-}
-
-#input_el:focus {
-  outline: none;
+#wsMessages {
+  font-size: 0.7rem;
 }