diff --git a/index.html b/index.html
index 6c854befe0faab8001243e9daca58a761ca3d5d3..76e31bacd97bbda49318dfcfed8c2381c90124de 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
     <meta charset="utf-8">
     <link rel="shortcut icon" type="image/png" href="./static/favicon.png"/>
     <meta name="theme-color" content="#e65c00">
-    <meta name="viewport" content="width=device-width, initial-scale=0.8">
+    <meta name="viewport" content="width=0.7, initial-scale=0.7">
     <title>Web Milliways</title>
   </head>
   <body>
diff --git a/package-lock.json b/package-lock.json
index 61352b0100202a672c7a6eeebb4ef4221edcbc90..3312d5b6a3c8476a2e9b050229857c15fc78a729 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -321,6 +321,15 @@
       "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=",
       "dev": true
     },
+    "axios": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz",
+      "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=",
+      "requires": {
+        "follow-redirects": "1.2.4",
+        "is-buffer": "1.1.5"
+      }
+    },
     "babel-code-frame": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
@@ -3526,6 +3535,14 @@
       "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=",
       "dev": true
     },
+    "follow-redirects": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.4.tgz",
+      "integrity": "sha512-Suw6KewLV2hReSyEOeql+UUkBVyiBm3ok1VPrVFRZnQInWpdoZbbiG5i8aJVSjTr0yQ4Ava0Sh6/joCg1Brdqw==",
+      "requires": {
+        "debug": "2.6.8"
+      }
+    },
     "for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -4401,8 +4418,7 @@
     "is-buffer": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
-      "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
-      "dev": true
+      "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
     },
     "is-builtin-module": {
       "version": "1.0.0",
diff --git a/package.json b/package.json
index ca9564e78ffc3d046c66a5b4b65edc4a6f0cc3f7..3bd06a36faa8e5512b30717156a511810f3559fd 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
     "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
   },
   "dependencies": {
+    "axios": "^0.16.2",
     "pouchdb-browser": "^6.3.2",
     "pouchdb-find": "^6.3.2",
     "pouchdb-live-find": "^0.4.0",
diff --git a/src/App.vue b/src/App.vue
index 964361b0c8569bd295245f12b6ba1c146d8c8ef4..bc4699ef43958aa67d72a644b97d4cbd974d3ec9 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -24,4 +24,30 @@ body {
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
 }
+
+.scroll {
+  overflow-y: hidden;
+}
+
+@media only screen and (max-width: 800px) {
+  .scroll {
+    overflow-y: overlay;
+  }
+}
+
+.scroll:hover {
+  overflow-y: overlay;
+}
+
+.scroll::-webkit-scrollbar-track {
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.scroll::-webkit-scrollbar {
+  width: 8px;
+}
+
+.scroll::-webkit-scrollbar-thumb {
+  background-color: #000000;
+}
 </style>
diff --git a/src/assets/logo-orange.png b/src/assets/logo-orange.png
new file mode 100644
index 0000000000000000000000000000000000000000..e7f4c58dfada980e6b3046c93e52a53d9d079af1
Binary files /dev/null and b/src/assets/logo-orange.png differ
diff --git a/src/components/chat.vue b/src/components/chat.vue
index 73fc0dc4246ca25fe7411e56fd5a6222fab10e9e..68c8e409b1e693dd05eb9b1df4469cc5565c6200 100644
--- a/src/components/chat.vue
+++ b/src/components/chat.vue
@@ -1,16 +1,25 @@
 <template>
-  <div id="chat">
-    <p>{{ msg }}</p>
+  <div id="chat" class="scroll">
+    <div id="chat-inner">
+      <div v-for="msg in talker">
+        <div class="msg">
+          {{ msg.photo }}
+          <p>
+            <b>{{ msg.user }}</b> {{ msg.timestamp }}
+            <br>
+            <span v-html="msg.msg"/>
+          </p>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
 export default {
   name: 'chat',
-  data () {
-    return {
-      msg: 'Test message'
-    }
+  pouch: {
+    talker: {}
   }
 }
 </script>
@@ -18,7 +27,30 @@ export default {
 <!-- Add "scoped" attribute to limit CSS to this component only -->
 <style scoped>
 #chat {
+  height: calc(100vh - 110px);
   margin-left: 10px;
   margin-right: 10px;
+  margin-top: 10px;
+  overflow-x: hidden;
+}
+
+#chat-inner {
+  margin-right: 5px;
+}
+
+@media only screen and (max-width: 800px) {
+  #chat {
+    height: calc(100vh - 180px);
+    margin-left: 5px;
+    margin-right: 0px;
+  }
+
+  #chat-inner {
+    margin-right: 10px;
+  }
+
+  .msg {
+    font-size: 8pt;
+  }
 }
 </style>
