diff --git a/data/favicon.ico b/data/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..71b25fe6ee6012a4c26602977262d217af885520
Binary files /dev/null and b/data/favicon.ico differ
diff --git a/data/index.htm b/data/index.htm
new file mode 100644
index 0000000000000000000000000000000000000000..25b3efd946bd04db7113946b397832ff3eb74de8
--- /dev/null
+++ b/data/index.htm
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    <title>Events Log</title>
+    <style type="text/css" media="screen">
+    body {
+      margin:0;
+      padding:0;
+      background-color: black;
+    }
+
+    #dbg {
+      font-family: monaco;
+      font-size: 12px;
+      line-height: 13px;
+      color: #AAA;
+      margin:0;
+      padding:0;
+      padding-left:4px;
+    }
+    </style>
+    <script type="text/javascript">
+    function ge(s){ return document.getElementById(s);}
+    function ce(s){ return document.createElement(s);}
+    function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); }
+    function addMessage(m){
+      var msg = ce("div");
+      msg.innerText = m;
+      ge("dbg").appendChild(msg);
+      stb();
+    }
+    function startEvents(){
+      var es = new EventSource('/events');
+      es.onopen = function(e) {
+        addMessage("Events Opened");
+      };
+      es.onerror = function(e) {
+        if (e.target.readyState != EventSource.OPEN) {
+          addMessage("Events Closed");
+        }
+      };
+      es.onmessage = function(e) {
+        addMessage("Event: " + e.data);
+      };
+      es.addEventListener('ota', function(e) {
+        addMessage("Event[ota]: " + e.data);
+      }, false);
+    }
+    function onBodyLoad(){
+      startEvents();
+    }
+    </script>
+  </head>
+  <body id="body" onload="onBodyLoad()">
+    <pre id="dbg"></pre>
+  </body>
+</html>
diff --git a/platformio.ini b/platformio.ini
index 97270c27dc10b86b85d7618ee5826658a3649fe1..7d72a23c298ddf5fb310c5bbebad12e399908089 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -15,11 +15,13 @@ framework = arduino
 upload_port = COM8
 monitor_port = COM8
 monitor_speed = 115200
+board_build.filesystem = littlefs
 lib_deps = 
     https://github.com/ayushsharma82/ESPConnect.git
 	https://github.com/me-no-dev/AsyncTCP.git
 	https://github.com/me-no-dev/ESPAsyncWebServer.git
     https://github.com/PlummersSoftwareLLC/SmartMatrix.git
+    https://github.com/sstaub/TickTwo.git
 
 [env:esp32dev_ota]
 extends = env:esp32dev
diff --git a/src/config.h.sample b/src/config.h.sample
index 6bc7a599e147d7d2fb873ce75d211536c8c0c38f..8fb35b7c018fd479cb26159b0851ffb56757ce43 100644
--- a/src/config.h.sample
+++ b/src/config.h.sample
@@ -1,3 +1 @@
-#define WIFI_SSID "YourSSID"
-#define WIFI_PSK  "YourPSK"
 #define HOSTNAME  "MatrixOfLife"
diff --git a/src/display.cpp b/src/display.cpp
index 66b6da23c8899bc41bfb4688bf96aa198142b516..856ff8905e93428fff614cdfc24ffc70eeeca73a 100644
--- a/src/display.cpp
+++ b/src/display.cpp
@@ -42,7 +42,7 @@ void displayLoop()
         backgroundLayer.drawPixel(j, i, g[i][j] ? colorWhite : colorBlack);
       }
     }
-    backgroundLayer.swapBuffers();
+    backgroundLayer.swapBuffers(false);
   }
 }
 
