diff --git a/app.js b/app.js
index e40aa19..5ea5275 100644
--- a/app.js
+++ b/app.js
@@ -42,7 +42,20 @@ var passportConf = require('./config/passport');
  * Create Express server.
  */
 
+var socket = require('socket.io');
+var express = require('express');
+var http = require('http');
+
 var app = express();
+var server = http.createServer(app);
+
+server.listen(2999, function()  {
+    console.log('server started on %d', 2999);
+});
+
+var io = socket.listen(server);
+
+//console.log('Express server started on port %s', server.address().port);
 
 /**
  * Connect to MongoDB.
@@ -205,4 +218,71 @@ app.listen(app.get('port'), function() {
   console.log('Express server listening on port %d in %s mode', app.get('port'), app.get('env'));
 });
 
-module.exports = app;
\ No newline at end of file
+module.exports = app;
+
+/**
+ * Chat
+ */
+
+var usernames = {};
+var numUsers = 0;
+
+io.on('connection', function (socket) {
+    var addedUser = false;
+
+    // when the client emits 'new message', this listens and executes
+    socket.on('new message', function (data) {
+        // we tell the client to execute 'new message'
+        socket.broadcast.emit('new message', {
+            username: socket.username,
+            message: data
+        });
+    });
+
+    // when the client emits 'add user', this listens and executes
+    socket.on('add user', function (username) {
+        // we store the username in the socket session for this client
+        socket.username = username;
+        // add the client's username to the global list
+        usernames[username] = username;
+        ++numUsers;
+        addedUser = true;
+        socket.emit('login', {
+            numUsers: numUsers
+        });
+        // echo globally (all clients) that a person has connected
+        socket.broadcast.emit('user joined', {
+            username: socket.username,
+            numUsers: numUsers
+        });
+    });
+
+    // when the client emits 'typing', we broadcast it to others
+    socket.on('typing', function () {
+        socket.broadcast.emit('typing', {
+            username: socket.username
+        });
+    });
+
+    // when the client emits 'stop typing', we broadcast it to others
+    socket.on('stop typing', function () {
+        socket.broadcast.emit('stop typing', {
+            username: socket.username
+        });
+    });
+
+    // when the user disconnects.. perform this
+    socket.on('disconnect', function () {
+        // remove the username from global usernames list
+        if (addedUser) {
+            delete usernames[socket.username];
+            --numUsers;
+
+            // echo globally that this client has left
+            socket.broadcast.emit('user left', {
+                username: socket.username,
+                numUsers: numUsers
+            });
+        }
+    });
+});
\ No newline at end of file
diff --git a/config/passport.js b/config/passport.js
index 685d915..69d1b99 100644
--- a/config/passport.js
+++ b/config/passport.js
@@ -64,6 +64,7 @@ passport.use(new TwitterStrategy(secrets.twitter, function(req, accessToken, tok
           user.profile.name = user.profile.name || profile.displayName;
           user.profile.location = user.profile.location || profile._json.location;
           user.profile.picture = user.profile.picture || profile._json.profile_image_url_https;
+          user.profile.username = profile.displayName;
           user.save(function(err) {
             req.flash('info', { msg: 'Twitter account has been linked.' });
             done(err, user);
diff --git a/controllers/curriculum.js b/controllers/curriculum.js
index 3c44bfb..4955e5f 100644
--- a/controllers/curriculum.js
+++ b/controllers/curriculum.js
@@ -6,7 +6,5 @@
 exports.index = function(req, res) {
     res.render('curriculum/curriculum', {
         title: 'Curriculum',
-        test: 'hi',
-        courses: Course.all
     });
 };
\ No newline at end of file
diff --git a/models/User.js b/models/User.js
index 95b9a69..66c32ff 100644
--- a/models/User.js
+++ b/models/User.js
@@ -15,7 +15,8 @@ var userSchema = new mongoose.Schema({
     gender: { type: String, default: '' },
     location: { type: String, default: '' },
     website: { type: String, default: '' },
-    picture: { type: String, default: '' }
+    picture: { type: String, default: '' },
+    username: { type: String, default: '' }
   },
 
   resetPasswordToken: String,
diff --git a/package.json b/package.json
index e21b1f0..566d3c5 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
     "passport-oauth": "^1.0.0",
     "passport-twitter": "^1.0.2",
     "request": "^2.44.0",
+    "socket.io": "^1.1.0",
     "stripe": "^2.8.0",
     "tumblr.js": "^0.0.4",
     "twilio": "^1.7.0",
diff --git a/public/css/firechat-default.css b/public/css/firechat-default.css
deleted file mode 100644
index abdf597..0000000
--- a/public/css/firechat-default.css
+++ /dev/null
@@ -1,944 +0,0 @@
-@charset "UTF-8";
-/* Boilerplate: Reset
-============================================================ */
-#firechat div,
-#firechat span,
-#firechat applet,
-#firechat object,
-#firechat iframe,
-#firechat h1,
-#firechat h2,
-#firechat h3,
-#firechat h4,
-#firechat h5,
-#firechat h6,
-#firechat p,
-#firechat blockquote,
-#firechat pre,
-#firechat a,
-#firechat abbr,
-#firechat acronym,
-#firechat address,
-#firechat big,
-#firechat cite,
-#firechat code,
-#firechat del,
-#firechat dfn,
-#firechat em,
-#firechat img,
-#firechat ins,
-#firechat kbd,
-#firechat q,
-#firechat s,
-#firechat samp,
-#firechat small,
-#firechat strike,
-#firechat strong,
-#firechat sub,
-#firechat sup,
-#firechat tt,
-#firechat var,
-#firechat b,
-#firechat u,
-#firechat i,
-#firechat center,
-#firechat dl,
-#firechat dt,
-#firechat dd,
-#firechat ol,
-#firechat ul,
-#firechat li,
-#firechat fieldset,
-#firechat form,
-#firechat label,
-#firechat legend,
-#firechat table,
-#firechat caption,
-#firechat tbody,
-#firechat tfoot,
-#firechat thead,
-#firechat tr,
-#firechat th,
-#firechat td,
-#firechat article,
-#firechat aside,
-#firechat canvas,
-#firechat details,
-#firechat embed,
-#firechat figure,
-#firechat figcaption,
-#firechat footer,
-#firechat header,
-#firechat hgroup,
-#firechat menu,
-#firechat nav,
-#firechat output,
-#firechat ruby,
-#firechat section,
-#firechat summary,
-#firechat time,
-#firechat mark,
-#firechat audio,
-#firechat video {
-    border: 0;
-    font-size: 12px;
-    font-family: arial, helvetica, sans-serif;
-    vertical-align: baseline;
-    margin: 0;
-    padding: 0;
-}
-#firechat article,
-#firechat aside,
-#firechat details,
-#firechat figcaption,
-#firechat figure,
-#firechat footer,
-#firechat header,
-#firechat hgroup,
-#firechat menu,
-#firechat nav,
-#firechat section {
-    display: block;
-}
-#firechat body {
-    line-height: 1;
-}
-#firechat ol,
-#firechat ul {
-    list-style: none;
-}
-#firechat blockquote,
-#firechat q {
-    quotes: none;
-}
-#firechat blockquote:before,
-#firechat blockquote:after,
-#firechat q:before,
-#firechat q:after {
-    content: none;
-}
-#firechat table {
-    border-collapse: collapse;
-    border-spacing: 0;
-}
-/* Boilerplate: Mixins
-============================================================ */
-.clearfix {
-    *zoom: 1;
-}
-.clearfix:before,
-.clearfix:after {
-    display: table;
-    content: "";
-    line-height: 0;
-}
-.clearfix:after {
-    clear: both;
-}
-/* Boilerplate: Responsive Layout
-============================================================ */
-#firechat {
-    color: #333;
-    text-align: left;
-}
-#firechat .center {
-    float: none !important;
-    margin-left: auto !important;
-    margin-right: auto !important;
-}
-#firechat .left {
-    float: left !important;
-}
-#firechat .right {
-    float: right !important;
-}
-#firechat .alignleft {
-    text-align: left !important;
-}
-#firechat .alignright {
-    text-align: right !important;
-}
-#firechat .aligncenter {
-    text-align: center !important;
-}
-#firechat .hidden {
-    display: none !important;
-}
-#firechat .row {
-    clear: both;
-}
-#firechat .fifth,
-#firechat .fivesixth,
-#firechat .fourfifth,
-#firechat .half,
-#firechat .ninetenth,
-#firechat .quarter,
-#firechat .sevententh,
-#firechat .sixth,
-#firechat .tenth,
-#firechat .third,
-#firechat .threefifth,
-#firechat .threequarter,
-#firechat .threetenth,
-#firechat .twofifth,
-#firechat .twothird,
-#firechat .full {
-    margin-left: 2.127659574468085%;
-    float: left;
-    min-height: 1px;
-}
-#firechat .fifth:first-child,
-#firechat .fivesixth:first-child,
-#firechat .fourfifth:first-child,
-#firechat .half:first-child,
-#firechat .ninetenth:first-child,
-#firechat .quarter:first-child,
-#firechat .sevententh:first-child,
-#firechat .sixth:first-child,
-#firechat .tenth:first-child,
-#firechat .third:first-child,
-#firechat .threefifth:first-child,
-#firechat .threequarter:first-child,
-#firechat .threetenth:first-child,
-#firechat .twofifth:first-child,
-#firechat .twothird:first-child,
-#firechat .full:first-child {
-    margin-left: 0;
-}
-#firechat .tenth {
-    width: 8.08510638297872%;
-}
-#firechat .sixth {
-    width: 14.893617021276595%;
-}
-#firechat .fifth {
-    width: 18.297872340425535%;
-}
-#firechat .quarter {
-    width: 23.404255319148938%;
-}
-#firechat .threetenth {
-    width: 26.3829787235%;
-}
-#firechat .third {
-    width: 31.914893617021278%;
-}
-#firechat .twofifth {
-    width: 38.72340425531915%;
-}
-#firechat .half {
-    width: 48.93617021276596%;
-}
-#firechat .sevententh {
-    width: 58.7234042555%;
-}
-#firechat .threefifth {
-    width: 59.14893617021278%;
-}
-#firechat .twothird {
-    width: 65.95744680851064%;
-}
-#firechat .threequarter {
-    width: 74.46808510638297%;
-}
-#firechat .ninetenth {
-    width: 74.8936170215%;
-}
-#firechat .fourfifth {
-    width: 79.57446808510639%;
-}
-#firechat .fivesixth {
-    width: 82.9787234042553%;
-}
-#firechat .full {
-    width: 100%;
-}
-#firechat .clipped {
-    overflow: hidden;
-}
-#firechat strong {
-    font-weight: bold;
-}
-#firechat em {
-    font-style: italic;
-}
-#firechat label {
-    display: block;
-}
-#firechat a {
-    color: #005580;
-}
-#firechat a:visited,
-#firechat a:hover,
-#firechat a:active {
-    color: #005580;
-}
-#firechat p {
-    margin: 10px 0;
-}
-#firechat h1,
-#firechat h2,
-#firechat h3,
-#firechat h4,
-#firechat h5,
-#firechat h6 {
-    margin: 10px 0;
-    font-family: inherit;
-    font-weight: bold;
-    line-height: 20px;
-    color: inherit;
-}
-#firechat h1,
-#firechat h2,
-#firechat h3 {
-    line-height: 40px;
-}
-#firechat h1 {
-    font-size: 38.5px;
-}
-#firechat h2 {
-    font-size: 31.5px;
-}
-#firechat h3 {
-    font-size: 24.5px;
-}
-#firechat h4 {
-    font-size: 17.5px;
-}
-#firechat h5 {
-    font-size: 14px;
-}
-#firechat h6 {
-    font-size: 11.9px;
-}
-#firechat small {
-    font-size: 90%;
-}
-/* Component: Tabs
-============================================================ */
-#firechat .nav {
-    list-style: none;
-}
-#firechat .nav > li > a {
-    display: block;
-    background-color: #eeeeee;
-    text-decoration: none;
-    overflow: hidden;
-    white-space: nowrap;
-}
-#firechat .nav > li > a:hover,
-#firechat .nav > li > a:focus {
-    background-color: #ffffff;
-}
-#firechat .nav-tabs {
-    border-bottom: 1px solid #ddd;
-    clear: both;
-}
-#firechat .nav-tabs > li {
-    float: left;
-    margin-bottom: -1px;
-    max-width: 45%;
-}
-#firechat .nav-tabs > li > a {
-    -webkit-border-top-right-radius: 4px;
-    -webkit-border-bottom-right-radius: 0;
-    -webkit-border-bottom-left-radius: 0;
-    -webkit-border-top-left-radius: 4px;
-    -moz-border-radius-topright: 4px;
-    -moz-border-radius-bottomright: 0;
-    -moz-border-radius-bottomleft: 0;
-    -moz-border-radius-topleft: 4px;
-    border-top-right-radius: 4px;
-    border-bottom-right-radius: 0;
-    border-bottom-left-radius: 0;
-    border-top-left-radius: 4px;
-    padding: 4px 8px;
-    margin-right: 2px;
-    line-height: 20px;
-    border: 1px solid transparent;
-    border-color: #cccccc;
-}
-#firechat .nav-tabs > .active > a,
-#firechat .nav-tabs > .active > a:hover,
-#firechat .nav-tabs > .active > a:focus {
-    border-bottom-color: transparent;
-    background-color: #ffffff;
-    cursor: default;
-}
-#firechat .tab-content {
-    overflow: auto;
-}
-#firechat .tab-content > .tab-pane {
-    display: none;
-}
-#firechat .tab-content > .active {
-    display: block;
-    background-color: #ffffff;
-}
-/* Component: dropdowns
-============================================================ */
-#firechat .caret {
-    display: inline-block;
-    width: 0;
-    height: 0;
-    vertical-align: top;
-    border-top: 4px solid #000000;
-    border-right: 4px solid transparent;
-    border-left: 4px solid transparent;
-    content: "";
-    margin-top: 8px;
-    margin-left: 2px;
-}
-#firechat .firechat-dropdown {
-    position: relative;
-}
-#firechat .firechat-dropdown-toggle {
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-    text-decoration: none;
-}
-#firechat .firechat-dropdown-toggle:focus,
-#firechat .firechat-dropdown-toggle:active {
-    outline: none;
-    text-decoration: none;
-}
-#firechat .firechat-dropdown-toggle.btn {
-    padding: 4px 0 0;
-    height: 22px;
-}
-#firechat .firechat-dropdown-menu {
-    *zoom: 1;
-    -webkit-border-top-right-radius: 0;
-    -webkit-border-bottom-right-radius: 4px;
-    -webkit-border-bottom-left-radius: 4px;
-    -webkit-border-top-left-radius: 0;
-    -moz-border-radius-topright: 0;
-    -moz-border-radius-bottomright: 4px;
-    -moz-border-radius-bottomleft: 4px;
-    -moz-border-radius-topleft: 0;
-    border-top-right-radius: 0;
-    border-bottom-right-radius: 4px;
-    border-bottom-left-radius: 4px;
-    border-top-left-radius: 0;
-    z-index: 1000;
-    display: none;
-    float: left;
-    position: absolute;
-    top: 100%;
-    left: 0;
-    width: 100%;
-    background-color: #ffffff;
-    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-    -webkit-background-clip: padding-box;
-    -moz-background-clip: padding;
-    background-clip: padding-box;
-    border: 1px solid #ccc;
-    min-width: 98%;
-    padding: 0;
-    margin: -1px 0 0;
-}
-#firechat .firechat-dropdown-menu:before,
-#firechat .firechat-dropdown-menu:after {
-    display: table;
-    content: "";
-    line-height: 0;
-}
-#firechat .firechat-dropdown-menu:after {
-    clear: both;
-}
-#firechat .firechat-dropdown-menu ul {
-    background-color: #ffffff;
-    list-style: none;
-    overflow-y: scroll;
-    max-height: 300px;
-}
-#firechat .firechat-dropdown-menu ul > li > a {
-    display: block;
-    padding: 1px 1px 1px 3px;
-    clear: both;
-    font-weight: normal;
-    line-height: 20px;
-    color: #333333;
-    white-space: nowrap;
-}
-#firechat .firechat-dropdown-menu ul > li > a.highlight {
-    background-color: #d9edf7;
-}
-#firechat .firechat-dropdown-menu ul > li > a:hover,
-#firechat .firechat-dropdown-menu ul > li > a:focus,
-#firechat .firechat-dropdown-menu ul > .active > a,
-#firechat .firechat-dropdown-menu ul > .active > a:hover,
-#firechat .firechat-dropdown-menu ul > .active > a:focus {
-    text-decoration: none;
-    color: #000000;
-    background-color: #d9edf7;
-    outline: 0;
-}
-#firechat .firechat-dropdown-menu ul > .disabled > a,
-#firechat .firechat-dropdown-menu ul > .disabled > a:hover,
-#firechat .firechat-dropdown-menu ul > .disabled > a:focus {
-    color: #999999;
-    text-decoration: none;
-    background-color: transparent;
-    background-image: none;
-    cursor: default;
-}
-#firechat .firechat-dropdown-header {
-    position: relative;
-    width: 100%;
-    padding: 10px 0;
-    background-color: #eeeeee;
-    border-bottom: 1px solid #cccccc;
-}
-#firechat .firechat-dropdown-footer {
-    position: relative;
-    width: 100%;
-    padding: 10px 0px;
-    background-color: #eeeeee;
-    border-top: 1px solid #cccccc;
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-#firechat .open {
-    *z-index: 1000;
-}
-#firechat .open > .firechat-dropdown-menu {
-    display: block;
-    border: 1px solid #cccccc;
-    -webkit-border-top-right-radius: 0;
-    -webkit-border-bottom-right-radius: 4px;
-    -webkit-border-bottom-left-radius: 4px;
-    -webkit-border-top-left-radius: 0;
-    -moz-border-radius-topright: 0;
-    -moz-border-radius-bottomright: 4px;
-    -moz-border-radius-bottomleft: 4px;
-    -moz-border-radius-topleft: 0;
-    border-top-right-radius: 0;
-    border-bottom-right-radius: 4px;
-    border-bottom-left-radius: 4px;
-    border-top-left-radius: 0;
-}
-#firechat .open > .firechat-dropdown-toggle {
-    outline: none;
-    text-decoration: none;
-    -webkit-border-top-right-radius: 4px;
-    -webkit-border-bottom-right-radius: 0;
-    -webkit-border-bottom-left-radius: 0;
-    -webkit-border-top-left-radius: 4px;
-    -moz-border-radius-topright: 4px;
-    -moz-border-radius-bottomright: 0;
-    -moz-border-radius-bottomleft: 0;
-    -moz-border-radius-topleft: 4px;
-    border-top-right-radius: 4px;
-    border-bottom-right-radius: 0;
-    border-bottom-left-radius: 0;
-    border-top-left-radius: 4px;
-}
-/* Component: Prompts
-============================================================ */
-#firechat .prompt-wrapper {
-    position: absolute;
-    z-index: 1000;
-}
-#firechat .prompt {
-    position: absolute;
-    z-index: 1001;
-    background-color: #ffffff;
-    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45);
-    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45);
-    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.45);
-}
-#firechat .prompt-header {
-    padding: 4px 8px;
-    font-weight: bold;
-    background-color: #eeeeee;
-    border: 1px solid #cccccc;
-    -webkit-border-top-right-radius: 4px;
-    -webkit-border-bottom-right-radius: 0;
-    -webkit-border-bottom-left-radius: 0;
-    -webkit-border-top-left-radius: 4px;
-    -moz-border-radius-topright: 4px;
-    -moz-border-radius-bottomright: 0;
-    -moz-border-radius-bottomleft: 0;
-    -moz-border-radius-topleft: 4px;
-    border-top-right-radius: 4px;
-    border-bottom-right-radius: 0;
-    border-bottom-left-radius: 0;
-    border-top-left-radius: 4px;
-}
-#firechat .prompt-header a.close {
-    opacity: 0.6;
-    font-size: 13px;
-    margin-top: 2px;
-}
-#firechat .prompt-header a.close:hover {
-    opacity: 0.9;
-}
-#firechat .prompt-body {
-    background-color: #ffffff;
-    padding: 4px 8px;
-    border-left: 1px solid #cccccc;
-    border-right: 1px solid #cccccc;
-}
-#firechat .prompt-footer {
-    padding: 4px 8px;
-    background-color: #eeeeee;
-    border: 1px solid #cccccc;
-    -webkit-border-top-right-radius: 0;
-    -webkit-border-bottom-right-radius: 4px;
-    -webkit-border-bottom-left-radius: 4px;
-    -webkit-border-top-left-radius: 0;
-    -moz-border-radius-topright: 0;
-    -moz-border-radius-bottomright: 4px;
-    -moz-border-radius-bottomleft: 4px;
-    -moz-border-radius-topleft: 0;
-    border-top-right-radius: 0;
-    border-bottom-right-radius: 4px;
-    border-bottom-left-radius: 4px;
-    border-top-left-radius: 0;
-}
-#firechat .prompt-background {
-    background-color: #333333;
-    border: 1px solid #333333;
-    opacity: 0.8;
-    z-index: 1000;
-    height: 100%;
-    width: 100%;
-}
-/* Component: Buttons
-============================================================ */
-#firechat .btn {
-    -webkit-touch-callout: none;
-    -webkit-user-select: none;
-    -khtml-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-    -webkit-border-radius: 4px;
-    -moz-border-radius: 4px;
-    border-radius: 4px;
-    height: 24px;
-    display: inline-block;
-    *display: inline;
-    *zoom: 1;
-    padding: 2px 5px;
-    margin-bottom: 0;
-    text-align: center;
-    vertical-align: middle;
-    cursor: pointer;
-    color: #333333;
-    font-size: 12px;
-    text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
-    background-color: #f5f5f5;
-    background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
-    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
-    background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
-    background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
-    background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
-    background-repeat: repeat-x;
-    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
-    border-color: #e6e6e6 #e6e6e6 #bfbfbf;
-    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-    *background-color: #e6e6e6;
-    filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-    border: 1px solid #cccccc;
-    *border: 0;
-    border-bottom-color: #b3b3b3;
-    *margin-left: .3em;
-    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-#firechat .btn:hover,
-#firechat .btn:focus,
-#firechat .btn:active,
-#firechat .btn.active,
-#firechat .btn.disabled,
-#firechat .btn[disabled] {
-    color: #333333;
-    background-color: #e6e6e6;
-    *background-color: #d9d9d9;
-    outline: 0;
-}
-#firechat .btn:active,
-#firechat .btn.active {
-    background-color: #cccccc;
-}
-#firechat .btn:first-child {
-    *margin-left: 0;
-}
-#firechat .btn:hover,
-#firechat .btn:focus {
-    color: #333333;
-    text-decoration: none;
-    background-position: 0 -15px;
-    -webkit-transition: background-position 0.1s linear;
-    -moz-transition: background-position 0.1s linear;
-    -o-transition: background-position 0.1s linear;
-    transition: background-position 0.1s linear;
-}
-#firechat .btn.active,
-#firechat .btn:active {
-    background-image: none;
-    outline: 0;
-    -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-    -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-#firechat .btn.disabled,
-#firechat .btn[disabled] {
-    cursor: default;
-    background-image: none;
-    opacity: 0.65;
-    filter: alpha(opacity=65);
-}
-#firechat .btn.disabled:active,
-#firechat .btn[disabled]:active {
-    -webkit-box-shadow: inherit;
-    -moz-box-shadow: inherit;
-    box-shadow: inherit;
-    background-color: #e6e6e6;
-}
-/* Component: Context Menu
-============================================================ */
-#firechat .contextmenu {
-    position: fixed;
-    z-index: 1001;
-    min-width: 150px;
-    border: 1px solid #cccccc;
-    -webkit-border-radius: 4px;
-    -moz-border-radius: 4px;
-    border-radius: 4px;
-}
-#firechat .contextmenu ul {
-    background-color: #ffffff;
-    list-style: none;
-}
-#firechat .contextmenu ul > li > a {
-    display: block;
-    padding: 3px 10px;
-    clear: both;
-    font-weight: normal;
-    line-height: 20px;
-    color: #333333;
-    white-space: nowrap;
-}
-#firechat .contextmenu ul > li > a.highlight {
-    background-color: #d9edf7;
-}
-#firechat .contextmenu ul > li > a:hover,
-#firechat .contextmenu ul > li > a:focus {
-    text-decoration: none;
-    color: #ffffff;
-    background-color: #0081c2;
-    outline: 0;
-}
-/* Custom Styles
-============================================================ */
-#firechat {
-    padding: 0;
-    font-family: sans-serif;
-    font-size: 12px;
-    line-height: 18px;
-}
-#firechat input,
-#firechat textarea {
-    width: 100%;
-    font-family: sans-serif;
-    font-size: 12px;
-    line-height: 18px;
-    padding: 2px 5px;
-    border: 1px solid #cccccc;
-    -webkit-border-radius: 1px;
-    -moz-border-radius: 1px;
-    border-radius: 1px;
-    -webkit-box-sizing: border-box;
-    -moz-box-sizing: border-box;
-    box-sizing: border-box;
-}
-#firechat input:-moz-placeholder,
-#firechat textarea:-moz-placeholder {
-    color: #aaaaaa;
-}
-#firechat input:-ms-input-placeholder,
-#firechat textarea:-ms-input-placeholder {
-    color: #aaaaaa;
-}
-#firechat input::-webkit-input-placeholder,
-#firechat textarea::-webkit-input-placeholder {
-    color: #aaaaaa;
-}
-#firechat input[disabled],
-#firechat textarea[disabled] {
-    background-color: #eeeeee;
-}
-#firechat input {
-    height: 24px;
-}
-#firechat textarea {
-    resize: none;
-    height: 40px;
-}
-#firechat .search-wrapper {
-    -webkit-border-radius: 15px;
-    -moz-border-radius: 15px;
-    border-radius: 15px;
-    border: 1px solid #cccccc;
-    margin: 0 5px;
-    padding: 2px 5px;
-    background: #ffffff;
-}
-#firechat .search-wrapper > input[type=text] {
-    padding-left: 0px;
-    border: none;
-}
-#firechat .search-wrapper > input[type=text]:focus,
-#firechat .search-wrapper > input[type=text]:active {
-    outline: 0;
-}
-#firechat .chat {
-    overflow: auto;
-    -ms-overflow-x: hidden;
-    overflow-x: hidden;
-    height: 290px;
-    position: relative;
-    margin-bottom: 5px;
-    border: 1px solid #cccccc;
-    border-top: none;
-    overflow-y: scroll;
-}
-#firechat .chat textarea {
-    overflow: auto;
-    vertical-align: top;
-}
-#firechat .message {
-    color: #333;
-    padding: 3px 5px;
-    border-bottom: 1px solid #ccc;
-}
-#firechat .message.highlighted {
-    background-color: #d9edf7;
-}
-#firechat .message .name {
-    font-weight: bold;
-    overflow-x: hidden;
-}
-#firechat .message.message-self {
-    color: #2675ab;
-}
-#firechat .message:nth-child(odd) {
-    background-color: #f9f9f9;
-}
-#firechat .message:nth-child(odd).highlighted {
-    background-color: #d9edf7;
-}
-#firechat .message:nth-child(odd).message-local {
-    background-color: #effafc;
-}
-#firechat .message-content {
-    word-wrap: break-word;
-    padding-right: 45px;
-}
-#firechat .message-content.red {
-    color: red;
-}
-#firechat .message.message-notification .message-content {
-    font-style: italic;
-}
-#firechat ul::-webkit-scrollbar {
-    -webkit-appearance: none;
-    width: 7px;
-}
-#firechat ul::-webkit-scrollbar-thumb {
-    border-radius: 4px;
-    -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
-}
-#firechat #firechat-header {
-    padding: 6px 0 0 0;
-    height: 40px;
-}
-#firechat #firechat-tabs {
-    height: 435px;
-}
-#firechat #firechat-tab-list {
-    background-color: #ffffff;
-}
-#firechat #firechat-tab-content {
-    width: 100%;
-    background-color: #ffffff;
-}
-#firechat .tab-pane-menu {
-    border: 1px solid #ccc;
-    border-top: none;
-    vertical-align: middle;
-    padding-bottom: 5px;
-}
-#firechat .tab-pane-menu .firechat-dropdown {
-    margin: 5px 0 0 5px;
-}
-#firechat .tab-pane-menu > .icon {
-    margin: 5px 2px 0;
-}
-#firechat .icon {
-    display: inline-block;
-    *margin-right: .3em;
-    line-height: 20px;
-    vertical-align: middle;
-    background-repeat: no-repeat;
-    padding: 0;
-    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAL8AAAANEAYAAACoeGM7AAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAM80lEQVR42t2cZ4xWRRfHZ3dhwbK7CJZYYiOxV+wlUQzCKmBBsWPvYjQiaiyxYUwULLErltgrNpRF4q5iV0BRLFE0diUi7LIq6Pqw74f//rzO4Zm9d+5z9c37ni//3OfemXtm5sypc58q55xzrrpauHSpcOONheedJ9x9d2Hv3sLvvxfefbfw5puFf/5Zvr+8VFUl7Ozs/rkNNxTef79w222F77wjPOoo4ccfF/O+UDvGzTVEf8wH1zU1wlJJePTRwv33Fx57rPDnn/1+wbzzG+IXvrhmPUPzY/kJEc/HzqslK1fLLy8cMUK4667CFVYQ/vqr8PXXhU884f8eL6eXXKJ2wqSd3tDY2KOHc84NGqSRnnCC7tbVCdvbhRMnasWnTdt5Z+eca2pK6797Yh6ef1641lrCPfYQfv21UNwl62qv115b2Nws/PZb4d57C3/7LftalaOQ/C63nPCII4Tsg6228scHffqp8LHHhPfe6/8eu49feEH44YfCq64S/v67cMGC7tuvu65w6FDhllsKkS/0zoQJaZzMmSO+JaEzZmgkY8b88Ydzzs2b19HhnHNffSWJ6tVLM1cq/fKLrjXiUaPUrn9/XZ9yip6rqVmyxDnnnnrKvPbAA4V6TTJxP/zgT4zdyC+9JOzXz++PgceSVSS1tUJtE+c22si/f+21Pj9Wwd54o//8ZpsJd9xR2KtX9+8vmkLzcsABPt9z5wr32af88xiOf4oaGoSHH17M+/LKg203aJDwiy+EVh5DyHzuuWcxfDn31lvOOffMM7Nn6z1SsZ2dn30m/OQT4eefC7/8UvjBB0LaVzav7D873vfeE6LQISvv3Of50P6OJetgWFp5ZeFzz5V/b1bEEd1333x80g+O4mWXCXEQMDB33CG8/XYhBhuDmMZnOn30kZ5/+23nnGtpefNN55wbP96Xl/nzhYsWCdvauEbeQP1Of6US/XRZfDz8Bx8U9uwpPPFE4eOPC/EMVl9dOG6ccORI4Q03CA87LHbA5RcCwgO47TbhokXCYcOERCKQ7GJiMLiPokeQ2QCjRwtvukkY6zHg0V10kXDAAH++EIz77hM++aRwxRWFq64q3H57v9/+/YVPPy2cOFF47rnCNE8kll/Gi4eIAWU9v/vOnz8MA4qjb18hG51+2JiLF+ebX/hA4U+b5t+/7jrhAw8IkQ88WvnPiZzi4Q0eXL6/dEJhawaGDydmk4uEQmhu1kj79tVImb8RI8TZwIG0p78ddnAubOhDhIcPIXd4nnh4OHYYzPXXF7K/ed5GBLb/NLKRFOtMfyCZAiIKns8aSVp9xP4i8nv//Ti+icjgl0iDeUsjpKDSyJYeqqs1A+3taDBpkn79Qi5YW9vf5wXq0YP+1L5U6loAUjpW4WPhQhN00EFC2SXnDj1UiAF44w2hTWnEEqkcCIWJYkShQNbDIIJZZx2h9Xhs/7EpFDwCNs6UKf584tGQsiFCGTJEuMEG/jxBzBe/H3+8cJddhBiAZ58thl/eM2uW8MgjhWyAyy8XojhRUGuuKayvF7Jhf/pJ+O67wltvFU6dGscv78fTgg45RPjII+XbYZA23bT8/Vtu8ecBDy9MpHS0EROFr1B76lQp7r32su20EwYMkGSOG4dq4I0a4fDh9G9TQN0TKR0iB8aD3G+9tRDHDoPPenKf53GYiABiPWm7f046SUgKDs8aQwxlVfiQTVXh0BChxir+kDol5WPv24im+Ahcb6iu9jVnR4euGX9oXsLj7GKYHP68eUI8ANsRA7WKE8sNkUopdvgJsW0QUBbc3oewl6GQs9LUDgr85JOF5OZHjRKyESZNEp55ppBIC7IbxgoSkQzt2OgXX1wMv9RCFBAuG4HgSREKU0vB42Jjo6gxtPvtJ8TzTM91+kQOf731hHj4KHybA0bRK8B1bpNNhAqkE8OH40CKLZ3I4a+0kq6l8JuapPAbGx991Lm/r5s8+aFDlWOdOVMuS0ODNm5bm1RWSwsSrJY2FZVGRFYYYgwt+wO5EpeJ4eaa+zxPexQ+/Wcl+iFCxeBvs43QRujW048lu09Y16KI/m3Ewu8h/VS55w/5b7AGMrbW5lyXImQh8EhtqGBDGOu525QDnm5RhOcBzZ8vfO01IYIaev/Mmf5zpALsfSiv5bYLfeqpQhSUrYHY92GYbD+shx0XKSOKlrFk1xHFTQRon7M1FNtepaOErwsvFBIqkzKKlQ8MDoTnCpFKwyCi2FZbTYhDgydO6gfaaaesnFC0xQcUUQwlBi6VpPCHDJGKmDyZGWMldY2nPmmSP5NEdlkJRYSCZr7FRbiIzTX3eZ72tiiclZjf444rzyd6hkid9+fNCNhxvfJKvn5C/YVy9nZVYw89ZCdfI6TVEuCLCI4Zhp+Ojq6FIAdLFR0PjpQOFpyFsQMidw5ZxRo7ATaEI6fMgFD8FJsxXCgee00qSiWPJNfKaYLp08u/P1YQ4ZfxWgWDAcOjZmMR8G+3nZBTPbwffhYuFJ5zjhCPKi9ZBUzkh8dpBTkk2KSKLr1UiAIJEYYhK2GQIOto4OGj8NdYQ4hck8tnvkmtQSGDHKK6On8nUNtIPHwUvp05SeSwYTI1s2YpJhk5kliO/uP4Qe6IxMnhk9JhXqyCtQ4Oz9Oe/nwzl05EtJbI5fMeTs8QscIP+zwtEuA+csz+fvjhOH5tf6SSoZCjEipeKw5MVj12PemZ9tXVvunt2dOuHE+K06oqvbG2lp2ikS1ZIknv3burO45lXn+9kKItOfzQwhO6jR0r5PiXPaUQmzNHkMllIyhMMBudmgKnjqxnwnSssooQhWpTLOR6UVwc/4ul1lYhx7rwRBF4PBEEwxLFavhkeTmmN2aMEAG3ghdrqHx149z555efx9DGY37vvFNoPcxQe1v0SyObe8cRIYf/4otCPHwUPsVge4yXWgTEumWl9nZtsPp6irbk8OVyTJ5sYyRJdGOjkitJjUP3FyzwNzIOV1ZivdinFG3J4dt5twqW9eJ5nUFKDIB1xNIIh8p6oijUa64RcnyT2SLSJHWXdtqKcdD/BRcIifBiCf44TIGe2WILYaiGhWHFoaFGxnxtvnlWDvwZY/yLF6toy+8LF2rGamrESXK8WRIwe7YcjKYmzdD06Xpi7lzFWqR4/1pYeywMT5ncL4rpiiuE9tgnIRyeozP9ZyVSBNZyskHxpFFUCGroOCf36Zf28MtzRCqxOXPaM24iJxsJQQgOCgyiqM4GJvduKZRbjOWXXH+fPkKKiUQmOnyYRBrwZeeXCIxxQ/mPSfpEkZn3KZO+7PHiH38U2mKurQHY478ooHSSaZswgWOZum5uFra2iqPOTs0g93EE4NzWAKZM4RioIoDx4/PNE4YQxWNTs6HUgL1Pe3sMNCuF9qOVG2penIPHYOHokcJ8+WVhS4sQR4NaJP3F7luI9tSALBFxovhBW6uonFSORm7mzMEAzJjhnHMNDdLI9fXgq68651xdnZLYffpY+cpIhLzkUNNySd98I7QKFPs0cGC+4dv3sCAYIE4bhXJboWvwrruExxwjJPWStyjD8/b0AyEiCt4Wh6xBpCi+225CG5kUdWogxC/HMyldshGJ7IhcWIfTTxciLxhWqKgcJxuPc/h2nTAAaYrq4IP9dni01jCEiVM3nIdWdaizUxuTDdvaSgQQ6kfPNTVhKPgOgP7j5id0Dh9DbR05Unlch54PfQeQRuyrUM7ZOoxkCq6+WkikhiOBIQ/JEx7sGWfE8QlZOYKyOi7sS+uQgdn3ra/4LT/ZSS1razEEfBgoOf0rdRUaIB4rXyCedpqQFAu5KzxdPyBJPPRlj7d1Tw89JLznHiGeb+hDFVIWIQPll9SS3ynKUUwj5RWbI6Q/inWVEgJsUxRFKdKi+T3rLKEtshf9IZytPdgIgNM75PA5dWQ9fBAFE0+cu+dDGQyAHwGMHi0P/sordT12LB4+Ct//0Cbvh1zWINr9gAePIYe4thGCbU//sYRHn/fDLBwyawhDOfe8ESbvCx3/zHuKJp5Cil8cVlVlxcjXhv5yICvh4aPwreDlJY5nUay1Cj2rINn2lD4qPX6KwcPzRQHhKeP5WeQ+ng2nSyhCcv4fKip1EuIXRQlfXIMUn7nPl9SkhKhFFM2v7QcDEIoAQoiHbxV+5V/uEgGQAuJLXRQ7X/KS0sHD///8cpcIl/P69MN3GDh2RBzUvvjLFVI2NuK1einty+CsZDMY/z0qyuOvkGwIY8+zMuE2ZYGHj8JHUWQlm/sm1ENRZ81dWrShLEVAy1/20F9EEZrTQnk9HJCNYL+YLMrD+F/jF7IbnBQQNQDOi/MFJ9fk8O26xisMQmb7OykaagD+p/RLl3JNDj+U0gn13z0xLhQ0BtEqfLtP7TXP057+YvcDCpvDInwvYInUcnIqqjLKK2/IydlnF8NHfvq3FP9/ANgTCJ9z9ZF7AAAAAElFTkSuQmCC) no-repeat top left;
-    opacity: 0.3;
-    font-size: 22px;
-    font-family: Arial;
-    font-weight: bold;
-    overflow: hidden;
-}
-#firechat .icon.plus {
-    margin-top: 0;
-    vertical-align: top;
-    background: transparent;
-}
-#firechat .icon.search {
-    background-position: 0 0;
-    width: 13px;
-    height: 13px;
-}
-#firechat .icon.close {
-    background-position: -120px 0;
-    width: 13px;
-    height: 13px;
-}
-#firechat .icon.user-chat {
-    background-position: -138px 0;
-    width: 17px;
-    height: 13px;
-}
-#firechat .icon.user-group {
-    background-position: -18px 0;
-    width: 17px;
-    height: 13px;
-}
-#firechat .icon.user-mute {
-    background-position: -84px 0;
-    width: 13px;
-    height: 13px;
-}
-#firechat .icon.user-mute.red {
-    background-position: -102px 0;
-    width: 13px;
-    height: 13px;
-}
-#firechat .icon:hover,
-#firechat .btn:hover > .icon {
-    opacity: 0.6;
-}
-#firechat a > .icon {
-    margin: 3px 1px;
-}
\ No newline at end of file
diff --git a/public/css/styles.less b/public/css/styles.less
index 3b965a3..9aed791 100644
--- a/public/css/styles.less
+++ b/public/css/styles.less
@@ -97,4 +97,127 @@ body { margin: 0; height: 100%; position: relative; }
 
 .btn-nav {
   margin-top: 5px;
+}
+
+ul {
+  list-style: none;
+  word-wrap: break-word;
+}
+
+/* Pages */
+
+.pages {
+  height: inherit;
+  margin: 0;
+  padding: 0;
+  width: inherit;
+}
+
+.page {
+  height: 100%;
+  position: absolute;
+  width: 100%;
+}
+
+/* Login Page */
+
+.login.page {
+  background-color: #000;
+}
+
+.login.page .form {
+  height: 100px;
+  margin-top: -100px;
+  position: absolute;
+
+  text-align: center;
+  top: 50%;
+  width: 100%;
+}
+
+.login.page .form .usernameInput {
+  background-color: transparent;
+  border: none;
+  border-bottom: 2px solid #fff;
+  outline: none;
+  padding-bottom: 15px;
+  text-align: center;
+  width: 400px;
+}
+
+.login.page .title {
+  font-size: 200%;
+}
+
+.login.page .usernameInput {
+  font-size: 200%;
+  letter-spacing: 3px;
+}
+
+.login.page .title, .login.page .usernameInput {
+  color: #fff;
+  font-weight: 100;
+}
+
+/* Chat page */
+
+.chat.page {
+  display: none;
+}
+
+/* Font */
+
+.messages {
+  font-size: 150%;
+}
+
+.inputMessage {
+  font-size: 100%;
+}
+
+.log {
+  color: gray;
+  font-size: 70%;
+  margin: 5px;
+  text-align: center;
+}
+
+/* Messages */
+
+.chatArea {
+  height: 100%;
+  padding-bottom: 60px;
+}
+
+.messages {
+  height: 100%;
+  margin: 0;
+  overflow-y: scroll;
+  padding: 10px 20px 10px 20px;
+}
+
+.message.typing .messageBody {
+  color: gray;
+}
+
+.username {
+  float: left;
+  font-weight: 700;
+  overflow: hidden;
+  padding-right: 15px;
+  text-align: right;
+}
+
+/* Input */
+
+.inputMessage {
+  border: 10px solid #000;
+  bottom: 0;
+  height: 60px;
+  left: 0;
+  outline: none;
+  padding-left: 10px;
+  position: absolute;
+  right: 0;
+  width: 100%;
 }