diff --git a/src/components/login.vue b/src/components/login.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c2b67be695909ee9ba1872d74e9a9de638895e34
--- /dev/null
+++ b/src/components/login.vue
@@ -0,0 +1,126 @@
+<template>
+  <div id="login">
+    <div id="login-wrap">
+      <img src="../assets/logo-orange.png" class="logo">
+      <form id="login-form">
+        <input type="text" id="login-user" placeholder="Username" autofocus/><br>
+        <input type="password" id="login-pass" placeholder="Password"/><br>
+        <input type="submit" id="login-submit" class="button" value="Login">
+      </form>
+    </div>
+  </div>
+</template>
+
+<script>
+import PouchDB from 'pouchdb-browser'
+import axios from 'axios'
+
+export default {
+  name: 'login',
+  data () {
+    return {
+    }
+  },
+  mounted: function () {
+    var db = new PouchDB('auth')
+
+    db.get('user')
+    .then(function (result) {
+      hideLogin()
+      return
+    }).catch(function () {
+      // Do nothing since we need to auth the user
+    })
+
+    document.getElementById('login-form').addEventListener('submit', function (e) {
+      var user = document.getElementById('login-user').value
+      var pass = document.getElementById('login-pass').value
+
+      axios.post('https://sucs.org/~unreturnable/webmw-test/', { username: user, password: pass, action: 'login' })
+      .then(function (response) {
+        if (typeof response.data === 'string') {
+          if (response.data.startsWith('success:')) {
+            var token = response.data.replace('success:', '')
+
+            db.get('user')
+            .then(function (success) {
+              db.remove(success)
+              .then(function (success) {
+                db.put({ _id: 'user', token: token })
+                .then(function (success) {
+                  location.reload()
+                }).catch(function () {
+                })
+              }).catch(function () {
+              })
+            }).catch(function () {
+              db.put({ _id: 'user', token: token })
+              .then(function (success) {
+                location.reload()
+              }).catch(function () {
+              })
+            })
+          }
+        } else {
+          console.log(response.data.error)
+        }
+      }).catch(function (error) {
+        console.log(error.message)
+      })
+
+      return false
+    })
+  }
+}
+
+function hideLogin () {
+  var login = document.getElementById('login')
+  login.parentElement.removeChild(login)
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+#login {
+  position: absolute;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  background-color: #f2f2f2;
+}
+
+#login-wrap {
+  width: 100%;
+  text-align: center;
+  margin-top: 100px;
+}
+
+#login-header {
+  font-size: 32pt;
+}
+
+#login-user {
+  padding: 10px;
+}
+
+#login-pass {
+  margin-top: 10px;
+  padding: 10px;
+}
+
+#login-submit {
+  margin-top: 10px;
+}
+
+.button {
+  background: #e65c00;
+  color: #f2f2f2;
+  padding: 10px 40px 10px 40px;
+  border: none;
+}
+
+.logo {
+  width: 200px;
+  margin-left: -15px;
+}
+</style>
diff --git a/src/components/online.vue b/src/components/online.vue
index b3f3241e5f57d69e99c7d72d410d766ce22780ba..e21a5f2867c54841c737fc47613b68bfa42d38b4 100644
--- a/src/components/online.vue
+++ b/src/components/online.vue
@@ -1,15 +1,25 @@
 <template>
   <div class="bar-wrapper" v-if="config">