@@ -53,7 +53,7 @@ void showEndScreen(int ticks) {
 void fadeOutGame(int brightness) {
   brightness = lightPowerMap8bit[brightness];
   backgroundLayer.setBrightness(brightness);
-  backgroundLayer.swapBuffers();
+  //backgroundLayer.swapBuffers(false);
 }
 
 void showEvolutions(int ticks)
@@ -70,8 +70,8 @@ void showEvolutions(int ticks)
 
 void clearDisplay()
 {
-  indexedLayer.swapBuffers();
+  indexedLayer.swapBuffers(false);
   backgroundLayer.fillScreen({0, 0, 0});
   backgroundLayer.setBrightness(255);
-  backgroundLayer.swapBuffers();
+  backgroundLayer.swapBuffers(false);
 }
diff --git a/src/main.cpp b/src/main.cpp
index 7c915f0ac380d885e7ce4e6a28e03cac8522825b..1c9f642b20d501cc49a60dd6343148bfad878c9f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,24 +1,31 @@
 #include <Arduino.h>
+#include "TickTwo.h"
 #include "utils.h"
 #include "network.h"
 #include "display.h"
 #include "gameoflife.h"
 
 int noEvolutionTicksLimit = 100;
-int brightnessPercent = 100;
+int brightnessPercent = 50;
+int gameInterval = 50;
+
+TickTwo gameTimer(gameLoop, gameInterval);
+TickTwo displayTimer(displayLoop, gameInterval);
 
 void setup()
 {
   Serial.begin(115200);
   logLine("", true);
-  setupDisplay();
   setupNetwork();
+  setupDisplay();
   setupGameOfLife();
+  gameTimer.start();
+  displayTimer.start();
 }
 
 void loop()
 {
   networkLoop();
-  gameLoop();
-  displayLoop();
+  gameTimer.update();
+  displayTimer.update();
 }
diff --git a/src/network.cpp b/src/network.cpp
index 50440d9e0e5ab9303a0375981ad0c7d7ab17dc29..5053e9599175b140e446bc0879481c17adaca88e 100644
--- a/src/network.cpp
+++ b/src/network.cpp
@@ -3,14 +3,16 @@
 #include "utils.h"
 #include "network.h"
 
-AsyncWebServer server(80);
 MDNSResponder mdns;
+AsyncWebServer server(80);
+AsyncEventSource events("/events");
 
 void setupNetwork()
 {
   setupWifi();
   setupMDNS();
   setupOTA();
+  setupWebserver();
 }
 
 void setupWifi()
@@ -33,7 +35,6 @@ void setupMDNS()
   {
     logLine("MDNS responder started");
     mdns.addService("http", "tcp", 80);
-    mdns.addService("ws", "tcp", 81);
   }
   else
   {
@@ -44,30 +45,37 @@ void setupMDNS()
 void setupOTA()
 {
   ArduinoOTA.onStart([]()
-                     { 
-    runGame = false;
-    logLine("OTA Update Start"); });
+                     { events.send("Update Start", "ota"); });
   ArduinoOTA.onEnd([]()
-                   { 
-    logLine("OTA Update End"); 
-    runGame = true; });
+                   { events.send("Update End", "ota"); });
   ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
                         {
     char p[32];
     sprintf(p, "Progress: %u%%\n", (progress/(total/100)));
-    logLine(p, false); });
+    events.send(p, "ota"); });
   ArduinoOTA.onError([](ota_error_t error)
                      {
-    if(error == OTA_AUTH_ERROR) logLine("OTA Auth Failed");
-    else if(error == OTA_BEGIN_ERROR) logLine("OTA Begin Failed");
-    else if(error == OTA_CONNECT_ERROR) logLine("OTA Connect Failed");
-    else if(error == OTA_RECEIVE_ERROR) logLine("OTA Receive Failed");
-    else if(error == OTA_END_ERROR) logLine("OTA End Failed");
-    runGame = true; });
+    if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota");
+    else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota");
+    else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota");
+    else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota");
+    else if(error == OTA_END_ERROR) events.send("End Failed", "ota"); });
   ArduinoOTA.setHostname(HOSTNAME);
   ArduinoOTA.begin();
 }
 
+void setupWebserver()
+{
+  LittleFS.begin();
+  server.serveStatic("/", LittleFS, "/");
+  events.onConnect([](AsyncEventSourceClient *client)
+                   { client->send("hello!", NULL, millis(), 1000); });
+  server.addHandler(&events);
+  server.onNotFound([](AsyncWebServerRequest *request)
+                    { request->send(404); });
+  server.begin();
+}
+
 void networkLoop()
 {
   ArduinoOTA.handle();
diff --git a/src/network.h b/src/network.h
index 2475984e4e2908daf7c5c446e6f0d9e2d2d3cebf..23ff9b0e0b089ec63732ac05411b0e3d41c3b67e 100644
--- a/src/network.h
+++ b/src/network.h
@@ -2,6 +2,8 @@
 #define NETWORK_H
 
 #include <Arduino.h>
+#include <FS.h>
+#include <LittleFS.h>
 #include <ArduinoOTA.h>
 #include <ESPConnect.h>
 #include <Wifi.h>
@@ -14,8 +16,8 @@ extern bool runGame;
 void setupNetwork();
 void setupMDNS();
 void setupWifi();
-void setupWebserver();
 void setupOTA();
+void setupWebserver();
 void networkLoop();
 
 #endif