\ No newline at end of file
diff --git a/public/js/firechat-default.js b/public/js/firechat-default.js
deleted file mode 100644
index 298ba2f..0000000
--- a/public/js/firechat-default.js
+++ /dev/null
@@ -1,1869 +0,0 @@
-(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index<t.index?-1:1}),"value")};var F=function(n,t,r,e){var u={},i=k(t||w.identity);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};w.groupBy=function(n,t,r){return F(n,t,r,function(n,t,r){(w.has(n,t)?n[t]:n[t]=[]).push(r)})},w.countBy=function(n,t,r){return F(n,t,r,function(n,t){w.has(n,t)||(n[t]=0),n[t]++})},w.sortedIndex=function(n,t,r,e){r=null==r?w.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n","	":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
-this["FirechatDefaultTemplates"] = this["FirechatDefaultTemplates"] || {};
-
-this["FirechatDefaultTemplates"]["templates/layout-full.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div id=\'firechat\' class=\'full\'>\n<div id=\'firechat-header\' class=\'clearfix\'>\n<div class=\'clearfix\'><div class=\'half firechat-dropdown\' style=\'\'>\n<a id=\'firechat-btn-rooms\' class=\'firechat-dropdown-toggle btn full\' data-toggle="firechat-dropdown" href=\'#\'>\n<span class=\'icon user-chat\'></span>\nChat Rooms\n<span class=\'caret\'></span>\n</a>\n<div class=\'firechat-dropdown-menu full\' role=\'menu\'><ul id=\'firechat-room-list\'></ul><div class=\'firechat-dropdown-footer aligncenter\'>\n<button type=\'button\' class=\'btn twothird center\' id=\'firechat-btn-create-room-prompt\'>Create Room</button>\n</div></div></div>\n<div class=\'half firechat-dropdown\' style=\'\'>\n<a data-event=\'firechat-user-search-btn\' class=\'btn full firechat-dropdown-toggle\' data-toggle="firechat-dropdown" href=\'#\'>\n<span class=\'icon user-group\'></span>\nVisitors\n<span class=\'caret\'></span>\n</a>\n<div class=\'firechat-dropdown-menu\' role=\'menu\'>\n<div class=\'firechat-dropdown-header aligncenter clearfix\'>\n<div class=\'search-wrapper\'>\n<span class=\'icon search\'></span>\n<input type=\'text\' data-event=\'firechat-user-search\' data-template=\'templates/user-search-list-item.html\' data-target=\'firechat-user-search\' data-controls=\'firechat-user-search-controls\' class=\'center fivesixth\'>\n</div>\n</div>\n<ul id=\'firechat-user-search\'></ul><div class=\'firechat-dropdown-footer aligncenter clearfix\'>\n<div id=\'firechat-user-search-controls\' class=\'clearfix\'>\n<span class="quarter"></span>\n<button type=\'button\' class=\'btn half\' data-event=\'firechat-user-search\' data-toggle=\'firechat-pagination-next\' data-template=\'templates/user-search-list-item.html\' data-target=\'firechat-user-search\' data-controls=\'firechat-user-search-controls\' disabled=disabled>Next</button>\n</div><label class=\'center full\'>\n<small>Use "+ Invite" button within chat rooms for regular invites.</small>\n</label>\n</div>\n</div>\n</div>\n</div>\n</div>\n<div id=\'firechat-tabs\' class=\'clearfix\'>\n<ul id=\'firechat-tab-list\' class=\'nav nav-tabs clearfix\'></ul>\n<div id=\'firechat-tab-content\' class=\'tab-content\'></div>\n</div><div id=\'firechat-footer\' class=\'clearfix\'></div>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/layout-popout.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div id=\'firechat\' class=\'full\'>\n<div id=\'firechat-tabs\' class=\'clearfix\'>\n<ul id=\'firechat-tab-list\' class=\'nav nav-tabs clearfix\'></ul>\n<div id=\'firechat-tab-content\' class=\'tab-content\'></div>\n</div>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/message-context-menu.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<div data-toggle=\'firechat-contextmenu\' class=\'contextmenu\' data-message-id=\'' +__e( id ) +'\'>\n<ul>\n<li><a href=\'#!\' data-event=\'firechat-user-warn\'>Warn User</a></li>\n'; if (allowKick) { ;__p += '\n<li><a href=\'#!\' data-event=\'firechat-user-kick\'>Kick User</a></li>\n'; } ;__p += '\n<li><a href=\'#!\' data-event=\'firechat-user-suspend-hour\'>Suspend User (1 Hour)</a></li>\n<li><a href=\'#!\' data-event=\'firechat-user-suspend-day\'>Suspend User (1 Day)</a></li>\n<li><a href=\'#!\' data-event=\'firechat-message-delete\'>Delete Message</a></li>\n</ul>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/message.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<div class=\'message message-' +__e( type ) +' '; if (isSelfMessage) { ;__p += ' message-self '; } ;__p += '\' data-message-id=\'' +__e( id ) +'\' data-user-id=\'' +__e( userId ) +'\' data-user-name=\'' +__e( name ) +'\' data-class="firechat-message">\n<div class=\'clearfix\'>\n<label class=\'fourfifth\'>\n<strong class=\'name\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</strong>\n<em>(' +__e( localtime ) +')</em>:\n</label>'; if (!disableActions) { ;__p += '\n<label class=\'fifth alignright\'>\n<a href=\'#!\' data-event=\'firechat-user-chat\' class=\'icon user-chat\' title=\'Invite to Private Chat\'>&nbsp;</a>\n<a href=\'#!\' data-event=\'firechat-user-mute-toggle\' class=\'icon user-mute\' title=\'Mute User\'>&nbsp;</a>\n</label>\n'; } ;__p += '</div>\n<div class=\'clearfix message-content\'>\n' +((__t = ( message )) == null ? '' : __t) +'\n</div>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt-alert.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div class=\'aligncenter clearfix\'>\n<h6>' +__e( message ) +'</h6>\n<p class=\'clearfix\'>\n<button type=\'button\' class=\'btn quarter right close\'>Close</button>\n</p>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt-create-room.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div class=\'clearfix\'>\n<h6>Give your chat room a name:</h6>\n<input data-input=\'firechat-room-name\' type=\'text\' placeholder=\'Room name...\' style=\'margin-bottom: 5px;\' maxlength=\'' +__e( maxLengthRoomName ) +'\'>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt-invitation.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div class=\'aligncenter clearfix\'>\n<h5>' +__e( fromUserName ) +'</h5>\n<p>invited you to join</p>\n<h5>' +__e( toRoomName ) +'</h5>\n<p class=\'clearfix\'>\n<button data-toggle=\'accept\' type=\'button\' class=\'btn\'>Accept</button>\n<button data-toggle=\'decline\' type=\'button\' class=\'btn\'>Decline</button>\n</p>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt-invite-private.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div class=\'aligncenter clearfix\'>\n<h6>Invite <strong>' +__e( userName ) +'</strong> to ' +__e( roomName ) +'?</h6>\n<p class=\'clearfix\'>\n<button data-toggle=\'accept\' type=\'button\' class=\'btn\'>Invite</button>\n<button data-toggle=\'decline\' type=\'button\' class=\'close btn\'>Cancel</button>\n</p>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt-invite-reply.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<div class=\'aligncenter clearfix\'>\n<h5>' +__e( toUserName ) +'</h5>\n<p>\n'; if (status === 'accepted') { ;__p += ' accepted your invite. '; } else { ;__p += ' declined your invite. '; } ;__p += '\n</p>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt-user-mute.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div class=\'aligncenter clearfix\'>\n<h5>' +__e( userName ) +'</h5>\n<p class=\'clearfix\'>\n<button data-toggle=\'accept\' type=\'button\' class=\'btn\'>Mute</button>\n<button data-toggle=\'decline\' type=\'button\' class=\'btn\'>Cancel</button>\n</p>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/prompt.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div class=\'prompt hidden\'>\n<div class=\'prompt-header\'>\n' +__e( title ) +'\n<a href=\'#!\' class=\'close right\'>X</a>\n</div>\n<div class=\'prompt-body clearfix\'>\n' +((__t = ( content )) == null ? '' : __t) +'\n</div>\n<div class=\'prompt-footer\'></div>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/room-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<li data-room-type=\'' +__e( type ) +'\' data-room-id=\'' +__e( id ) +'\' data-room-name=\'' +__e( name ) +'\'>\n<a href=\'#!\' class=\'clearfix '; if (isRoomOpen) { ;__p += ' highlight '; } ;__p += '\'>\n<span class=\'left\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</span>\n</a>\n</li>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/room-user-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<li data-user-id=\'' +__e( id ) +'\' data-user-name=\'' +__e( name ) +'\'>\n<a href=\'#!\' class=\'clearfix\'>\n<span class=\'left twothird clipped\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</span>'; if (!disableActions) { ;__p += '\n<span data-event=\'firechat-user-mute-toggle\' class=\'icon user-mute right '; if (isMuted) { ;__p += ' red '; } ;__p += '\' title=\'Toggle User Mute\'>&nbsp;</span>\n<span data-event=\'firechat-user-chat\' class=\'icon user-chat right\' title=\'Invite to Private Chat\'>&nbsp;</span>\n'; } ;__p += '\n</a>\n</li>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/room-user-search-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<li data-user-id=\'' +__e( id ) +'\' data-user-name=\'' +__e( name ) +'\'>\n<a href=\'#!\' class=\'clearfix\'>\n'; if (disableActions) { ;__p += '\n<span class=\'left fourfifth clipped\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</span>\n'; } else { ;__p += '\n<span data-event=\'firechat-user-invite\' class=\'left fourfifth clipped\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</span>\n<span data-event=\'firechat-user-invite\' class=\'icon plus right\' title=\'Invite to Room\'>+</span>\n'; } ;__p += '\n</a>\n</li>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/tab-content.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<div id=\'' +__e( id ) +'\' data-room-id=\'' +__e( id ) +'\' class=\'tab-pane\'>\n<div class=\'tab-pane-menu clearfix\'><div class=\'firechat-dropdown twofifth\'>\n<a data-event=\'firechat-user-room-list-btn\' class=\'full btn firechat-dropdown-toggle\' data-toggle="firechat-dropdown" href=\'#\' data-target=\'firechat-room-user-list-' +__e( id ) +'\'>\n<span class=\'icon user-group\'></span>\nIn Room\n<span class=\'caret\'></span>\n</a>\n<div class=\'firechat-dropdown-menu\' role=\'menu\'>\n<ul id=\'firechat-room-user-list-' +__e( id ) +'\' class=\'full\'></ul>\n</div>\n</div><div class=\'firechat-dropdown twofifth\'>\n<a data-event=\'firechat-user-search-btn\' class=\'full btn firechat-dropdown-toggle\' data-toggle="firechat-dropdown" href=\'#\'>\n<span class=\'icon plus\'>+</span>\nInvite\n<span class=\'caret\'></span>\n</a><div class=\'firechat-dropdown-menu\' role=\'menu\'>\n<div class=\'firechat-dropdown-header aligncenter clearfix\'>\n<div class=\'search-wrapper\'>\n<span class=\'icon search\'></span>\n<input type=\'text\' data-event=\'firechat-user-search\' data-template=\'templates/room-user-search-list-item.html\' data-target=\'firechat-room-user-search-' +__e( id ) +'\' data-controls=\'firechat-room-user-search-controls-' +__e( id ) +'\' class=\'center fivesixth\'>\n</div>\n</div>\n<ul id=\'firechat-room-user-search-' +__e( id ) +'\'></ul><div class=\'firechat-dropdown-footer aligncenter clearfix\'>\n<div id=\'firechat-room-user-search-controls-' +__e( id ) +'\' class=\'clearfix\'><span class="quarter"></span>\n<!--\n<button type=\'button\' class=\'btn third disabled\' data-event=\'firechat-user-search\' data-template=\'templates/room-user-search-list-item.html\' data-target=\'firechat-room-user-search-' +__e( id ) +'\' data-controls=\'firechat-room-user-search-controls-' +__e( id ) +'\' data-toggle=\'firechat-pagination-prev\' disabled=disabled>Prev</button>\n-->\n<button type=\'button\' class=\'btn half disabled\' data-event=\'firechat-user-search\' data-template=\'templates/room-user-search-list-item.html\' data-target=\'firechat-room-user-search-' +__e( id ) +'\' data-controls=\'firechat-room-user-search-controls-' +__e( id ) +'\' data-toggle=\'firechat-pagination-next\'  disabled=disabled>Next</button>\n</div>\n</div>\n</div>\n</div><a href=\'#!\' data-event=\'firechat-close-tab\' class=\'icon close right\' style=\'15px 5px\' title=\'Leave Room\'></a></div><div class=\'clearfix\'>\n<div id=\'firechat-messages' +__e( id ) +'\' class=\'chat\'></div>\n</div><div class=\'clearfix\'>\n<label>Your message:</label>\n<textarea id=\'textarea' +__e( id ) +'\' placeholder=\'Type your message here...\'></textarea>\n</div>\n</div>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/tab-menu-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape;with (obj) {__p += '<li data-room-id=\'' +__e( id ) +'\'>\n<a href=\'#' +__e( id ) +'\' data-toggle=\'firechat-tab\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</a>\n</li>';}return __p};
-
-this["FirechatDefaultTemplates"]["templates/user-search-list-item.html"] = function(obj) {obj || (obj = {});var __t, __p = '', __e = _.escape, __j = Array.prototype.join;function print() { __p += __j.call(arguments, '') }with (obj) {__p += '<li data-user-id=\'' +__e( id ) +'\' data-user-name=\'' +__e( name ) +'\'>\n<a href=\'#!\' class=\'clearfix\'>\n'; if (disableActions) { ;__p += '\n<span class=\'left fivesixth clipped\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</span>\n'; } else { ;__p += '\n<span data-event=\'firechat-user-chat\' class=\'left fivesixth clipped\' title=\'' +__e( name ) +'\'>' +__e( name ) +'</span>\n<span data-event=\'firechat-user-chat\' class=\'icon user-chat right\' title=\'Invite to Private Chat\'>&nbsp;</span>\n'; } ;__p += '\n</a>\n</li>';}return __p};
-(function($) {
-
-    // Shim for Function.bind(...) - (Required by IE < 9, FF < 4, SF < 6)
-    if (!Function.prototype.bind) {
-        Function.prototype.bind = function(oThis) {
-            if (typeof this !== "function") {
-                throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
-            }
-
-            var aArgs = Array.prototype.slice.call(arguments, 1),
-                fToBind = this,
-                fNOP = function() {},
-                fBound = function() {
-                    return fToBind.apply(this instanceof fNOP && oThis ? this : oThis,
-                        aArgs.concat(Array.prototype.slice.call(arguments)));
-                };
-
-            fNOP.prototype = this.prototype;
-            fBound.prototype = new fNOP();
-            return fBound;
-        };
-    }
-
-    // Shim for Object.keys(...) - (Required by IE < 9, FF < 4)
-    Object.keys = Object.keys || function(oObj) {
-        var result = [];
-        for (var name in oObj) {
-            if (oObj.hasOwnProperty(name)) {
-                result.push(name);
-            }
-        }
-        return result;
-    };
-
-})();
-
-// Firechat is a simple, easily-extensible data layer for multi-user,
-// multi-room chat, built entirely on [Firebase](https://firebase.com).
-//
-// The Firechat object is the primary conduit for all underlying data events.
-// It exposes a number of methods for binding event listeners, creating,
-// entering, or leaving chat rooms, initiating chats, sending messages,
-// and moderator actions such as warning, kicking, or suspending users.
-//
-//     Firechat.js 1.0.0
-//     https://firebase.com
-//     (c) 2014 Firebase
-//     License: MIT
-
-// Setup
-// --------------
-(function(Firebase) {
-
-    // Establish a reference to the `window` object, and save the previous value
-    // of the `Firechat` variable.
-    var root = this,
-        previousFirechat = root.Firechat;
-
-    function Firechat(firebaseRef, options) {
-
-        // Instantiate a new connection to Firebase.
-        this._firebase = firebaseRef;
-
-        // User-specific instance variables.
-        this._user = null;
-        this._userId = null;
-        this._userName = null;
-        this._isModerator = false;
-
-        // A unique id generated for each session.
-        this._sessionId = null;
-
-        // A mapping of event IDs to an array of callbacks.
-        this._events = {};
-
-        // A mapping of room IDs to a boolean indicating presence.
-        this._rooms = {};
-
-        // A mapping of operations to re-queue on disconnect.
-        this._presenceBits = {};
-
-        // Commonly-used Firebase references.
-        this._userRef        = null;
-        this._messageRef     = this._firebase.child('room-messages');
-        this._roomRef        = this._firebase.child('room-metadata');
-        this._privateRoomRef = this._firebase.child('room-private-metadata');
-        this._moderatorsRef  = this._firebase.child('moderators');
-        this._suspensionsRef = this._firebase.child('suspensions');
-        this._usersOnlineRef = this._firebase.child('user-names-online');
-
-        // Setup and establish default options.
-        this._options = options || {};
-
-        // The number of historical messages to load per room.
-        this._options.numMaxMessages = this._options.numMaxMessages || 50;
-    }
-
-    // Run Firechat in *noConflict* mode, returning the `Firechat` variable to
-    // its previous owner, and returning a reference to the Firechat object.
-    Firechat.noConflict = function noConflict() {
-        root.Firechat = previousFirechat;
-        return Firechat;
-    };
-
-    // Export the Firechat object as a global.
-    root.Firechat = Firechat;
-
-    // Firechat Internal Methods
-    // --------------
-    Firechat.prototype = {
-
-        // Load the initial metadata for the user's account and set initial state.
-        _loadUserMetadata: function(onComplete) {
-            var self = this;
-
-            // Update the user record with a default name on user's first visit.
-            this._userRef.transaction(function(current) {
-                if (!current || !current.id || !current.name) {
-                    return {
-                        id: self._userId,
-                        name: self._userName
-                    };
-                }
-            }, function(error, committed, snapshot) {
-                self._user = snapshot.val();
-                self._moderatorsRef.child(self._userId).once('value', function(snapshot) {
-                    self._isModerator = !!snapshot.val();
-                    root.setTimeout(onComplete, 0);
-                });
-            });
-        },
-
-        // Initialize Firebase listeners and callbacks for the supported bindings.
-        _setupDataEvents: function() {
-            // Monitor connection state so we can requeue disconnect operations if need be.
-            this._firebase.root().child('.info/connected').on('value', function(snapshot) {
-                if (snapshot.val() === true) {
-                    // We're connected (or reconnected)! Set up our presence state.
-                    for (var i = 0; i < this._presenceBits; i++) {
-                        var op = this._presenceBits[i],
-                            ref = this._firebase.root().child(op.ref);
-
-                        ref.onDisconnect().set(op.offlineValue);
-                        ref.set(op.onlineValue);
-                    }
-                }
-            }, this);
-
-            // Generate a unique session id for the visit.
-            var sessionRef = this._userRef.child('sessions').push();
-            this._sessionId = sessionRef.name();
-            this._queuePresenceOperation(sessionRef, true, null);
-
-            // Register our username in the public user listing.
-            var usernameRef = this._usersOnlineRef.child(this._userName.toLowerCase());
-            var usernameSessionRef = usernameRef.child(this._sessionId);
-            this._queuePresenceOperation(usernameSessionRef, {
-                id: this._userId,
-                name: this._userName
-            }, null);
-
-            // Listen for state changes for the given user.
-            this._userRef.on('value', this._onUpdateUser, this);
-
-            // Listen for chat invitations from other users.
-            this._userRef.child('invites').on('child_added', this._onFirechatInvite, this);
-
-            // Listen for messages from moderators and adminstrators.
-            this._userRef.child('notifications').on('child_added', this._onNotification, this);
-        },
-
-        // Append the new callback to our list of event handlers.
-        _addEventCallback: function(eventId, callback) {
-            this._events[eventId] = this._events[eventId] || [];
-            this._events[eventId].push(callback);
-        },
-
-        // Retrieve the list of event handlers for a given event id.
-        _getEventCallbacks: function(eventId) {
-            if (this._events.hasOwnProperty(eventId)) {
-                return this._events[eventId];
-            }
-            return [];
-        },
-
-        // Invoke each of the event handlers for a given event id with specified data.
-        _invokeEventCallbacks: function(eventId) {
-            var args = [],
-                callbacks = this._getEventCallbacks(eventId);
-
-            Array.prototype.push.apply(args, arguments);
-            args = args.slice(1);
-
-            for (var i = 0; i < callbacks.length; i += 1) {
-                callbacks[i].apply(null, args);
-            }
-        },
-
-        // Keep track of on-disconnect events so they can be requeued if we disconnect the reconnect.
-        _queuePresenceOperation: function(ref, onlineValue, offlineValue) {
-            ref.onDisconnect().set(offlineValue);
-            ref.set(onlineValue);
-            this._presenceBits[ref.toString()] = {
-                ref: ref,
-                onlineValue: onlineValue,
-                offlineValue: offlineValue
-            };
-        },
-
-        // Remove an on-disconnect event from firing upon future disconnect and reconnect.
-        _removePresenceOperation: function(path, value) {
-            var ref = new Firebase(path);
-            ref.onDisconnect().cancel();
-            ref.set(value);
-            delete this._presenceBits[path];
-        },
-
-        // Event to monitor current user state.
-        _onUpdateUser: function(snapshot) {
-            this._user = snapshot.val();
-            this._invokeEventCallbacks('user-update', this._user);
-        },
-
-        // Event to monitor current auth + user state.
-        _onAuthRequired: function() {
-            this._invokeEventCallbacks('auth-required');
-        },
-
-        // Events to monitor room entry / exit and messages additional / removal.
-        _onEnterRoom: function(room) {
-            this._invokeEventCallbacks('room-enter', room);
-        },
-        _onNewMessage: function(roomId, snapshot) {
-            var message = snapshot.val();
-            message.id = snapshot.name();
-            this._invokeEventCallbacks('message-add', roomId, message);
-        },
-        _onRemoveMessage: function(roomId, snapshot) {
-            var messageId = snapshot.name();
-            this._invokeEventCallbacks('message-remove', roomId, messageId);
-        },
-        _onLeaveRoom: function(roomId) {
-            this._invokeEventCallbacks('room-exit', roomId);
-        },
-
-        // Event to listen for notifications from administrators and moderators.
-        _onNotification: function(snapshot) {
-            var notification = snapshot.val();
-            if (!notification.read) {
-                if (notification.notificationType !== 'suspension' || notification.data.suspendedUntil < new Date().getTime()) {
-                    snapshot.ref().child('read').set(true);
-                }
-                this._invokeEventCallbacks('notification', notification);
-            }
-        },
-
-        // Events to monitor chat invitations and invitation replies.
-        _onFirechatInvite: function(snapshot) {
-            var self = this,
-                invite = snapshot.val();
-
-            // Skip invites we've already responded to.
-            if (invite.status) {
-                return;
-            }
-
-            invite.id = invite.id || snapshot.name();
-            self.getRoom(invite.roomId, function(room) {
-                invite.toRoomName = room.name;
-                self._invokeEventCallbacks('room-invite', invite);
-            });
-        },
-        _onFirechatInviteResponse: function(snapshot) {
-            var self = this,
-                invite = snapshot.val();
-
-            invite.id = invite.id || snapshot.name();
-            this._invokeEventCallbacks('room-invite-response', invite);
-        }
-    };
-
-    // Firechat External Methods
-    // --------------
-
-    // Initialize the library and setup data listeners.
-    Firechat.prototype.setUser = function(userId, userName, callback) {
-        var self = this;
-
-        self._firebase.root().child('.info/authenticated').on('value', function(snapshot) {
-            var authenticated = snapshot.val();
-            if (authenticated) {
-                self._firebase.root().child('.info/authenticated').off();
-
-                self._userId = userId.toString();
-                self._userName = userName.toString();
-                self._userRef = self._firebase.child('users').child(self._userId);
-                self._loadUserMetadata(function() {
-                    root.setTimeout(function() {
-                        callback(self._user);
-                        self._setupDataEvents();
-                    }, 0);
-                });
-            } else {
-                self.warn('Firechat requires an authenticated Firebase reference. Pass an authenticated reference before loading.');
-            }
-        });
-    };
-
-    // Resumes the previous session by automatically entering rooms.
-    Firechat.prototype.resumeSession = function() {
-        this._userRef.child('rooms').once('value', function(snapshot) {
-            var rooms = snapshot.val();
-            for (var roomId in rooms) {
-                this.enterRoom(rooms[roomId].id);
-            }
-        }, /* onError */ function(){}, /* context */ this);
-    };
-
-    // Callback registration. Supports each of the following events:
-    Firechat.prototype.on = function(eventType, cb) {
-        this._addEventCallback(eventType, cb);
-    };
-
-    // Create and automatically enter a new chat room.
-    Firechat.prototype.createRoom = function(roomName, roomType, callback) {
-        var self = this,
-            newRoomRef = this._roomRef.push();
-
-        var newRoom = {
-            id: newRoomRef.name(),
-            name: roomName,
-            type: roomType || 'public',
-            createdByUserId: this._userId,
-            createdAt: Firebase.ServerValue.TIMESTAMP
-        };
-
-        if (roomType === 'private') {
-            newRoom.authorizedUsers = {};
-            newRoom.authorizedUsers[this._userId] = true;
-        }
-
-        newRoomRef.set(newRoom, function(error) {
-            if (!error) {
-                self.enterRoom(newRoomRef.name());
-            }
-            if (callback) {
-                callback(newRoomRef.name());
-            }
-        });
-    };
-
-    // Enter a chat room.
-    Firechat.prototype.enterRoom = function(roomId) {
-        var self = this;
-        self.getRoom(roomId, function(room) {
-            var roomName = room.name;
-
-            if (!roomId || !roomName) return;
-
-            // Skip if we're already in this room.
-            if (self._rooms[roomId]) {
-                return;
-            }
-
-            self._rooms[roomId] = true;
-
-            if (self._user) {
-                // Save entering this room to resume the session again later.
-                self._userRef.child('rooms').child(roomId).set({
-                    id: roomId,
-                    name: roomName,
-                    active: true
-                });
-
-                // Set presence bit for the room and queue it for removal on disconnect.
-                var presenceRef = self._firebase.child('room-users').child(roomId).child(self._userId).child(self._sessionId);
-                self._queuePresenceOperation(presenceRef, {
-                    id: self._userId,
-                    name: self._userName
-                }, null);
-            }
-
-            // Invoke our callbacks before we start listening for new messages.
-            self._onEnterRoom({ id: roomId, name: roomName });
-
-            // Setup message listeners
-            self._roomRef.child(roomId).once('value', function(snapshot) {
-                self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_added', function(snapshot) {
-                    self._onNewMessage(roomId, snapshot);
-                }, /* onCancel */ function() {
-                    // Turns out we don't have permission to access these messages.
-                    self.leaveRoom(roomId);
-                }, /* context */ self);
-
-                self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_removed', function(snapshot) {
-                    self._onRemoveMessage(roomId, snapshot);
-                }, /* onCancel */ function(){}, /* context */ self);
-            }, /* onFailure */ function(){}, self);
-        });
-    };
-
-    // Leave a chat room.
-    Firechat.prototype.leaveRoom = function(roomId) {
-        var self = this,
-            userRoomRef = self._firebase.child('room-users').child(roomId);
-
-        // Remove listener for new messages to this room.
-        self._messageRef.child(roomId).off();
-
-        if (self._user) {
-            var presenceRef = userRoomRef.child(self._userId).child(self._sessionId);
-
-            // Remove presence bit for the room and cancel on-disconnect removal.
-            self._removePresenceOperation(presenceRef.toString(), null);
-
-            // Remove session bit for the room.
-            self._userRef.child('rooms').child(roomId).remove();
-        }
-
-        delete self._rooms[roomId];
-
-        // Invoke event callbacks for the room-exit event.
-        self._onLeaveRoom(roomId);
-    };
-
-    Firechat.prototype.sendMessage = function(roomId, messageContent, messageType, cb) {
-        var self = this,
-            message = {
-                userId: self._userId,
-                name: self._userName,
-                timestamp: Firebase.ServerValue.TIMESTAMP,
-                message: messageContent,
-                type: messageType || 'default'
-            },
-            newMessageRef;
-
-        if (!self._user) {
-            self._onAuthRequired();
-            if (cb) {
-                cb(new Error('Not authenticated or user not set!'));
-            }
-            return;
-        }
-
-        newMessageRef = self._messageRef.child(roomId).push();
-        newMessageRef.setWithPriority(message, Firebase.ServerValue.TIMESTAMP, cb);
-    };
-
-    Firechat.prototype.deleteMessage = function(roomId, messageId, cb) {
-        var self = this;
-
-        self._messageRef.child(roomId).child(messageId).remove(cb);
-    };
-
-    // Mute or unmute a given user by id. This list will be stored internally and
-    // all messages from the muted clients will be filtered client-side after
-    // receipt of each new message.
-    Firechat.prototype.toggleUserMute = function(userId, cb) {
-        var self = this;
-
-        if (!self._user) {
-            self._onAuthRequired();
-            if (cb) {
-                cb(new Error('Not authenticated or user not set!'));
-            }
-            return;
-        }
-
-        self._userRef.child('muted').child(userId).transaction(function(isMuted) {
-            return (isMuted) ? null : true;
-        }, cb);
-    };
-
-    // Send a moderator notification to a specific user.
-    Firechat.prototype.sendSuperuserNotification = function(userId, notificationType, data, cb) {
-        var self = this,
-            userNotificationsRef = self._firebase.child('users').child(userId).child('notifications');
-
-        userNotificationsRef.push({
-            fromUserId: self._userId,
-            timestamp: Firebase.ServerValue.TIMESTAMP,
-            notificationType: notificationType,
-            data: data || {}
-        }, cb);
-    };
-
-    // Warn a user for violating the terms of service or being abusive.
-    Firechat.prototype.warnUser = function(userId) {
-        var self = this;
-
-        self.sendSuperuserNotification(userId, 'warning');
-    };
-
-    // Suspend a user by putting the user into read-only mode for a period.
-    Firechat.prototype.suspendUser = function(userId, timeLengthSeconds, cb) {
-        var self = this,
-            suspendedUntil = new Date().getTime() + 1000*timeLengthSeconds;
-
-        self._suspensionsRef.child(userId).set(suspendedUntil, function(error) {
-            if (error && cb) {
-                return cb(error);
-            } else {
-                self.sendSuperuserNotification(userId, 'suspension', {
-                    suspendedUntil: suspendedUntil
-                });
-                return cb(null);
-            }
-        });
-    };
-
-    // Invite a user to a specific chat room.
-    Firechat.prototype.inviteUser = function(userId, roomId) {
-        var self = this,
-            sendInvite = function() {
-                var inviteRef = self._firebase.child('users').child(userId).child('invites').push();
-                inviteRef.set({
-                    id: inviteRef.name(),
-                    fromUserId: self._userId,
-                    fromUserName: self._userName,
-                    roomId: roomId
-                });
-
-                // Handle listen unauth / failure in case we're kicked.
-                inviteRef.on('value', self._onFirechatInviteResponse, function(){}, self);
-            };
-
-        if (!self._user) {
-            self._onAuthRequired();
-            return;
-        }
-
-        self.getRoom(roomId, function(room) {
-            if (room.type === 'private') {
-                var authorizedUserRef = self._roomRef.child(roomId).child('authorizedUsers');
-                authorizedUserRef.child(userId).set(true, function(error) {
-                    if (!error) {
-                        sendInvite();
-                    }
-                });
-            } else {
-                sendInvite();
-            }
-        });
-    };
-
-    Firechat.prototype.acceptInvite = function(inviteId, cb) {
-        var self = this;
-
-        self._userRef.child('invites').child(inviteId).once('value', function(snapshot) {
-            var invite = snapshot.val();
-            if (invite === null && cb) {
-                return cb(new Error('acceptInvite(' + inviteId + '): invalid invite id'));
-            } else {
-                self.enterRoom(invite.roomId);
-                self._userRef.child('invites').child(inviteId).update({
-                    'status': 'accepted',
-                    'toUserName': self._userName
-                }, cb);
-            }
-        }, self);
-    };
-
-    Firechat.prototype.declineInvite = function(inviteId, cb) {
-        var self = this,
-            updates = {
-                'status': 'declined',
-                'toUserName': self._userName
-            };
-
-        self._userRef.child('invites').child(inviteId).update(updates, cb);
-    };
-
-    Firechat.prototype.getRoomList = function(cb) {
-        var self = this;
-
-        self._roomRef.once('value', function(snapshot) {
-            cb(snapshot.val());
-        });
-    };
-
-    Firechat.prototype.getUsersByRoom = function() {
-        var self = this,
-            roomId = arguments[0],
-            query = self._firebase.child('room-users').child(roomId),
-            cb = arguments[arguments.length - 1],
-            limit = null;
-
-        if (arguments.length > 2) {
-            limit = arguments[1];
-        }
-
-        query = (limit) ? query.limit(limit) : query;
-
-        query.once('value', function(snapshot) {
-            var usernames = snapshot.val() || {},
-                usernamesUnique = {};
-
-            for (var username in usernames) {
-                for (var session in usernames[username]) {
-                    // Skip all other sessions for this user as we only need one.
-                    usernamesUnique[username] = usernames[username][session];
-                    break;
-                }
-            }
-
-            root.setTimeout(function() {
-                cb(usernamesUnique);
-            }, 0);
-        });
-    };
-
-    Firechat.prototype.getUsersByPrefix = function(prefix, startAt, endAt, limit, cb) {
-        var self = this,
-            query = this._usersOnlineRef,
-            prefixLower = prefix.toLowerCase();
-
-        if (startAt) {
-            query = query.startAt(null, startAt);
-        } else if (endAt) {
-            query = query.endAt(null, endAt);
-        } else {
-            query = (prefixLower) ? query.startAt(null, prefixLower) : query.startAt();
-        }
-
-        query = (limit) ? query.limit(limit) : query;
-
-        query.once('value', function(snapshot) {
-            var usernames = snapshot.val() || {},
-                usernamesFiltered = {};
-
-            for (var userNameKey in usernames) {
-                var sessions = usernames[userNameKey],
-                    userName, userId, usernameClean;
-
-                // Grab the user data from the first registered active session.
-                for (var sessionId in sessions) {
-                    userName = sessions[sessionId].name;
-                    userId = sessions[sessionId].id;
-
-                    // Skip all other sessions for this user as we only need one.
-                    break;
-                }
-
-                // Filter out any usernames that don't match our prefix and break.
-                if ((prefix.length > 0) && (userName.toLowerCase().indexOf(prefixLower) !== 0))
-                    continue;
-
-                usernamesFiltered[userName] = {
-                    name: userName,
-                    id: userId
-                };
-            }
-
-            root.setTimeout(function() {
-                cb(usernamesFiltered);
-            }, 0);
-        });
-    };
-
-    // Miscellaneous helper methods.
-    Firechat.prototype.getRoom = function(roomId, callback) {
-        this._roomRef.child(roomId).once('value', function(snapshot) {
-            callback(snapshot.val());
-        });
-    };
-
-    Firechat.prototype.userIsModerator = function() {
-        return this._isModerator;
-    };
-
-    Firechat.prototype.warn = function(msg) {
-        if (console) {
-            msg = 'Firechat Warning: ' + msg;
-            if (typeof console.warn === 'function') {
-                console.warn(msg);
-            } else if (typeof console.log === 'function') {
-                console.log(msg);
-            }
-        }
-    };
-})(Firebase);
-(function($) {
-
-
-    if (!$ || (parseInt($().jquery.replace(/\./g, ""), 10) < 170)) {
-        throw new Error("jQuery 1.7 or later required!");
-    }
-
-    var root = this,
-        previousFirechatUI = root.FirechatUI;
-
-    root.FirechatUI = FirechatUI;
-
-    if (!self.FirechatDefaultTemplates) {
-        throw new Error("Unable to find chat templates!");
-    }
-
-    function FirechatUI(firebaseRef, el, options) {
-        var self = this;
-
-        if (!firebaseRef) {
-            throw new Error('FirechatUI: Missing required argument `firebaseRef`');
-        }
-
-        if (!el) {
-            throw new Error('FirechatUI: Missing required argument `el`');
-        }
-
-        options = options || {};
-        this._options = options;
-
-        this._el = el;
-        this._user = null;
-        this._chat = new Firechat(firebaseRef, options);
-
-        // A list of rooms to enter once we've made room for them (once we've hit the max room limit).
-        this._roomQueue = [];
-
-        // Define some constants regarding maximum lengths, client-enforced.
-        this.maxLengthUsername = 15;
-        this.maxLengthUsernameDisplay = 15;
-        this.maxLengthRoomName = 24;
-        this.maxLengthMessage = 120;
-        this.maxUserSearchResults = 100;
-
-        // Define some useful regexes.
-        this.urlPattern = /\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim;
-        this.pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
-
-        this._renderLayout();
-
-        // Grab shortcuts to commonly used jQuery elements.
-        this.$wrapper = $('#firechat');
-        this.$roomList = $('#firechat-room-list');
-        this.$tabList = $('#firechat-tab-list');
-        this.$tabContent = $('#firechat-tab-content');
-        this.$messages = {};
-
-        // Rate limit messages from a given user with some defaults.
-        this.$rateLimit = {
-            limitCount: 10,         // max number of events
-            limitInterval: 10000,   // max interval for above count in milliseconds
-            limitWaitTime: 30000,   // wait time if a user hits the wait limit
-            history: {}
-        };
-
-        // Setup UI bindings for chat controls.
-        this._bindUIEvents();
-
-        // Setup bindings to internal methods
-        this._bindDataEvents();
-    }
-
-    // Run FirechatUI in *noConflict* mode, returning the `FirechatUI` variable to
-    // its previous owner, and returning a reference to the FirechatUI object.
-    FirechatUI.noConflict = function noConflict() {
-        root.FirechatUI = previousFirechatUI;
-        return FirechatUI;
-    };
-
-    FirechatUI.prototype = {
-
-        _bindUIEvents: function() {
-            // Chat-specific custom interactions and functionality.
-            this._bindForHeightChange();
-            this._bindForTabControls();
-            this._bindForRoomList();
-            this._bindForUserRoomList();
-            this._bindForUserSearch();
-            this._bindForUserMuting();
-            this._bindForChatInvites();
-            this._bindForRoomListing();
-
-            // Generic, non-chat-specific interactive elements.
-            this._setupTabs();
-            this._setupDropdowns();
-            this._bindTextInputFieldLimits();
-        },
-
-        _bindDataEvents: function() {
-            this._chat.on('user-update', this._onUpdateUser.bind(this));
-
-            // Bind events for new messages, enter / leaving rooms, and user metadata.
-            this._chat.on('room-enter', this._onEnterRoom.bind(this));
-            this._chat.on('room-exit', this._onLeaveRoom.bind(this));
-            this._chat.on('message-add', this._onNewMessage.bind(this));
-            this._chat.on('message-remove', this._onRemoveMessage.bind(this));
-
-            // Bind events related to chat invitations.
-            this._chat.on('room-invite', this._onChatInvite.bind(this));
-            this._chat.on('room-invite-response', this._onChatInviteResponse.bind(this));
-
-            // Binds events related to admin or moderator notifications.
-            this._chat.on('notification', this._onNotification.bind(this));
-        },
-
-        _renderLayout: function() {
-            var template = FirechatDefaultTemplates["templates/layout-full.html"];
-            $(this._el).html(template({
-                maxLengthUsername: this.maxLengthUsername
-            }));
-        },
-
-        _onUpdateUser: function(user) {
-            // Update our current user state and render latest user name.
-            this._user = user;
-
-            // Update our interface to reflect which users are muted or not.
-            var mutedUsers = this._user.muted || {};
-            $('[data-event="firechat-user-mute-toggle"]').each(function(i, el) {
-                var userId = $(this).closest('[data-user-id]').data('user-id');
-                $(this).toggleClass('red', !!mutedUsers[userId]);
-            });
-
-            // Ensure that all messages from muted users are removed.
-            for (var userId in mutedUsers) {
-                $('.message[data-user-id="' + userId + '"]').fadeOut();
-            }
-        },
-
-        _onEnterRoom: function(room) {
-            this.attachTab(room.id, room.name);
-        },
-        _onLeaveRoom: function(roomId) {
-            this.removeTab(roomId);
-
-            // Auto-enter rooms in the queue
-            if ((this._roomQueue.length > 0)) {
-                this._chat.enterRoom(this._roomQueue.shift(roomId));
-            }
-        },
-        _onNewMessage: function(roomId, message) {
-            var userId = message.userId;
-            if (!this._user || !this._user.muted || !this._user.muted[userId]) {
-                this.showMessage(roomId, message);
-            }
-        },
-        _onRemoveMessage: function(roomId, messageId) {
-            this.removeMessage(roomId, messageId);
-        },
-
-        // Events related to chat invitations.
-        _onChatInvite: function(invitation) {
-            var self = this;
-            var template = FirechatDefaultTemplates["templates/prompt-invitation.html"];
-            var $prompt = this.prompt('Invite', template(invitation));
-            $prompt.find('a.close').click(function() {
-                $prompt.remove();
-                self._chat.declineInvite(invitation.id);
-                return false;
-            });
-
-            $prompt.find('[data-toggle=accept]').click(function() {
-                $prompt.remove();
-                self._chat.acceptInvite(invitation.id);
-                return false;
-            });
-
-            $prompt.find('[data-toggle=decline]').click(function() {
-                $prompt.remove();
-                self._chat.declineInvite(invitation.id);
-                return false;
-            });
-        },
-        _onChatInviteResponse: function(invitation) {
-            if (!invitation.status) return;
-
-            var self = this,
-                template = FirechatDefaultTemplates["templates/prompt-invite-reply.html"],
-                $prompt;
-
-            if (invitation.status && invitation.status === 'accepted') {
-                $prompt = this.prompt('Accepted', template(invitation));
-                this._chat.getRoom(invitation.roomId, function(room) {
-                    self.attachTab(invitation.roomId, room.name);
-                });
-            } else {
-                $prompt = this.prompt('Declined', template(invitation));
-            }
-
-            $prompt.find('a.close').click(function() {
-                $prompt.remove();
-                return false;
-            });
-        },
-
-        // Events related to admin or moderator notifications.
-        _onNotification: function(notification) {
-            if (notification.notificationType === 'warning') {
-                this.renderAlertPrompt('Warning', 'You are being warned for inappropriate messaging. Further violation may result in temporary or permanent ban of service.');
-            } else if (notification.notificationType === 'suspension') {
-                var suspendedUntil = notification.data.suspendedUntil,
-                    secondsLeft = Math.round((suspendedUntil - new Date().getTime()) / 1000),
-                    timeLeft = '';
-
-                if (secondsLeft > 0) {
-                    if (secondsLeft > 2*3600) {
-                        var hours = Math.floor(secondsLeft / 3600);
-                        timeLeft = hours + ' hours, ';
-                        secondsLeft -= 3600*hours;
-                    }
-                    timeLeft += Math.floor(secondsLeft / 60) + ' minutes';
-                    this.renderAlertPrompt('Suspended', 'A moderator has suspended you for violating site rules. You cannot send messages for another ' + timeLeft + '.');
-                }
-            }
-        }
-    };
-
-    /**
-     * Initialize an authenticated session with a user id and name.
-     * This method assumes that the underlying Firebase reference has
-     * already been authenticated.
-     */
-    FirechatUI.prototype.setUser = function(userId, userName) {
-        var self = this;
-
-        // Initialize data events
-        self._chat.setUser(userId, userName, function(user) {
-            self._user = user;
-
-            if (self._chat.userIsModerator()) {
-                self._bindSuperuserUIEvents();
-            }
-
-            self._chat.resumeSession();
-        });
-    };
-
-    /**
-     * Exposes internal chat bindings via this external interface.
-     */
-    FirechatUI.prototype.on = function(eventType, cb) {
-        var self = this;
-
-        this._chat.on(eventType, cb);
-    };
-
-    /**
-     * Binds a custom context menu to messages for superusers to warn or ban
-     * users for violating terms of service.
-     */
-    FirechatUI.prototype._bindSuperuserUIEvents = function() {
-        var self = this,
-            parseMessageVars = function(event) {
-                var $this = $(this),
-                    messageId = $this.closest('[data-message-id]').data('message-id'),
-                    userId = $('[data-message-id="' + messageId + '"]').closest('[data-user-id]').data('user-id'),
-                    roomId = $('[data-message-id="' + messageId + '"]').closest('[data-room-id]').data('room-id');
-
-                return { messageId: messageId, userId: userId, roomId: roomId };
-            },
-            clearMessageContextMenus = function() {
-                // Remove any context menus currently showing.
-                $('[data-toggle="firechat-contextmenu"]').each(function() {
-                    $(this).remove();
-                });
-
-                // Remove any messages currently highlighted.
-                $('#firechat .message.highlighted').each(function() {
-                    $(this).removeClass('highlighted');
-                });
-            },
-            showMessageContextMenu = function(event) {
-                var $this = $(this),
-                    $message = $this.closest('[data-message-id]'),
-                    template = FirechatDefaultTemplates["templates/message-context-menu.html"],
-                    messageVars = parseMessageVars.call(this, event),
-                    $template;
-
-                event.preventDefault();
-
-                // Clear existing menus.
-                clearMessageContextMenus();
-
-                // Highlight the relevant message.
-                $this.addClass('highlighted');
-
-                self._chat.getRoom(messageVars.roomId, function(room) {
-                    // Show the context menu.
-                    $template = $(template({
-                        id: $message.data('message-id')
-                    }));
-                    $template.css({
-                        left: event.clientX,
-                        top: event.clientY
-                    }).appendTo(self.$wrapper);
-                });
-            };
-
-        // Handle dismissal of message context menus (any non-right-click click event).
-        $(document).bind('click', { self: this }, function(event) {
-            if (!event.button || event.button != 2) {
-                clearMessageContextMenus();
-            }
-        });
-
-        // Handle display of message context menus (via right-click on a message).
-        $(document).delegate('[data-class="firechat-message"]', 'contextmenu', showMessageContextMenu);
-
-        // Handle click of the 'Warn User' contextmenu item.
-        $(document).delegate('[data-event="firechat-user-warn"]', 'click', function(event) {
-            var messageVars = parseMessageVars.call(this, event);
-            self._chat.warnUser(messageVars.userId);
-        });
-
-        // Handle click of the 'Suspend User (1 Hour)' contextmenu item.
-        $(document).delegate('[data-event="firechat-user-suspend-hour"]', 'click', function(event) {
-            var messageVars = parseMessageVars.call(this, event);
-            self._chat.suspendUser(messageVars.userId, /* 1 Hour = 3600s */ 60*60);
-        });
-
-        // Handle click of the 'Suspend User (1 Day)' contextmenu item.
-        $(document).delegate('[data-event="firechat-user-suspend-day"]', 'click', function(event) {
-            var messageVars = parseMessageVars.call(this, event);
-            self._chat.suspendUser(messageVars.userId, /* 1 Day = 86400s */ 24*60*60);
-        });
-
-        // Handle click of the 'Delete Message' contextmenu item.
-        $(document).delegate('[data-event="firechat-message-delete"]', 'click', function(event) {
-            var messageVars = parseMessageVars.call(this, event);
-            self._chat.deleteMessage(messageVars.roomId, messageVars.messageId);
-        });
-    };
-
-    /**
-     * Binds to height changes in the surrounding div.
-     */
-    FirechatUI.prototype._bindForHeightChange = function() {
-        var self = this,
-            $el = $(this._el),
-            lastHeight = null;
-
-        setInterval(function() {
-            var height = $el.height();
-            if (height != lastHeight) {
-                lastHeight = height;
-                $('.chat').each(function(i, el) {
-
-                });
-            }
-        }, 500);
-    };
-
-    /**
-     * Binds custom inner-tab events.
-     */
-    FirechatUI.prototype._bindForTabControls = function() {
-        var self = this;
-
-        // Handle click of tab close button.
-        $(document).delegate('[data-event="firechat-close-tab"]', 'click', function(event) {
-            var roomId = $(this).closest('[data-room-id]').data('room-id');
-            self._chat.leaveRoom(roomId);
-            return false;
-        });
-    };
-
-    /**
-     * Binds room list dropdown to populate room list on-demand.
-     */
-    FirechatUI.prototype._bindForRoomList = function() {
-        var self = this;
-
-        $('#firechat-btn-rooms').bind('click', function() {
-            if ($(this).parent().hasClass('open')) {
-                return;
-            }
-
-            var $this = $(this),
-                template = FirechatDefaultTemplates["templates/room-list-item.html"],
-                selectRoomListItem = function() {
-                    var parent = $(this).parent(),
-                        roomId = parent.data('room-id'),
-                        roomName = parent.data('room-name');
-
-                    if (self.$messages[roomId]) {
-                        self.focusTab(roomId);
-                    } else {
-                        self._chat.enterRoom(roomId, roomName);
-                    }
-                    return false;
-                };
-
-            self._chat.getRoomList(function(rooms) {
-                self.$roomList.empty();
-                for (var roomId in rooms) {
-                    var room = rooms[roomId];
-                    if (room.type != "public") continue;
-                    room.isRoomOpen = !!self.$messages[room.id];
-                    var $roomItem = $(template(room));
-                    $roomItem.children('a').bind('click', selectRoomListItem);
-                    self.$roomList.append($roomItem.toggle(true));
-                }
-            });
-        });
-    };
-
-    /**
-     * Binds user list dropdown per room to populate user list on-demand.
-     */
-    FirechatUI.prototype._bindForUserRoomList = function() {
-        var self = this;
-
-        // Upon click of the dropdown, autofocus the input field and trigger list population.
-        $(document).delegate('[data-event="firechat-user-room-list-btn"]', 'click', function(event) {
-            event.stopPropagation();
-
-            var $this = $(this),
-                roomId = $this.closest('[data-room-id]').data('room-id'),
-                template = FirechatDefaultTemplates["templates/room-user-list-item.html"],
-                targetId = $this.data('target'),
-                $target = $('#' + targetId);
-
-            $target.empty();
-            self._chat.getUsersByRoom(roomId, function(users) {
-                for (var username in users) {
-                    user = users[username];
-                    user.disableActions = (!self._user || user.id === self._user.id);
-                    user.nameTrimmed = self.trimWithEllipsis(user.name, self.maxLengthUsernameDisplay);
-                    user.isMuted = (self._user && self._user.muted && self._user.muted[user.id]);
-                    $target.append($(template(user)));
-                }
-                self.sortListLexicographically('#' + targetId);
-            });
-        });
-    };
-
-    /**
-     * Binds user search buttons, dropdowns, and input fields for searching all
-     * active users currently in chat.
-     */
-    FirechatUI.prototype._bindForUserSearch = function() {
-        var self = this,
-            handleUserSearchSubmit = function(event) {
-                var $this = $(this),
-                    targetId = $this.data('target'),
-                    controlsId = $this.data('controls'),
-                    templateId = $this.data('template'),
-                    prefix = $this.val() || $this.data('prefix') || '',
-                    startAt = $this.data('startAt') || null,
-                    endAt = $this.data('endAt') || null;
-
-                event.preventDefault();
-
-                userSearch(targetId, templateId, controlsId, prefix, startAt, endAt);
-            },
-            userSearch = function(targetId, templateId, controlsId, prefix, startAt, endAt) {
-                var $target = $('#' + targetId),
-                    $controls = $('#' + controlsId),
-                    template = FirechatDefaultTemplates[templateId];
-
-                // Query results, filtered by prefix, using the defined startAt and endAt markets.
-                self._chat.getUsersByPrefix(prefix, startAt, endAt, self.maxUserSearchResults, function(users) {
-                    var numResults = 0,
-                        $prevBtn, $nextBtn, username, firstResult, lastResult;
-
-                    $target.empty();
-
-                    for (username in users) {
-                        var user = users[username];
-
-                        // Disable buttons for <me>.
-                        user.disableActions = (!self._user || user.id === self._user.id);
-
-                        numResults += 1;
-
-                        $target.append(template(user));
-
-                        // If we've hit our result limit, the additional value signifies we should paginate.
-                        if (numResults === 1) {
-                            firstResult = user.name.toLowerCase();
-                        } else if (numResults >= self.maxUserSearchResults) {
-                            lastResult = user.name.toLowerCase();
-                            break;
-                        }
-                    }
-
-                    if ($controls) {
-                        $prevBtn = $controls.find('[data-toggle="firechat-pagination-prev"]');
-                        $nextBtn = $controls.find('[data-toggle="firechat-pagination-next"]');
-
-                        // Sort out configuration for the 'next' button
-                        if (lastResult) {
-                            $nextBtn
-                                .data('event', 'firechat-user-search')
-                                .data('startAt', lastResult)
-                                .data('prefix', prefix)
-                                .removeClass('disabled').removeAttr('disabled');
-                        } else {
-                            $nextBtn
-                                .data('event', null)
-                                .data('startAt', null)
-                                .data('prefix', null)
-                                .addClass('disabled').attr('disabled', 'disabled');
-                        }
-                    }
-                });
-            };
-
-        $(document).delegate('[data-event="firechat-user-search"]', 'keyup', handleUserSearchSubmit);
-        $(document).delegate('[data-event="firechat-user-search"]', 'click', handleUserSearchSubmit);
-
-        // Upon click of the dropdown, autofocus the input field and trigger list population.
-        $(document).delegate('[data-event="firechat-user-search-btn"]', 'click', function(event) {
-            event.stopPropagation();
-            var $input = $(this).next('div.firechat-dropdown-menu').find('input');
-            $input.focus();
-            $input.trigger(jQuery.Event('keyup'));
-        });
-
-        // Ensure that the dropdown stays open despite clicking on the input element.
-        $(document).delegate('[data-event="firechat-user-search"]', 'click', function(event) {
-            event.stopPropagation();
-        });
-    };
-
-    /**
-     * Binds user mute toggles and removes all messages for a given user upon mute.
-     */
-    FirechatUI.prototype._bindForUserMuting = function() {
-        var self = this;
-        $(document).delegate('[data-event="firechat-user-mute-toggle"]', 'click', function(event) {
-            var $this = $(this),
-                userId = $this.closest('[data-user-id]').data('user-id'),
-                userName = $this.closest('[data-user-name]').data('user-name'),
-                isMuted = $this.hasClass('red'),
-                template = FirechatDefaultTemplates["templates/prompt-user-mute.html"];
-
-            event.preventDefault();
-
-            // Require user confirmation for muting.
-            if (!isMuted) {
-                var $prompt = self.prompt('Mute User?', template({
-                    userName: userName
-                }));
-
-                $prompt.find('a.close').first().click(function() {
-                    $prompt.remove();
-                    return false;
-                });
-
-                $prompt.find('[data-toggle=decline]').first().click(function() {
-                    $prompt.remove();
-                    return false;
-                });
-
-                $prompt.find('[data-toggle=accept]').first().click(function() {
-                    self._chat.toggleUserMute(userId);
-                    $prompt.remove();
-                    return false;
-                });
-            } else {
-                self._chat.toggleUserMute(userId);
-            }
-        });
-    };
-
-    /**
-     * Binds to elements with the data-event='firechat-user-(private)-invite' and
-     * handles invitations as well as room creation and entering.
-     */
-    FirechatUI.prototype._bindForChatInvites = function() {
-        var self = this,
-            renderInvitePrompt = function(event) {
-                var $this = $(this),
-                    userId = $this.closest('[data-user-id]').data('user-id'),
-                    roomId = $this.closest('[data-room-id]').data('room-id'),
-                    userName = $this.closest('[data-user-name]').data('user-name'),
-                    template = FirechatDefaultTemplates["templates/prompt-invite-private.html"],
-                    $prompt;
-
-                self._chat.getRoom(roomId, function(room) {
-                    $prompt = self.prompt('Invite', template({
-                        userName: userName,
-                        roomName: room.name
-                    }));
-
-                    $prompt.find('a.close').click(function() {
-                        $prompt.remove();
-                        return false;
-                    });
-
-                    $prompt.find('[data-toggle=decline]').click(function() {
-                        $prompt.remove();
-                        return false;
-                    });
-
-                    $prompt.find('[data-toggle=accept]').first().click(function() {
-                        $prompt.remove();
-                        self._chat.inviteUser(userId, roomId, room.name);
-                        return false;
-                    });
-                    return false;
-                });
-                return false;
-            },
-            renderPrivateInvitePrompt = function(event) {
-                var $this = $(this),
-                    userId = $this.closest('[data-user-id]').data('user-id'),
-                    userName = $this.closest('[data-user-name]').data('user-name'),
-                    template = FirechatDefaultTemplates["templates/prompt-invite-private.html"],
-                    $prompt;
-
-                if (userId && userName) {
-                    $prompt = self.prompt('Private Invite', template({
-                        userName: userName,
-                        roomName: 'Private Chat'
-                    }));
-
-                    $prompt.find('a.close').click(function() {
-                        $prompt.remove();
-                        return false;
-                    });
-
-                    $prompt.find('[data-toggle=decline]').click(function() {
-                        $prompt.remove();
-                        return false;
-                    });
-
-                    $prompt.find('[data-toggle=accept]').first().click(function() {
-                        $prompt.remove();
-                        var roomName = 'Private Chat';
-                        self._chat.createRoom(roomName, 'private', function(roomId) {
-                            self._chat.inviteUser(userId, roomId, roomName);
-                        });
-                        return false;
-                    });
-                }
-                return false;
-            };
-
-        $(document).delegate('[data-event="firechat-user-chat"]', 'click', renderPrivateInvitePrompt);
-        $(document).delegate('[data-event="firechat-user-invite"]', 'click', renderInvitePrompt);
-    };
-
-    /**
-     * Binds to room dropdown button, menu items, and create room button.
-     */
-    FirechatUI.prototype._bindForRoomListing = function() {
-        var self = this,
-            $createRoomPromptButton = $('#firechat-btn-create-room-prompt'),
-            $createRoomButton = $('#firechat-btn-create-room'),
-            renderRoomList = function(event) {
-                var type = $(this).data('room-type');
-
-                self.sortListLexicographically('#firechat-room-list');
-            };
-
-        // Handle click of the create new room prompt-button.
-        $createRoomPromptButton.bind('click', function(event) {
-            self.promptCreateRoom();
-            return false;
-        });
-
-        // Handle click of the create new room button.
-        $createRoomButton.bind('click', function(event) {
-            var roomName = $('#firechat-input-room-name').val();
-            $('#firechat-prompt-create-room').remove();
-            self._chat.createRoom(roomName);
-            return false;
-        });
-    };
-
-    /**
-     * A stripped-down version of bootstrap-tab.js.
-     *
-     * Original bootstrap-tab.js Copyright 2012 Twitter, Inc.,licensed under the Apache v2.0
-     */
-    FirechatUI.prototype._setupTabs = function() {
-        var self = this,
-            show = function($el) {
-                var $this = $el,
-                    $ul = $this.closest('ul:not(.firechat-dropdown-menu)'),
-                    selector = $this.attr('data-target'),
-                    previous = $ul.find('.active:last a')[0],
-                    $target,
-                    e;
-
-                if (!selector) {
-                    selector = $this.attr('href');
-                    selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '');
-                }
-
-                if ($this.parent('li').hasClass('active')) return;
-
-                e = $.Event('show', { relatedTarget: previous });
-
-                $this.trigger(e);
-
-                if (e.isDefaultPrevented()) return;
-
-                $target = $(selector);
-
-                activate($this.parent('li'), $ul);
-                activate($target, $target.parent(), function () {
-                    $this.trigger({
-                        type: 'shown',
-                        relatedTarget: previous
-                    });
-                });
-            },
-            activate = function (element, container, callback) {
-                var $active = container.find('> .active'),
-                    transition = callback && $.support.transition && $active.hasClass('fade');
-
-                function next() {
-                    $active
-                        .removeClass('active')
-                        .find('> .firechat-dropdown-menu > .active')
-                        .removeClass('active');
-
-                    element.addClass('active');
-
-                    if (transition) {
-                        element.addClass('in');
-                    } else {
-                        element.removeClass('fade');
-                    }
-
-                    if (element.parent('.firechat-dropdown-menu')) {
-                        element.closest('li.firechat-dropdown').addClass('active');
-                    }
-
-                    if (callback) {
-                        callback();
-                    }
-                }
-
-                if (transition) {
-                    $active.one($.support.transition.end, next);
-                } else {
-                    next();
-                }
-
-                $active.removeClass('in');
-            };
-
-        $(document).delegate('[data-toggle="firechat-tab"]', 'click', function(event) {
-            event.preventDefault();
-            show($(this));
-        });
-    };
-
-    /**
-     * A stripped-down version of bootstrap-dropdown.js.
-     *
-     * Original bootstrap-dropdown.js Copyright 2012 Twitter, Inc., licensed under the Apache v2.0
-     */
-    FirechatUI.prototype._setupDropdowns = function() {
-        var self = this,
-            toggle = '[data-toggle=firechat-dropdown]',
-            toggleDropdown = function(event) {
-                var $this = $(this),
-                    $parent = getParent($this),
-                    isActive = $parent.hasClass('open');
-
-                if ($this.is('.disabled, :disabled')) return;
-
-                clearMenus();
-
-                if (!isActive) {
-                    $parent.toggleClass('open');
-                }
-
-                $this.focus();
-
-                return false;
-            },
-            clearMenus = function() {
-                $('[data-toggle=firechat-dropdown]').each(function() {
-                    getParent($(this)).removeClass('open');
-                });
-            },
-            getParent = function($this) {
-                var selector = $this.attr('data-target'),
-                    $parent;
-
-                if (!selector) {
-                    selector = $this.attr('href');
-                    selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
-                }
-
-                $parent = selector && $(selector);
-
-                if (!$parent || !$parent.length) $parent = $this.parent();
-
-                return $parent;
-            };
-
-        $(document)
-            .bind('click', clearMenus)
-            .delegate('.firechat-dropdown-menu', 'click', function(event) { event.stopPropagation(); })
-            .delegate('[data-toggle=firechat-dropdown]', 'click', toggleDropdown);
-    };
-
-    /**
-     * Binds to any text input fields with data-provide='limit' and
-     * data-counter='<selector>', and upon value change updates the selector
-     * content to reflect the number of characters remaining, as the 'maxlength'
-     * attribute less the current value length.
-     */
-    FirechatUI.prototype._bindTextInputFieldLimits = function() {
-        $('body').delegate('input[data-provide="limit"], textarea[data-provide="limit"]', 'keyup', function(event) {
-            var $this = $(this),
-                $target = $($this.data('counter')),
-                limit = $this.attr('maxlength'),
-                count = $this.val().length;
-
-            $target.html(Math.max(0, limit - count));
-        });
-    };
-
-    /**
-     * Given a title and message content, show an alert prompt to the user.
-     *
-     * @param    {string}    title
-     * @param    {string}    message
-     */
-    FirechatUI.prototype.renderAlertPrompt = function(title, message) {
-        var template = FirechatDefaultTemplates["templates/prompt-alert.html"],
-            $prompt = this.prompt(title, template({ message: message }));
-
-        $prompt.find('.close').click(function() {
-            $prompt.remove();
-            return false;
-        });
-        return;
-    };
-
-    /**
-     * Toggle input field s if we want limit / unlimit input fields.
-     */
-    FirechatUI.prototype.toggleInputs = function(isEnabled) {
-        $('#firechat-tab-content textarea').each(function() {
-            var $this = $(this);
-            if (isEnabled) {
-                $(this).val('');
-            } else {
-                $(this).val('You have exceeded the message limit, please wait before sending.');
-            }
-            $this.prop('disabled', !isEnabled);
-        });
-        $('#firechat-input-name').prop('disabled', !isEnabled);
-    };
-
-    /**
-     * Given a room id and name, attach the tab to the interface and setup events.
-     *
-     * @param    {string}    roomId
-     * @param    {string}    roomName
-     */
-    FirechatUI.prototype.attachTab = function(roomId, roomName) {
-        var self = this;
-
-        // If this tab already exists, give it focus.
-        if (this.$messages[roomId]) {
-            this.focusTab(roomId);
-            return;
-        }
-
-        var room = {
-            id: roomId,
-            name: roomName
-        };
-
-        // Populate and render the tab content template.
-        var tabTemplate = FirechatDefaultTemplates["templates/tab-content.html"];
-        var $tabContent = $(tabTemplate(room));
-        this.$tabContent.prepend($tabContent);
-        var $messages = $('#firechat-messages' + roomId);
-
-        // Keep a reference to the message listing for later use.
-        this.$messages[roomId] = $messages;
-
-        // Attach on-enter event to textarea.
-        var $textarea = $tabContent.find('textarea').first();
-        $textarea.bind('keydown', function(e) {
-            var message = self.trimWithEllipsis($textarea.val(), self.maxLengthMessage);
-            if ((e.which === 13) && (message !== '')) {
-                $textarea.val('');
-                self._chat.sendMessage(roomId, message);
-                return false;
-            }
-        });
-
-        // Populate and render the tab menu template.
-        var tabListTemplate = FirechatDefaultTemplates["templates/tab-menu-item.html"];
-        var $tab = $(tabListTemplate(room));
-        this.$tabList.prepend($tab);
-
-        // Attach on-shown event to move tab to front and scroll to bottom.
-        $tab.bind('shown', function(event) {
-            $messages.scrollTop($messages[0].scrollHeight);
-        });
-
-        // Dynamically update the width of each tab based upon the number open.
-        var tabs = this.$tabList.children('li');
-        var tabWidth = Math.floor($('#firechat-tab-list').width() / tabs.length);
-        this.$tabList.children('li').css('width', tabWidth);
-
-        // Update the room listing to reflect that we're now in the room.
-        this.$roomList.children('[data-room-id=' + roomId + ']').children('a').addClass('highlight');
-
-        // Sort each item in the user list alphabetically on click of the dropdown.
-        $('#firechat-btn-room-user-list-' + roomId).bind('click', function() {
-            self.sortListLexicographically('#firechat-room-user-list-' + roomId);
-            return false;
-        });
-
-        // Automatically select the new tab.
-        this.focusTab(roomId);
-    };
-
-    /**
-     * Given a room id, focus the given tab.
-     *
-     * @param    {string}    roomId
-     */
-    FirechatUI.prototype.focusTab = function(roomId) {
-        if (this.$messages[roomId]) {
-            var $tabLink = this.$tabList.find('[data-room-id=' + roomId + ']').find('a');
-            if ($tabLink.length) {
-                $tabLink.first().trigger('click');
-            }
-        }
-    };
-
-    /**
-     * Given a room id, remove the tab and all child elements from the interface.
-     *
-     * @param    {string}    roomId
-     */
-    FirechatUI.prototype.removeTab = function(roomId) {
-        delete this.$messages[roomId];
-
-        // Remove the inner tab content.
-        this.$tabContent.find('[data-room-id=' + roomId + ']').remove();
-
-        // Remove the tab from the navigation menu.
-        this.$tabList.find('[data-room-id=' + roomId + ']').remove();
-
-        // Dynamically update the width of each tab based upon the number open.
-        var tabs = this.$tabList.children('li');
-        var tabWidth = Math.floor($('#firechat-tab-list').width() / tabs.length);
-        this.$tabList.children('li').css('width', tabWidth);
-
-        // Automatically select the next tab if there is one.
-        this.$tabList.find('[data-toggle="firechat-tab"]').first().trigger('click');
-
-        // Update the room listing to reflect that we're now in the room.
-        this.$roomList.children('[data-room-id=' + roomId + ']').children('a').removeClass('highlight');
-    };
-
-    /**
-     * Render a new message in the specified chat room.
-     *
-     * @param    {string}    roomId
-     * @param    {string}    message
-     */
-    FirechatUI.prototype.showMessage = function(roomId, rawMessage) {
-        var self = this;
-
-        // Setup defaults
-        var message = {
-            id              : rawMessage.id,
-            localtime       : self.formatTime(rawMessage.timestamp),
-            message         : rawMessage.message || '',
-            userId          : rawMessage.userId,
-            name            : rawMessage.name,
-            type            : rawMessage.type || 'default',
-            isSelfMessage   : (self._user && rawMessage.userId == self._user.id),
-            disableActions  : (!self._user || rawMessage.userId == self._user.id)
-        };
-
-        // While other data is escaped in the Underscore.js templates, escape and
-        // process the message content here to add additional functionality (add links).
-        // Also trim the message length to some client-defined maximum.
-        var messageConstructed = '';
-        message.message = _.map(message.message.split(' '), function(token) {
-            if (self.urlPattern.test(token) || self.pseudoUrlPattern.test(token)) {
-                return self.linkify(encodeURI(token));
-            } else {
-                return _.escape(token);
-            }
-        }).join(' ');
-        message.message = self.trimWithEllipsis(message.message, self.maxLengthMessage);
-
-        // Populate and render the message template.
-        var template = FirechatDefaultTemplates["templates/message.html"];
-        var $message = $(template(message));
-        var $messages = self.$messages[roomId];
-        if ($messages) {
-
-            var scrollToBottom = false;
-            if ($messages.scrollTop() / ($messages[0].scrollHeight - $messages[0].offsetHeight) >= 0.95) {
-                // Pinned to bottom
-                scrollToBottom = true;
-            } else if ($messages[0].scrollHeight <= $messages.height()) {
-                // Haven't added the scrollbar yet
-                scrollToBottom = true;
-            }
-
-            $messages.append($message);
-
-            if (scrollToBottom) {
-                $messages.scrollTop($messages[0].scrollHeight);
-            }
-        }
-    };
-
-    /**
-     * Remove a message by id.
-     *
-     * @param    {string}    roomId
-     * @param    {string}    messageId
-     */
-    FirechatUI.prototype.removeMessage = function(roomId, messageId) {
-        $('.message[data-message-id="' + messageId + '"]').remove();
-    };
-
-    /**
-     * Given a selector for a list element, sort the items alphabetically.
-     *
-     * @param    {string}    selector
-     */
-    FirechatUI.prototype.sortListLexicographically = function(selector) {
-        $(selector).children("li").sort(function(a, b) {
-            var upA = $(a).text().toUpperCase();
-            var upB = $(b).text().toUpperCase();
-            return (upA < upB) ? -1 : (upA > upB) ? 1 : 0;
-        }).appendTo(selector);
-    };
-
-    /**
-     * Remove leading and trailing whitespace from a string and shrink it, with
-     * added ellipsis, if it exceeds a specified length.
-     *
-     * @param    {string}    str
-     * @param    {number}    length
-     * @return   {string}
-     */
-    FirechatUI.prototype.trimWithEllipsis = function(str, length) {
-        str = str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
-        return (length && str.length <= length) ? str : str.substring(0, length) + '...';
-    };
-
-    /**
-     * Given a timestamp, format it in the form hh:mm am/pm. Defaults to now
-     * if the timestamp is undefined.
-     *
-     * @param    {Number}    timestamp
-     * @param    {string}    date
-     */
-    FirechatUI.prototype.formatTime = function(timestamp) {
-        var date = (timestamp) ? new Date(timestamp) : new Date(),
-            hours = date.getHours() || 12,
-            minutes = '' + date.getMinutes(),
-            ampm = (date.getHours() >= 12) ? 'pm' : 'am';
-
-        hours = (hours > 12) ? hours - 12 : hours;
-        minutes = (minutes.length < 2) ? '0' + minutes : minutes;
-        return '' + hours + ':' + minutes + ampm;
-    };
-
-    /**
-     * Launch a prompt to allow the user to create a new room.
-     */
-    FirechatUI.prototype.promptCreateRoom = function() {
-        var self = this;
-        var template = FirechatDefaultTemplates["templates/prompt-create-room.html"];
-
-        var $prompt = this.prompt('Create Public Room', template({
-            maxLengthRoomName: this.maxLengthRoomName,
-            isModerator: self._chat.userIsModerator()
-        }));
-        $prompt.find('a.close').first().click(function() {
-            $prompt.remove();
-            return false;
-        });
-
-
-        $prompt.find('[data-toggle=submit]').first().click(function() {
-            var name = $prompt.find('[data-input=firechat-room-name]').first().val();
-            if (name !== '') {
-                self._chat.createRoom(name, 'public');
-                $prompt.remove();
-            }
-            return false;
-        });
-
-        $prompt.find('[data-input=firechat-room-name]').first().focus();
-        $prompt.find('[data-input=firechat-room-name]').first().bind('keydown', function(e) {
-            if (e.which === 13) {
-                var name = $prompt.find('[data-input=firechat-room-name]').first().val();
-                if (name !== '') {
-                    self._chat.createRoom(name, 'public');
-                    $prompt.remove();
-                    return false;
-                }
-            }
-        });
-    };
-
-    /**
-     * Inner method to launch a prompt given a specific title and HTML content.
-     * @param    {string}    title
-     * @param    {string}    content
-     */
-    FirechatUI.prototype.prompt = function(title, content) {
-        var template = FirechatDefaultTemplates["templates/prompt.html"],
-            $prompt;
-
-        $prompt = $(template({
-            title: title,
-            content: content
-        })).css({
-                top: this.$wrapper.position().top + (0.333 * this.$wrapper.height()),
-                left: this.$wrapper.position().left + (0.125 * this.$wrapper.width()),
-                width: 0.75 * this.$wrapper.width()
-            });
-        this.$wrapper.append($prompt.removeClass('hidden'));
-        return $prompt;
-    };
-
-    // see http://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links
-    FirechatUI.prototype.linkify = function(str) {
-        var self = this;
-        return str
-            .replace(self.urlPattern, '<a target="_blank" href="$&">$&</a>')
-            .replace(self.pseudoUrlPattern, '$1<a target="_blank" href="http://$2">$2</a>');
-    };
-
-})(jQuery);
\ No newline at end of file
diff --git a/public/js/main.js b/public/js/main.js
index c12a97e..a5253f4 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -1,38 +1,269 @@
 $(document).ready(function() {
 
-    function init() {
-        var firepadRef = getExampleRef();
-        var ref = new Firebase('scorching-heat-2873.firebaseIO.com');
-        var codeMirror = CodeMirror(document.getElementById('firepad-container'), {
-            lineNumbers: true,
-            mode: 'javascript'
-        });
-        var firepad = Firepad.fromCodeMirror(firepadRef, codeMirror, {
-            defaultText: '// JavaScript Editing with Firepad!\\nfunction go() {\n  var message = "Hello, world.";\n  console.log(message);\n}'
+    var FADE_TIME = 150; // ms
+    var TYPING_TIMER_LENGTH = 400; // ms
+    var COLORS = [
+        '#e21400', '#91580f', '#f8a700', '#f78b00',
+        '#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
+        '#3b88eb', '#3824aa', '#a700ff', '#d300e7'
+    ];
+
+    // Initialize varibles
+    var $window = $(window);
+    var $usernameInput = $('.usernameInput'); // Input for username
+    var $messages = $('.messages'); // Messages area
+    var $inputMessage = $('.inputMessage'); // Input message input box
+
+    var $loginPage = $('.login.page'); // The login page
+    var $chatPage = $('.chat.page'); // The chatroom page
+
+    // Prompt for setting a username
+    var username;
+    var connected = false;
+    var typing = false;
+    var lastTypingTime;
+    var $currentInput = $usernameInput.focus();
+
+    var socket = io("http://localhost:2999");
+
+
+    function addParticipantsMessage (data) {
+        var message = '';
+        if (data.numUsers === 1) {
+            message += "there's 1 participant";
+        } else {
+            message += "there are " + data.numUsers + " participants";
+        }
+        log(message);
+    }
+
+    // Sets the client's username
+    function setUsername () {
+        username = cleanInput($usernameInput.val().trim());
+
+        // If the username is valid
+        if (username) {
+            $loginPage.fadeOut();
+            $chatPage.show();
+            $loginPage.off('click');
+            $currentInput = $inputMessage.focus();
+
+            // Tell the server your username
+            socket.emit('add user', username);
+        }
+    }
+
+    // Sends a chat message
+    function sendMessage () {
+        var message = $inputMessage.val();
+        // Prevent markup from being injected into the message
+        message = cleanInput(message);
+        // if there is a non-empty message and a socket connection
+        if (message && connected) {
+            $inputMessage.val('');
+            addChatMessage({
+                username: username,
+                message: message
+            });
+            // tell server to execute 'new message' and send along one parameter
+            socket.emit('new message', message);
+        }
+    }
+
+    // Log a message
+    function log (message, options) {
+        var $el = $('<li>').addClass('log').text(message);
+        addMessageElement($el, options);
+    }
+
+    // Adds the visual chat message to the message list
+    function addChatMessage (data, options) {
+        // Don't fade the message in if there is an 'X was typing'
+        var $typingMessages = getTypingMessages(data);
+        options = options || {};
+        if ($typingMessages.length !== 0) {
+            options.fade = false;
+            $typingMessages.remove();
+        }
+
+        var $usernameDiv = $('<span class="username"/>')
+            .text(data.username)
+            .css('color', getUsernameColor(data.username));
+        var $messageBodyDiv = $('<span class="messageBody">')
+            .text(data.message);
+
+        var typingClass = data.typing ? 'typing' : '';
+        var $messageDiv = $('<li class="message"/>')
+            .data('username', data.username)
+            .addClass(typingClass)
+            .append($usernameDiv, $messageBodyDiv);
+
+        addMessageElement($messageDiv, options);
+    }
+
+    // Adds the visual chat typing message
+    function addChatTyping (data) {
+        data.typing = true;
+        data.message = 'is typing';
+        addChatMessage(data);
+    }
+
+    // Removes the visual chat typing message
+    function removeChatTyping (data) {
+        getTypingMessages(data).fadeOut(function () {
+            $(this).remove();
         });
     }
-    function getExampleRef() {
-        var ref = new Firebase('https://firepad.firebaseio-demo.com');
-        var hash = window.location.hash.replace(/#/g, '');
-        if (hash) {
-            ref = ref.child(hash);
+
+    // Adds a message element to the messages and scrolls to the bottom
+    // el - The element to add as a message
+    // options.fade - If the element should fade-in (default = true)
+    // options.prepend - If the element should prepend
+    //   all other messages (default = false)
+    function addMessageElement (el, options) {
+        var $el = $(el);
+
+        // Setup default options
+        if (!options) {
+            options = {};
+        }
+        if (typeof options.fade === 'undefined') {
+            options.fade = true;
+        }
+        if (typeof options.prepend === 'undefined') {
+            options.prepend = false;
+        }
+
+        // Apply options
+        if (options.fade) {
+            $el.hide().fadeIn(FADE_TIME);
+        }
+        if (options.prepend) {
+            $messages.prepend($el);
         } else {
-            ref = ref.push(); // generate unique location.
-            window.location = window.location + '#' + ref.name(); // add it as a hash to the URL.
+            $messages.append($el);
         }
-        if (typeof console !== 'undefined')
-            console.log('Firebase data: ', ref.toString());
-        return ref;
+        $messages[0].scrollTop = $messages[0].scrollHeight;
     }
-    init();
-    var chatRef = new Firebase('https://scorching-heat-2873.firebaseio.com/chat');
-    var auth = new FirebaseSimpleLogin(chatRef, function(err, user) {
-        console.log("HEY!")
-        if (user) {
-            var chat = new FirechatUI(chatRef, document.getElementById('firechat-wrapper'));
-            chat.setUser(user.uid, user.displayName);
+
+    // Prevents input from having injected markup
+    function cleanInput (input) {
+        return $('<div/>').text(input).text();
+    }
+
+    // Updates the typing event
+    function updateTyping () {
+        if (connected) {
+            if (!typing) {
+                typing = true;
+                socket.emit('typing');
+            }
+            lastTypingTime = (new Date()).getTime();
+
+            setTimeout(function () {
+                var typingTimer = (new Date()).getTime();
+                var timeDiff = typingTimer - lastTypingTime;
+                if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
+                    socket.emit('stop typing');
+                    typing = false;
+                }
+            }, TYPING_TIMER_LENGTH);
+        }
+    }
+
+    // Gets the 'X is typing' messages of a user
+    function getTypingMessages (data) {
+        return $('.typing.message').filter(function (i) {
+            return $(this).data('username') === data.username;
+        });
+    }
+
+    // Gets the color of a username through our hash function
+    function getUsernameColor (username) {
+        // Compute hash code
+        var hash = 7;
+        for (var i = 0; i < username.length; i++) {
+            hash = username.charCodeAt(i) + (hash << 5) - hash;
+        }
+        // Calculate color
+        var index = Math.abs(hash % COLORS.length);
+        return COLORS[index];
+    }
+
+    // Keyboard events
+
+    $window.keydown(function (event) {
+        // Auto-focus the current input when a key is typed
+        if (!(event.ctrlKey || event.metaKey || event.altKey)) {
+            $currentInput.focus();
         }
+        // When the client hits ENTER on their keyboard
+        if (event.which === 13) {
+            if (username) {
+                sendMessage();
+                socket.emit('stop typing');
+                typing = false;
+            } else {
+                setUsername();
+            }
+        }
+    });
+
+    $inputMessage.on('input', function() {
+        updateTyping();
     });
 
+    // Click events
+
+    // Focus input when clicking anywhere on login page
+    $loginPage.click(function () {
+        $currentInput.focus();
+    });
+
+    // Focus input when clicking on the message input's border
+    $inputMessage.click(function () {
+        $inputMessage.focus();
+    });
+
+    // Socket events
+
+    // Whenever the server emits 'login', log the login message
+    socket.on('login', function (data) {
+        connected = true;
+        // Display the welcome message
+        var message = "Welcome to Socket.IO Chat – ";
+        log(message, {
+            prepend: true
+        });
+        addParticipantsMessage(data);
+    });
+
+    // Whenever the server emits 'new message', update the chat body
+    socket.on('new message', function (data) {
+        addChatMessage(data);
+    });
+
+    // Whenever the server emits 'user joined', log it in the chat body
+    socket.on('user joined', function (data) {
+        log(data.username + ' joined');
+        addParticipantsMessage(data);
+    });
+
+    // Whenever the server emits 'user left', log it in the chat body
+    socket.on('user left', function (data) {
+        log(data.username + ' left');
+        addParticipantsMessage(data);
+        removeChatTyping(data);
+    });
+
+    // Whenever the server emits 'typing', show the typing message
+    socket.on('typing', function (data) {
+        addChatTyping(data);
+    });
+
+    // Whenever the server emits 'stop typing', kill the typing message
+    socket.on('stop typing', function (data) {
+        removeChatTyping(data);
+    });
 });
 
diff --git a/views/curriculum/curriculum.jade b/views/curriculum/curriculum.jade
index 8b9f563..e574567 100644
--- a/views/curriculum/curriculum.jade
+++ b/views/curriculum/curriculum.jade
@@ -2,4 +2,14 @@ extends ../layout
 
 block content
     .page-header
-        h3 Your Curriculum
\ No newline at end of file
+        h3 Your Curriculum
+    .col-xs-12.col-sm-12.col-md-12
+        ul.pages
+        li.chat.page
+            div.chatArea
+                ul.messages
+            input.inputMessage(placeholder='Type here...')
+        li.login.page
+            div.form
+                h3.title What's your nickname?
+                input.usernameInput(type='text', maxlength='14')
diff --git a/views/layout.jade b/views/layout.jade
index 1fba48c..8a190aa 100644
--- a/views/layout.jade
+++ b/views/layout.jade
@@ -7,6 +7,7 @@ html
     meta(name='description', content='')
     meta(name='csrf-token', content=_csrf)
     meta(name='author', content='')
+    script(src='https://cdn.socket.io/socket.io-1.0.0.js')
     title #{title} | Free Code Camp
     != css('styles')
   body