-    <div id="online" v-bind:style="{ 'background': config.onlineBg, 'color': config.onlineFg }">
+    <div id="online" class="scroll" v-bind:style="{ 'background': config.onlineBg, 'color': config.onlineFg }">
       <div id="online-list">
-	<h2>Online</h2>
-	<h3>{{ user }}</h3>
+        <h2 id="online-header">Online</h2>
+        <div v-for="user in online">
+          <div class="user">
+            {{ user.name }}
+            <div v-html="user.photo"></div>
+          </div>
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script>
+import PouchDB from 'pouchdb-browser'
+import mwCommand from '../mixins/mw-command.js'
+import urlChecker from '../mixins/url-checker.js'
+import stringToColor from '../mixins/string-to-color.js'
+
 export default {
   name: 'rooms',
   pouch: {
@@ -19,11 +29,51 @@ export default {
         selector: { _id: 'user' },
         first: true
       }
-    }
+    },
+    online: {}
   },
-  data () {
-    return {
-      user: 'unreturnable'
+  mixins: [ mwCommand, urlChecker, stringToColor ],
+  created: function () {
+    var ctx = this
+    ctx.sendCmdCallback('who', ctx.syncWho)
+    setInterval(function () { ctx.sendCmdCallback('who', ctx.syncWho) }, 5000)
+  },
+  methods: {
+    syncWho: function (response) {
+      if (response.state !== 'WHOL') {
+        var ctx = this
+        var db = new PouchDB('online')
+
+        db.destroy()
+        .then(function (res) {
+          ctx.storeUsers(response.data)
+        }).catch(function (err) {
+          console.log(err)
+        })
+      }
+    },
+    storeUsers: function (users) {
+      var db = new PouchDB('online')
+
+      for (var i = 0; i < users.length; i++) {
+        users[i]._id = users[i].name.toLowerCase()
+        // users[i].photo = this.getHackergotchi(users[i].name)
+        db.put(users[i])
+        .then(function (result) {
+        }).catch(function () {
+          // This was likely caused by the same user logged in with two clients
+        })
+      }
+    },
+    getHackergotchi: function (username) {
+      var hackergotchi = 'https://sucs.org/pictures/people/' + username.toLowerCase() + '.png'
+      var profileImage = ''
+      if (this.urlExists(hackergotchi)) {
+        profileImage = '<img src="' + hackergotchi + '" width="36"/>'
+      } else {
+        profileImage = '<div style="width: 36px; height: 36px; font-size: 7pt; text-align:center;padding-top: 5px; background-color: ' + this.stringToColour(username) + '">Don\'t <br>Panic</div>'
+      }
+      return profileImage
     }
   }
 }
@@ -69,10 +119,13 @@ function handleGesure () {
   height: 100vh;
 }
 
+#online-header {
+  margin-right: 25px;
+}
+
 #online-list {
   text-align: right;
-  margin-top: 25px;
-  margin-right: 25px;
+  margin-top: 50px;
 }
 
 @media only screen and (max-width: 800px) {
@@ -81,4 +134,16 @@ function handleGesure () {
     right: -250px;
   }
 }
+
+.user {
+  font-size: 12pt;
+  padding-top: 5px;
+  padding-bottom: 5px;
+  padding-right: 25px;
+}
+
+.user:hover {
+    background: rgba(0, 0, 0, 0.5);
+    cursor: pointer;
+}
 </style>
diff --git a/src/components/rooms.vue b/src/components/rooms.vue
index b97357a84e06f4714f40d761ee24298aaab4ddd6..0ee5f58cd1a53605d51f26c4a977ee183d938638 100644
--- a/src/components/rooms.vue
+++ b/src/components/rooms.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="bar-wrapper" v-if="config">
-    <div id="rooms" v-bind:style="{ 'background': config.roomsBg, 'color': config.roomsFg }">
+    <div id="rooms" class="scroll" v-bind:style="{ 'background': config.roomsBg, 'color': config.roomsFg }">
       <img src="../assets/logo.png" class="logo">
       <div id="rooms-list">
 	<h2>Rooms</h2>
diff --git a/src/components/talk.vue b/src/components/talk.vue
index b5156fc7faecd4c28bc31d75f118c60b0945299c..265d0f28b05590ed8ea04384996fa66a45b88a51 100644
--- a/src/components/talk.vue
+++ b/src/components/talk.vue
@@ -1,13 +1,15 @@
 <template>
   <div id="talk" v-if="config">
     <textarea id="talk-bar" placeholder=""></textarea>
-    <button id="send" v-bind:style="{ 'background': config.buttonBg, 'color': config.buttonFg }">
+    <button id="send" v-on:click="onSubmit" v-bind:style="{ 'background': config.buttonBg, 'color': config.buttonFg }">
       Send
     </button>
   </div>
 </template>
 
 <script>
+import mwCommand from '../mixins/mw-command.js'
+
 export default {
   name: 'chat',
   pouch: {
@@ -19,10 +21,12 @@ export default {
       }
     }
   },
-  data () {
-    return {
-      bgColor: '#e65c00',
-      fgColor: '#f2f2f2'
+  mixins: [ mwCommand ],
+  methods: {
+    onSubmit: function () {
+      var text = document.getElementById('talk-bar').value
+      document.getElementById('talk-bar').value
+      this.sendSay(text)
     }
   }
 }
diff --git a/src/components/topbar.vue b/src/components/topbar.vue
index 187858f927beab54cbb6ccc038e2c5d7373f20aa..adb19154cbe85a4b456ce96d15a9e67b637838c3 100644
--- a/src/components/topbar.vue
+++ b/src/components/topbar.vue
@@ -6,13 +6,15 @@
       Settings
     </button>
     <button id="logout" class="button" v-bind:style="{ 'background': config.buttonBg, 'color':
-    config.buttonFg }">
+    config.buttonFg }" v-on:click="logout">
       Logout
     </button>
   </div>
 </template>
 
 <script>
+import PouchDB from 'pouchdb-browser'
+
 export default {
   name: 'topbar',
   pouch: {
@@ -22,13 +24,32 @@ export default {
         selector: { _id: 'user' },
         first: true
       }
-    }
+    },
+    auth: {}
   },
   data () {
     return {
       bgColor: '#e65c00',
       fgColor: '#f2f2f2'
     }
+  },
+  methods: {
+    logout: function (event) {
+      var db = new PouchDB('auth')
+
+      db.get('user')
+      .then(function (result) {
+        console.log(result)
+        db.remove(result)
+        .then(function (result) {
+          location.reload()
+        }).catch(function (err) {
+          console.log(err)
+        })
+      }).catch(function () {
+        // Do nothing since we need to auth the user
+      })
+    }
   }
 }
 </script>
diff --git a/src/mixins/mw-command.js b/src/mixins/mw-command.js
new file mode 100644
index 0000000000000000000000000000000000000000..af04a98f795788223d9bf238032b98c1d5e3b9ca
--- /dev/null
+++ b/src/mixins/mw-command.js
@@ -0,0 +1,56 @@
+import axios from 'axios'
+import PouchDB from 'pouchdb-browser'
+
+export default {
+  created: function () {
+  },
+  methods: {
+    sendCmdCallback: function (cmd, callback, err) {
+      var db = new PouchDB('auth')
+
+      db.get('user')
+      .then(function (result) {
+        axios.post('https://sucs.org/~unreturnable/webmw-test/send.php', { send: cmd, mwsess: result.token })
+        .then(function (response) {
+          callback(response)
+        }).catch(function (error) {
+          console.log(error)
+        })
+      }).catch(function (err) {
+        console.log(err)
+      })
+    },
+    sendCmd: function (cmd) {
+      var db = new PouchDB('auth')
+
+      db.get('user')
+      .then(function (result) {
+        axios.post('https://sucs.org/~unreturnable/webmw-test/send.php', { send: cmd, mwsess: result.token })
+        .then(function (response) {
+          // Do nothing because we didn't want
+        }).catch(function (error) {
+          console.log(error)
+        })
+      }).catch(function (err) {
+        console.log(err)
+      })
+    },
+    sendSay: function (text) {
+      var db = new PouchDB('auth')
+      var what = 'say ' + text
+
+      db.get('user')
+      .then(function (result) {
+        axios.post('https://sucs.org/~unreturnable/webmw-test/send.php', { send: what, mwsess: result.token })
+        .then(function (response) {
+        }).catch(function (error) {
+          console.log(error)
+        })
+      }).catch(function (err) {
+        console.log(err)
+      })
+
+      return false
+    }
+  }
+}
diff --git a/src/mixins/mw-sync.js b/src/mixins/mw-sync.js
new file mode 100644
index 0000000000000000000000000000000000000000..6bb6c76abfc0601eac807663e5e04d746f91107e
--- /dev/null
+++ b/src/mixins/mw-sync.js
@@ -0,0 +1,216 @@
+import PouchDB from 'pouchdb-browser'
+import axios from 'axios'
+import urlChecker from '../mixins/url-checker.js'
+
+var profileImages = {}
+
+export default {
+  created: function () {
+    var mixin = this
+    var db = new PouchDB('auth')
+
+    db.get('user')
+    .then(function (result) {
+      setInterval(function () { mixin.sync(result.token) }, 1000)
+    }).catch(function (err) {
+      console.log(err)
+    })
+  },
+  mixins: [ urlChecker ],
+  methods: {
+    sync: function (token) {
+      var mixin = this
+
+      axios.get('https://sucs.org/~unreturnable/webmw-test/poll.php', { params: { mwsess: token } })
+      .then(mixin.onMsg).catch(function (error) {
+        console.log(error)
+      })
+    },
+    filterString: function (str) {
+      var escapedStr = str.replace(/&/g, '&amp;')
+      escapedStr = escapedStr.replace(/</g, '&lt;')
+      escapedStr = escapedStr.replace(/>/g, '&gt;')
+      escapedStr = escapedStr.replace(/\n/g, '')
+      escapedStr = escapedStr.replace(/\r/g, '')
+
+      return escapedStr
+    },
+    filterColours: function (str) {
+      /* Replace colour codes with appropriate span tags */
+      var msgColours = str.match(/\u001b../g)
+      if (msgColours !== null) {
+        for (var i = 0; i < msgColours.length; i++) {
+          if (isNaN(msgColours[i].charAt(1))) {
+            var colourBold = ''
+            var fgColour = msgColours[i].charAt(1)
+            if (fgColour !== fgColour.toLowerCase()) {
+              colourBold = ' bold'
+            }
+            str = str.replace(msgColours[i], '</span><span class=\'colourfg' + msgColours[i].charAt(1) + colourBold + ' colourbg' + msgColours[i].charAt(2) + '\'>')
+          } else {
+            str = str.replace(msgColours[i], '</span><span class=\'colour' + msgColours[i].replace(/\u001b/g, '') + '\'>')
+          }
+        }
+      }
+      return str
+    },
+    pad: function (number, length) {
+      var str = '' + number
+      while (str.length < length) {
+        str = '0' + str
+      }
+      return str
+    },
+    getHackergotchi: function (username) {
+      if (typeof profileImages[username] === 'undefined') {
+        var hackergotchi = 'https://sucs.org/pictures/people/' + username.toLowerCase() + '.png'
+        if (this.urlExists(hackergotchi)) {
+          profileImages[username] = '<img src="' + hackergotchi + '" width="36" />'
+        } else {
+          profileImages[username] = '<div style="width: 36px; height: 36px; font-size: 7pt; text-align: center; padding-top: 5px; background-color: ' + this.stringToColour(username) + '">Don\'t <br>Panic</div>'
+        }
+      }
+
+      return profileImages[username]
+    },
+    onMsg: function (msg) {
+      console.log(msg)
+
+      var db = new PouchDB('talker')
+
+      if (msg.status !== 200) {
+        if (msg.status === 'Socket open error') {
+          console.log('Error connecting to socket')
+        } else {
+          console.log('Problem: ' + msg.status)
+        }
+      } else {
+        msg = msg.data
+
+        for (var one in msg) {
+          switch (msg[one].state) {
+            case 'SAYR':
+            case 'SAYU':
+              // decode the json message
+              var detail = JSON.parse(msg[one].text)
+
+              var body = detail.text
+              // make a crude approximation of the old message styles
+              switch (detail.type) {
+                case 'say':
+                  body = detail.text
+                  break
+                case 'raw':
+                  body = detail.text
+                  break
+                case 'emote':
+                  switch (detail.plural) {
+                    case 1:
+                      body = msg[one].name + '\'s ' + detail.text
+                      break
+                    case 2:
+                      body = msg[one].name + '\' ' + detail.text
+                      break
+                    case 3:
+                      body = msg[one].name + '\'d ' + detail.text
+                      break
+                    case 4:
+                      body = msg[one].name + '\'ll ' + detail.text
+                      break
+                    default:
+                      body = msg[one].name + ' ' + detail.text
+                      break
+                  }
+                  break
+                case 'notsayto':
+                  body = msg[one].name + ' says (-' + detail.exclude + '): ' + detail.text
+                  break
+                case 'says':
+                  body = msg[one].name + ' says: ' + detail.text
+                  break
+                case 'whispers':
+                  body = msg[one].name + ' whispers: ' + detail.text
+                  break
+                default:
+                  body = msg[one].name + ' ' + detail.text
+                  break
+              }
+
+              /* Escape HTML characters */
+              var escapedMsg = this.filterString(body)
+              escapedMsg = this.filterColours(escapedMsg)
+
+              var msgTime = new Date(msg[one].when * 1000)
+              var timestamp = this.pad(msgTime.getHours(), 2) + ':' + this.pad(msgTime.getMinutes(), 2)
+
+              var toStore = {
+                _id: msg[one].serial.toString(),
+                user: msg[one].name,
+                photo: this.getHackergotchi(msg[one].name),
+                timestamp: timestamp,
+                msg: escapedMsg
+              }
+
+              db.put(toStore)
+              .then(function (result) {
+              }).catch(function () {
+                // Messages currently can't get updated in MW
+              })
+              break
+
+            case 11: // Talker message
+            case 17: // Board Message
+              /* Escape HTML characters */
+              escapedMsg = this.filterString(msg[one].text)
+              escapedMsg = this.filterColours(escapedMsg)
+
+              msgTime = new Date(msg[one].when * 1000)
+              timestamp = this.pad(msgTime.getHours(), 2) + ':' + this.pad(msgTime.getMinutes(), 2)
+
+              toStore = {
+                id_: timestamp,
+                timestamp: timestamp,
+                msg: escapedMsg
+              }
+
+              db.put(toStore)
+              .then(function (result) {
+                console.log(result)
+              }).catch(function () {
+                // Messages currently can't get updated in MW
+              })
+              break
+
+            case 14: // IPC_KICK
+              var what = '<div class=\'msgkick user_system\'>'
+              if (msg[one].text.substr(0, 1) === 'm') {
+                what += 'Boing, Zebedee\'s arrived. "Look up!" says Zebedee<br />'
+                what += 'You look up; a large object is falling towards you very fast, very very fast. It looks like a Magic Roundabout!<br />'
+                what += '"I wouldn\'t stand there if I was you" says Zebedee<br />'
+                what += 'WWWHHHEEEEEEEKKKKEEEERRRRRUUUUUNNNNNCCCCCHHHHHH<br />'
+                what += msg[one].name + ' has just dropped the Magic Roundabout of Death on you.<br/>'
+              } else {
+                what += 'Boing, Zebedee arrived.<br />'
+                what += '"Time for bed" says Zebedee<br />'
+                what += msg[one].name + ' has just dropped the Zebedee of Death on you.<br />'
+              }
+              if (msg[one].text.substr(1, 1) === 'r') what += '"' + msg[one].text.substr(2) + '" says Zebedee<br />'
+              what += '</div>'
+
+              console.log(what)
+              break
+
+            case 23: // CheckOnOff
+              // Implement who check
+              break
+
+            default:
+              console.log('Unknown message type \'' + msg[one].state + '\'')
+              console.log(msg[one].text)
+              break
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/mixins/string-to-color.js b/src/mixins/string-to-color.js
new file mode 100644
index 0000000000000000000000000000000000000000..68764cf09f1c5c481e9f3fdd83f4d907107de8a0
--- /dev/null
+++ b/src/mixins/string-to-color.js
@@ -0,0 +1,19 @@
+export default {
+  methods: {
+    stringToColour: function (str) {
+      var hash = 0
+
+      for (var i = 0; i < str.length; i++) {
+        hash = str.charCodeAt(i) + ((hash << 5) - hash)
+      }
+
+      var colour = '#'
+      for (i = 0; i < 3; i++) {
+        var value = (hash >> (i * 8)) & 0xFF
+        colour += ('00' + value.toString(16)).substr(-2)
+      }
+
+      return colour
+    }
+  }
+}
diff --git a/src/mixins/url-checker.js b/src/mixins/url-checker.js
new file mode 100644
index 0000000000000000000000000000000000000000..d13520c87a8a9ff1f2bb9d1793eabe86c741af7f
--- /dev/null
+++ b/src/mixins/url-checker.js
@@ -0,0 +1,10 @@
+export default {
+  methods: {
+    urlExists: function (url) {
+      var http = new XMLHttpRequest()
+      http.open('HEAD', url, false)
+      http.send()
+      return http.status !== 404
+    }
+  }
+}
diff --git a/src/pages/chat.vue b/src/pages/chat.vue
index 6bc87aa6be767eeb56dc57c8ab3f63b43199e392..f0f9ace530ee0556c3dfdd1e167e19f7f9d59afe 100644
--- a/src/pages/chat.vue
+++ b/src/pages/chat.vue
@@ -1,12 +1,15 @@
 <template>
-  <div id="container">
-    <rooms></rooms>
-    <div id="center-container">
-      <topbar></topbar>
-      <chat></chat>
-      <talk></talk>
+  <div>
+    <div id="container">
+      <rooms></rooms>
+      <div id="center-container">
+	<topbar></topbar>
+	<chat></chat>
+	<talk></talk>
+      </div>
+      <online></online>
     </div>
-    <online></online>
+    <login></login>
   </div>
 </template>
 
@@ -16,6 +19,10 @@ import topbar from '../components/topbar.vue'
 import chat from '../components/chat.vue'
 import talk from '../components/talk.vue'
 import online from '../components/online.vue'
+import login from '../components/login.vue'
+
+import mwSync from '../mixins/mw-sync.js'
+import mwCommand from '../mixins/mw-command.js'
 
 export default {
   components: {
@@ -23,7 +30,12 @@ export default {
     'topbar': topbar,
     'chat': chat,
     'talk': talk,
-    'online': online
+    'online': online,
+    'login': login
+  },
+  mixins: [ mwSync, mwCommand ],
+  created: function () {
+    this.sendCmd('replay count 999')
   }
 }
 </script>
diff --git a/src/pages/login.vue b/src/pages/login.vue
deleted file mode 100644
index abf65f7963bbdf2e6040a0f3c6feca1ae793d8bf..0000000000000000000000000000000000000000
--- a/src/pages/login.vue
+++ /dev/null
@@ -1,20 +0,0 @@
-<template>
-  <div class="login">
-    <h1>{{ msg }}</h1>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'login',
-  data () {
-    return {
-      msg: 'Login'
-    }
-  }
-}
-</script>
-
-<!-- Add "scoped" attribute to limit CSS to this component only -->
-<style scoped>
-</style>
diff --git a/src/router/index.js b/src/router/index.js
index 9a9b1985e3c32f445197b1f1d2fd09fe05246091..f92cfae89e22583bde780b54094328df3f8e0d8d 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,7 +1,6 @@
 import Vue from 'vue'
 import Router from 'vue-router'
 import chat from '@/pages/chat'
-import login from '@/pages/login'
 
 Vue.use(Router)
 
@@ -11,11 +10,6 @@ export default new Router({
       path: '/',
       name: 'Chat',
       component: chat
-    },
-    {
-      path: '/login',
-      name: 'Login',
-      component: login
     }
   ]
 })