diff --git a/Gemfile b/Gemfile
index 58e26f01..56ee0bd2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,6 +27,7 @@ gem 'rack-cors'
 gem 'redis'
 gem 'slack-notifier'
 gem 'snorlax'
+gem 'puma'
 
 # asset stuff
 gem 'jquery-rails'
@@ -44,7 +45,6 @@ group :test do
 end
 
 group :development, :test do
-  gem 'puma'
   gem 'better_errors'
   gem 'binding_of_caller'
   gem 'pry-byebug'
diff --git a/Procfile b/Procfile
index e00c3019..b6bae37c 100644
--- a/Procfile
+++ b/Procfile
@@ -1,3 +1,3 @@
-web: bundle exec rails server -p $PORT
+web: bundle exec puma -p $PORT
 worker:  bundle exec rake jobs:work
 
diff --git a/README.md b/README.md
index b3fdb8ab..67159c68 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY
 
 The license can be read [here][license].
 
-Copyright (c) 2016 Connor Turland
+Copyright (c) 2017 Connor Turland
 
 [site-beta]: http://metamaps.cc
 [license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
diff --git a/app/assets/stylesheets/emoji-mart-0.3.5.css b/app/assets/stylesheets/emoji-mart-0.3.5.css
new file mode 100644
index 00000000..3672320f
--- /dev/null
+++ b/app/assets/stylesheets/emoji-mart-0.3.5.css
@@ -0,0 +1,263 @@
+.emoji-mart,
+.emoji-mart * {
+  box-sizing: border-box;
+  line-height: 1.15;
+}
+
+.emoji-mart {
+  font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
+  font-size: 16px;
+  display: inline-block;
+  color: #222427;
+  border: 1px solid #d9d9d9;
+  border-radius: 5px;
+  background: #fff;
+}
+
+.emoji-mart .emoji-mart-emoji {
+  padding: 6px;
+}
+
+.emoji-mart-bar:first-child {
+  border-top-left-radius: 5px;
+  border-top-right-radius: 5px;
+}
+.emoji-mart-bar:last-child {
+  border-bottom-left-radius: 5px;
+  border-bottom-right-radius: 5px;
+}
+
+.emoji-mart-anchors {
+  display: flex;
+  justify-content: space-between;
+  padding: 0 6px;
+  color: #858585;
+  line-height: 0;
+}
+
+.emoji-mart-anchor {
+  position: relative;
+  flex: 1;
+  text-align: center;
+  padding: 12px 4px;
+  overflow: hidden;
+  transition: color .1s ease-out;
+}
+.emoji-mart-anchor:hover,
+.emoji-mart-anchor-selected {
+  color: #464646;
+}
+
+.emoji-mart-anchor-selected .emoji-mart-anchor-bar {
+  bottom: 0;
+}
+
+.emoji-mart-anchor-bar {
+  position: absolute;
+  bottom: -3px; left: 0;
+  width: 100%; height: 3px;
+  background-color: #464646;
+}
+
+.emoji-mart-anchors i {
+  display: inline-block;
+  width: 100%;
+  max-width: 22px;
+}
+
+.emoji-mart-anchors svg {
+  fill: currentColor;
+}
+
+.emoji-mart-scroll {
+  overflow-y: scroll;
+  height: 270px;
+  padding: 0 6px 6px 6px;
+  border: solid #d9d9d9;
+  border-width: 1px 0;
+}
+
+.emoji-mart-search {
+  font-size: 16px;
+  display: block;
+  width: 100%;
+  padding: .2em .6em;
+  margin-top: 6px;
+  border-radius: 25px;
+  border: 1px solid #d9d9d9;
+  outline: 0;
+}
+
+.emoji-mart-category .emoji-mart-emoji span {
+  z-index: 1;
+  position: relative;
+}
+
+.emoji-mart-category .emoji-mart-emoji:hover:before {
+  z-index: 0;
+  content: "";
+  position: absolute;
+  top: 0; left: 0;
+  width: 100%; height: 100%;
+  background-color: #f4f4f4;
+  border-radius: 100%;
+}
+
+.emoji-mart-category-label {
+  z-index: 2;
+  position: relative;
+  position: -webkit-sticky;
+  top: 0;
+}
+
+.emoji-mart-category-label span {
+  display: block;
+  width: 100%;
+  font-weight: 500;
+  padding: 5px 6px;
+  background-color: #fff;
+  background-color: rgba(255, 255, 255, .95);
+}
+
+.emoji-mart-emoji {
+  position: relative;
+  display: inline-block;
+  font-size: 0;
+}
+
+.emoji-mart-no-results {
+  font-size: 14px;
+  text-align: center;
+  padding-top: 70px;
+  color: #858585;
+}
+.emoji-mart-no-results span {
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.emoji-mart-preview {
+  position: relative;
+  height: 70px;
+}
+
+.emoji-mart-preview-emoji,
+.emoji-mart-preview-data,
+.emoji-mart-preview-skins {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.emoji-mart-preview-emoji {
+  left: 12px;
+}
+
+.emoji-mart-preview-data {
+  left: 68px; right: 12px;
+  word-break: break-word;
+}
+
+.emoji-mart-preview-skins {
+  right: 30px;
+  text-align: right;
+}
+
+.emoji-mart-preview-name {
+  font-size: 14px;
+}
+
+.emoji-mart-preview-shortname {
+  font-size: 12px;
+  color: #888;
+}
+.emoji-mart-preview-shortname + .emoji-mart-preview-shortname,
+.emoji-mart-preview-shortname + .emoji-mart-preview-emoticon,
+.emoji-mart-preview-emoticon + .emoji-mart-preview-emoticon {
+  margin-left: .5em;
+}
+
+.emoji-mart-preview-emoticon {
+  font-size: 11px;
+  color: #bbb;
+}
+
+.emoji-mart-title span {
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.emoji-mart-title .emoji-mart-emoji {
+  padding: 0;
+}
+
+.emoji-mart-title-label {
+  color: #999A9C;
+  font-size: 26px;
+  font-weight: 300;
+}
+
+.emoji-mart-skin-swatches {
+  font-size: 0;
+  padding: 2px 0;
+  border: 1px solid #d9d9d9;
+  border-radius: 12px;
+  background-color: #fff;
+}
+
+.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch {
+  width: 16px;
+  padding: 0 2px;
+}
+
+.emoji-mart-skin-swatches-opened .emoji-mart-skin-swatch-selected:after {
+  opacity: .75;
+}
+
+.emoji-mart-skin-swatch {
+  display: inline-block;
+  width: 0;
+  vertical-align: middle;
+  transition-property: width, padding;
+  transition-duration: .125s;
+  transition-timing-function: ease-out;
+}
+
+.emoji-mart-skin-swatch:nth-child(1) { transition-delay: 0 }
+.emoji-mart-skin-swatch:nth-child(2) { transition-delay: .03s }
+.emoji-mart-skin-swatch:nth-child(3) { transition-delay: .06s }
+.emoji-mart-skin-swatch:nth-child(4) { transition-delay: .09s }
+.emoji-mart-skin-swatch:nth-child(5) { transition-delay: .12s }
+.emoji-mart-skin-swatch:nth-child(6) { transition-delay: .15s }
+
+.emoji-mart-skin-swatch-selected {
+  position: relative;
+  width: 16px;
+  padding: 0 2px;
+}
+.emoji-mart-skin-swatch-selected:after {
+  content: "";
+  position: absolute;
+  top: 50%; left: 50%;
+  width: 4px; height: 4px;
+  margin: -2px 0 0 -2px;
+  background-color: #fff;
+  border-radius: 100%;
+  pointer-events: none;
+  opacity: 0;
+  transition: opacity .2s ease-out;
+}
+
+.emoji-mart-skin {
+  display: inline-block;
+  width: 100%; padding-top: 100%;
+  max-width: 12px;
+  border-radius: 100%;
+}
+
+.emoji-mart-skin-tone-1 { background-color: #ffc93a }
+.emoji-mart-skin-tone-2 { background-color: #fadcbc }
+.emoji-mart-skin-tone-3 { background-color: #e0bb95 }
+.emoji-mart-skin-tone-4 { background-color: #bf8f68 }
+.emoji-mart-skin-tone-5 { background-color: #9b643d }
+.emoji-mart-skin-tone-6 { background-color: #594539 }
diff --git a/app/assets/stylesheets/junto.css.erb b/app/assets/stylesheets/junto.css.erb
deleted file mode 100644
index f738705f..00000000
--- a/app/assets/stylesheets/junto.css.erb
+++ /dev/null
@@ -1,323 +0,0 @@
-.collaborator-video {
-  z-index: 1;
-  position: absolute;
-  width: 150px;
-  height: 150px;
-  cursor: default;
-  color: #FFF;
-}
-.collaborator-video .video-receive {
-    position: absolute;
-    width: 160px;
-    padding: 20px 20px 20px 170px;
-    background: #424242;
-    height: 110px;
-    border-top-left-radius: 75px;
-    border-bottom-left-radius: 75px;
-    border-top-right-radius: 2px;
-    border-bottom-right-radius: 2px;
-}
-.collaborator-video .video-receive .video-statement {
-    margin-bottom: 10px;
-}
-.collaborator-video .video-receive .btn-group .btn-yes {
-    margin-right: 10px;
-}
-.collaborator-video .video-receive .btn-group .btn-no {
-    background-color: #c04f4f;
-}
-.collaborator-video .video-receive .btn-group .btn-no:hover {
-    background-color: #A54242;
-}
-.collaborator-video .video-cutoff {
-  width: 150px;
-  height: 150px;
-  overflow: hidden;
-  border-radius: 75px;
-  z-index: 0;
-  position: relative;
-  -webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
-  -moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
-  box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
-}
-.collaborator-video .video-cutoff video {
-  height: 150px;
-  margin-left: -25px;
-}
-.collaborator-video .video-cutoff .collaborator-video-avatar {
-  position: absolute;
-  top: 0;
-  left: 0;
-  -webkit-user-select: none;
-  -khtml-user-select: none;
-  -moz-user-select: none;
-  -o-user-select: none;
-  user-select: none;
-  -webkit-user-drag: none;
-  display: none;
-}
-.collaborator-video .video-audio {
-  position: absolute;
-  width: 24px;
-  height: 24px;
-  top: 85%;
-  right: 0px;
-  cursor: pointer;
-  background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat;
-}
-.collaborator-video .video-audio:hover {
-  background-position-x: -24px;
-}
-.collaborator-video .video-audio.active {
-  background-position-y: -24px;
-}
-.collaborator-video .video-video {
-  position: absolute;
-  width: 24px;
-  height: 24px;
-  top: 85%;
-  left: 0px;
-  cursor: pointer;
-  background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat;
-}
-.collaborator-video .video-video:hover {
-  background-position-x: -24px;
-}
-.collaborator-video .video-video.active {
-  background-position-y: -24px;
-}
-.collaborator-video.my-video {
-  left: 30px;
-  top: 72px;
-}
-#chat-box-wrapper {
-  height: 100%;
-  float: right;
-}
-.chat-box {
-  position: relative;
-  display: flex;
-  flex-direction: column;
-  z-index: 1;
-  width: 300px;
-  height: 100%;
-  background: #424242;
-  box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
-}
-.chat-box .chat-button {
-  position: absolute;
-  top: 50%;
-  left: -36px;
-  width: 36px;
-  height: 49px;
-  background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
-  cursor: pointer;
-}
-.chat-box .chat-button.active {
-  background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
-}
-.chat-box .chat-button .chat-unread {
-  background: #DAB539;
-  position: absolute;
-  top: -3px;
-  left: -11px;
-  width: 20px;
-  height: 20px;
-  border-radius: 11px;
-  border: 2px solid #424242;
-  color: #424242;
-  text-align: center;
-  font-size: 12px;
-  font-weight: bold;
-  line-height: 20px;
-}
-.chat-box .junto-header {
-  width: 276px;
-  padding: 16px 8px 16px 16px;
-  font-size: 16px;
-  text-align: left;
-  font-weight: bold;
-  background-color: #000000;
-  color: #f5f5f5;
-  box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
-}
-.chat-box .junto-header .cursor-toggle {
-  width: 32px;
-  height: 32px;
-  margin-right: 8px;
-  margin-top: -8px;
-  float: right;
-  background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat;
-}
-.chat-box .junto-header .cursor-toggle:hover {
-  background-position-x: -32px;
-}
-.chat-box .junto-header .cursor-toggle.active {
-  background-position-y: -32px;
-}
-.chat-box .junto-header .video-toggle {
-  width: 32px;
-  height: 32px;
-  margin-right: 10px;
-  margin-top: -8px;
-  float: right;
-  background: url(<%= asset_path 'video_sprite.png' %>) no-repeat;
-}
-.chat-box .junto-header .video-toggle:hover {
-  background-position-x: -32px;
-}
-.chat-box .junto-header .video-toggle.active {
-  background-position-y: -32px;
-}
-.chat-box .participants {
-  width: 100%;
-  min-height: 150px;
-  padding: 16px 0px 16px 0px;
-  text-align: left;
-  color: #f5f5f5;
-  overflow-y: auto;
-}
-.chat-box .participants .conversation-live {
-  padding: 5px 10px 5px 10px;
-  background: #c04f4f;
-  margin: 5px 10px;
-  border-radius: 2px;
-}
-.chat-box .participants .conversation-live .call-action {
-  float: right;
-  cursor: pointer;
-  color: #EBFF00;
-}
-.chat-box .participants .participant {
-  width: 89%;
-  padding: 8px 8px 2px 8px;
-  color: #f5f5f5;
-  font-family: arial, sans-serif;
-  font-size: 13px;
-  line-height: 14px;
-}
-.chat-box .participants .participant .chat-participant-image {
-  width: 15%;
-  float: left;
-  overflow: hidden;
-  color: #BBB;
-  padding-top: 2px;
-}
-.chat-box .participants .participant .chat-participant-image img {
-  width: 32px;
-  height: 32px;
-  border-radius: 18px;
-}
-.chat-box .participants .participant .chat-participant-name {
-  width: 53%;
-  float: left;
-  font-size: 13px;
-  font-weight: bold;
-  margin-top: 12px;
-  padding: 2px 8px 0;
-  text-align: left;
-}
-.chat-box .participants .participant .chat-participant-invite-call,
-.chat-box .participants .participant .chat-participant-invite-join
- {
-  float: right;
-  background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
-}
-.chat-box .participants .participant .chat-participant-invite-call.pending,
-.chat-box .participants .participant .chat-participant-invite-join.pending {
-    background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
-}
-.chat-box .participants .participant .chat-participant-participating {
-  float: right;
-  margin-top: 14px;
-}
-.chat-box .participants .participant .chat-participant-participating .green-dot {
-  background: #4fc059;
-  width: 12px;
-  height: 12px;
-  border-radius: 6px;
-}
-.chat-box .chat-header {
-  width: 276px;
-  padding: 16px 8px 16px 16px;
-  font-size: 16px;
-  text-align: left;
-  font-weight: bold;
-  background-color: #000000;
-  color: #f5f5f5;
-  box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
-}
-.chat-box .chat-header .sound-toggle {
-  width: 24px;
-  height: 24px;
-  margin-right: 10px;
-  margin-top: -2px;
-  float: right;
-  background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat;
-}
-.chat-box .chat-header .sound-toggle:hover {
-  background-position-x: -24px;
-}
-.chat-box .chat-header .sound-toggle.active {
-  background-position-y: -24px;
-}
-.chat-box .chat-input {
-  min-height: 80px;
-  width: 94%;
-  padding: 8px 3% 8px 3%;
-  font-size: 13px;
-  outline: none;
-  resize: none;
-}
-.chat-box .chat-messages {
-  width: 100%;
-  padding: 16px 0px 0px 0px;
-  overflow-y: auto;
-  flex-grow: 1;
-}
-.chat-box .chat-messages .chat-message {
-  width: 89%;
-  padding: 8px 8px 2px 8px;
-  color: #f5f5f5;
-  font-family: arial, sans-serif;
-  font-size: 13px;
-  line-height: 14px;
-}
-.chat-box .chat-messages .chat-message a:link {
-  color: #4fb5c0;
-  text-decoration: underline;
-}
-.chat-box .chat-messages .chat-message a:visited {
-  color: #aea9fd;
-  text-decoration: underline;
-}
-.chat-box .chat-messages .chat-message a:hover {
-  color: #dab539;
-  text-decoration: underline;
-}
-.chat-box .chat-messages .chat-message .chat-message-user {
-  width: 15%;
-  float: left;
-  overflow: hidden;
-  color: #BBB;
-  padding-top: 2px;
-}
-.chat-box .chat-messages .chat-message .chat-message-user img {
-  border: 2px solid #424242;
-  width: 32px;
-  height: 32px;
-  border-radius: 18px;
-}
-.chat-box .chat-messages .chat-message .chat-message-text {
-  width: 73%;
-  float: left;
-  margin-top: 12px;
-  padding: 2px 8px 0;
-  text-align: left;
-  word-wrap: break-word;
-}
-.chat-box .chat-messages .chat-message .chat-message-time {
-  float: right;
-  font-size: 10px;
-  color: #757575;
-}
diff --git a/app/assets/stylesheets/junto.scss.erb b/app/assets/stylesheets/junto.scss.erb
new file mode 100644
index 00000000..6e891ced
--- /dev/null
+++ b/app/assets/stylesheets/junto.scss.erb
@@ -0,0 +1,370 @@
+.collaborator-video {
+  z-index: 1;
+  position: absolute;
+  width: 150px;
+  height: 150px;
+  cursor: default;
+  color: #FFF;
+
+  .video-receive {
+    position: absolute;
+    width: 160px;
+    padding: 20px 20px 20px 170px;
+    background: #424242;
+    height: 110px;
+    border-top-left-radius: 75px;
+    border-bottom-left-radius: 75px;
+    border-top-right-radius: 2px;
+    border-bottom-right-radius: 2px;
+
+    .video-statement {
+      margin-bottom: 10px;
+    }
+    .btn-group {
+      .btn-yes {
+        margin-right: 10px;
+      }
+      .btn-no {
+        background-color: #c04f4f;
+        &:hover {
+          background-color: #A54242;
+        }
+      }
+    }
+  }
+
+  .video-cutoff {
+    width: 150px;
+    height: 150px;
+    overflow: hidden;
+    border-radius: 75px;
+    z-index: 0;
+    position: relative;
+    -webkit-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
+    -moz-box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
+    box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23), 10px 10px 10px rgba(0, 0, 0, 0.19);
+
+    video {
+      height: 150px;
+      margin-left: -25px;
+    }
+    .collaborator-video-avatar {
+      position: absolute;
+      top: 0;
+      left: 0;
+      -webkit-user-select: none;
+      -khtml-user-select: none;
+      -moz-user-select: none;
+      -o-user-select: none;
+      user-select: none;
+      -webkit-user-drag: none;
+      display: none;
+    }
+  }
+  .video-audio {
+    position: absolute;
+    width: 24px;
+    height: 24px;
+    top: 85%;
+    right: 0px;
+    cursor: pointer;
+    background: url(<%= asset_path 'audio_sprite.png' %>) no-repeat;
+  }
+  .video-audio:hover {
+    background-position-x: -24px;
+  }
+  .video-audio.active {
+    background-position-y: -24px;
+  }
+  .video-video {
+    position: absolute;
+    width: 24px;
+    height: 24px;
+    top: 85%;
+    left: 0px;
+    cursor: pointer;
+    background: url(<%= asset_path 'camera_sprite.png' %>) no-repeat;
+  }
+  .video-video:hover {
+    background-position-x: -24px;
+  }
+  .video-video.active {
+    background-position-y: -24px;
+  }
+}
+
+.collaborator-video.my-video {
+  left: 30px;
+  top: 72px;
+}
+
+#chat-box-wrapper {
+  height: 100%;
+  float: right;
+}
+
+.chat-box {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  z-index: 1;
+  width: 300px;
+  height: 100%;
+  background: #424242;
+  box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
+
+  .chat-button {
+    position: absolute;
+    top: 50%;
+    left: -36px;
+    width: 36px;
+    height: 49px;
+    background: url(<%= asset_path 'junto.png' %>) no-repeat 2px 9px, url(<%= asset_path 'tray_tab.png' %>) no-repeat;
+    cursor: pointer;
+
+    &.active {
+      background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
+    }
+    .chat-unread {
+      background: #DAB539;
+      position: absolute;
+      top: -3px;
+      left: -11px;
+      width: 20px;
+      height: 20px;
+      border-radius: 11px;
+      border: 2px solid #424242;
+      color: #424242;
+      text-align: center;
+      font-size: 12px;
+      font-weight: bold;
+      line-height: 20px;
+    }
+  }
+
+  .junto-header {
+    width: 276px;
+    padding: 16px 8px 16px 16px;
+    font-size: 16px;
+    text-align: left;
+    font-weight: bold;
+    background-color: #000000;
+    color: #f5f5f5;
+    box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
+
+    .cursor-toggle {
+      width: 32px;
+      height: 32px;
+      margin-right: 8px;
+      margin-top: -8px;
+      float: right;
+      background: url(<%= asset_path 'cursor_sprite.png' %>) no-repeat;
+    }
+    .cursor-toggle:hover {
+      background-position-x: -32px;
+    }
+    .cursor-toggle.active {
+      background-position-y: -32px;
+    }
+    .video-toggle {
+      width: 32px;
+      height: 32px;
+      margin-right: 10px;
+      margin-top: -8px;
+      float: right;
+      background: url(<%= asset_path 'video_sprite.png' %>) no-repeat;
+    }
+    .video-toggle:hover {
+      background-position-x: -32px;
+    }
+    .video-toggle.active {
+      background-position-y: -32px;
+    }
+  }
+
+  .participants {
+    width: 100%;
+    min-height: 150px;
+    padding: 16px 0px 16px 0px;
+    text-align: left;
+    color: #f5f5f5;
+    overflow-y: auto;
+
+    .conversation-live {
+      padding: 5px 10px 5px 10px;
+      background: #c04f4f;
+      margin: 5px 10px;
+      border-radius: 2px;
+    }
+    .conversation-live .call-action {
+      float: right;
+      cursor: pointer;
+      color: #EBFF00;
+    }
+    .participant {
+      width: 89%;
+      padding: 8px 8px 2px 8px;
+      color: #f5f5f5;
+      font-family: arial, sans-serif;
+      font-size: 13px;
+      line-height: 14px;
+
+      .chat-participant-image {
+        width: 15%;
+        float: left;
+        overflow: hidden;
+        color: #BBB;
+        padding-top: 2px;
+      }
+      .chat-participant-image img {
+        width: 32px;
+        height: 32px;
+        border-radius: 18px;
+      }
+      .chat-participant-name {
+        width: 53%;
+        float: left;
+        font-size: 13px;
+        font-weight: bold;
+        margin-top: 12px;
+        padding: 2px 8px 0;
+        text-align: left;
+      }
+      .chat-participant-invite-call,
+      .chat-participant-invite-join
+       {
+        float: right;
+        background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
+      }
+      .chat-participant-invite-call.pending,
+      .chat-participant-invite-join.pending {
+          background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
+      }
+      .chat-participant-participating {
+        float: right;
+        margin-top: 14px;
+      }
+      .chat-participant-participating .green-dot {
+        background: #4fc059;
+        width: 12px;
+        height: 12px;
+        border-radius: 6px;
+      }
+    }
+  }
+
+  .chat-header {
+    width: 276px;
+    padding: 16px 8px 16px 16px;
+    font-size: 16px;
+    text-align: left;
+    font-weight: bold;
+    background-color: #000000;
+    color: #f5f5f5;
+    box-shadow: 0px 6px 3px rgba(0, 0, 0, 0.23);
+
+    .sound-toggle {
+      width: 24px;
+      height: 24px;
+      margin-right: 10px;
+      margin-top: -2px;
+      float: right;
+      background: url(<%= asset_path 'sound_sprite.png' %>) no-repeat;
+    }
+    .sound-toggle:hover {
+      background-position-x: -24px;
+    }
+    .sound-toggle.active {
+      background-position-y: -24px;
+    }
+  }
+
+  $chat_font_size: 16px;
+
+  .chat-input {
+    min-height: 80px;
+    width: 88%;
+    padding: 8px 9% 8px 3%;
+    font-size: $chat_font_size;
+    outline: none;
+    resize: none;
+  }
+
+  .chat-messages {
+    width: 100%;
+    padding: 16px 0px;
+    overflow-y: auto;
+    flex-grow: 1;
+
+    .chat-message {
+      width: 89%;
+      padding: 8px 8px 2px 8px;
+      color: #f5f5f5;
+      font-family: arial, sans-serif;
+      font-size: $chat_font_size;
+      line-height: $chat_font_size + 1px;
+
+      a:link {
+        color: #4fb5c0;
+        text-decoration: underline;
+      }
+      a:visited {
+        color: #aea9fd;
+        text-decoration: underline;
+      }
+      a:hover {
+        color: #dab539;
+        text-decoration: underline;
+      }
+      .chat-message-user {
+        width: 12%;
+        float: left;
+        overflow: hidden;
+        color: #BBB;
+        padding-top: 2px;
+      }
+      .chat-message-user img {
+        border: 2px solid #424242;
+        width: 28px;
+        height: 28px;
+        border-radius: 16px;
+      }
+      .chat-message-meta {
+        padding: 0 8px;
+        float: left;
+      }
+      .chat-message-username {
+        color: #4fc059;
+      }
+      .chat-message-text {
+        width: 80%;
+        float: left;
+        padding: 2px 8px 0;
+        text-align: left;
+        word-wrap: break-word;
+      }
+      .chat-message-time {
+        font-size: 12px;
+        color: #757575;
+      }
+    }
+  }
+
+  .new-message-area {
+    position: relative;
+
+    .extra-message-options {
+      height: 20px;
+      position: absolute;
+      right: 2px;
+      bottom: 74px;
+    
+      .emoji-picker-button {
+        font-size: 16px;
+        line-height: 20px;
+        cursor: pointer;
+        padding: 4px;
+      }
+    }
+  }
+}
diff --git a/app/views/layouts/_foot.html.erb b/app/views/layouts/_foot.html.erb
index e309c65f..ea4ab671 100644
--- a/app/views/layouts/_foot.html.erb
+++ b/app/views/layouts/_foot.html.erb
@@ -10,6 +10,6 @@
 
   Metamaps.Loading.setup()
 </script>
-<%= render :partial => 'layouts/googleanalytics' if Rails.env.production? %>
+<%= render :partial => 'layouts/googleanalytics' if ENV["GA_TRACKING_CODE"].present? %>
 </body>
 </html>
diff --git a/app/views/layouts/_googleanalytics.html.erb b/app/views/layouts/_googleanalytics.html.erb
index 11d41495..2acb9501 100644
--- a/app/views/layouts/_googleanalytics.html.erb
+++ b/app/views/layouts/_googleanalytics.html.erb
@@ -3,16 +3,13 @@
   # Google analytics, rendered on every page
   #%>
 
-<script type="text/javascript">
+<script>
+  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
 
-  var _gaq = _gaq || [];
-  _gaq.push(['_setAccount', 'UA-35984510-1']);
-  _gaq.push(['_trackPageview']);
-
-  (function() {
-    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
-    ga.src = ('https:' == document.location.protocol ? 'https://ssl.' : 'http://') +  'stats.g.doubleclick.net/dc.js';
-    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-  })();
+  ga('create', '<%= ENV["GA_TRACKING_CODE"] %>', 'auto');
+  ga('send', 'pageview');
 
 </script>
diff --git a/config/cable.yml b/config/cable.yml
index 0bbde6f7..34eb5cdc 100644
--- a/config/cable.yml
+++ b/config/cable.yml
@@ -6,4 +6,4 @@ test:
 
 production:
   adapter: redis
-  url: redis://localhost:6379/1
+  url: <%= ENV['REDISTOGO_URL'] %>
diff --git a/config/environments/production.rb b/config/environments/production.rb
index ab4769b6..fd7ff05a 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -3,6 +3,8 @@
 Rails.application.configure do
   # Settings specified here will take precedence over those in config/application.rb
 
+  config.action_cable.allowed_request_origins = ['https://metamaps.herokuapp.com', 'http://metamaps.herokuapp.com', 'https://metamaps.cc']
+
   # log to stdout
   logger = Logger.new(STDOUT)
   logger.formatter = config.log_formatter
diff --git a/doc/production/first-deploy.md b/doc/production/first-deploy.md
index 994e68c2..b6eddd22 100644
--- a/doc/production/first-deploy.md
+++ b/doc/production/first-deploy.md
@@ -103,7 +103,7 @@ server to see what problems show up:
 
 #### Upstart service for delayed_worker:
 
-Put the following code into `/etc/init/metamaps_delayed_worker.conf`:
+If your system uses upstart for init scripts, put the following code into `/etc/init/metamaps_delayed_job.conf`:
 
     description "Delayed Jobs Worker for Metamaps"
     
@@ -128,3 +128,30 @@ Then start the service and check the last ten lines of the log file to make sure
 
     sudo service metamaps_delayed_job start
     tail /var/log/upstart/metamaps_delayed_job.log
+
+#### Systemd service for delayed_worker:
+
+If your system uses systemd for init scripts, ptu the following code into `/etc/systemd/system/metamaps_delayed_job.service`:
+
+    [Unit]
+    Description=metamaps delayed job service
+    After=network-online.target
+
+    [Service]
+    ExecStart=/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin/bundle exec rails jobs:work
+    WorkingDirectory=/home/metamaps/metamaps
+    Restart=always
+    User=metamaps
+    Group=metamaps
+    Environment=HOME=/home/metamaps
+    Environment=PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin:/usr/local/rvm/gems/ruby-2.3.0@global/bin:/usr/local/rvm/rubies/ruby-2.3.0/bin:/usr/local/rvm/bin:/usr/local/bin:/usr/bin:/bin"
+    Environment=GEM_PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps:/usr/local/rvm/gems/ruby-2.3.0@global"
+    Environment=RAILS_ENV="production"
+
+    [Install]
+    WantedBy=multi-user.target
+
+Then start the service and check the last ten lines of the log file to make sure it's running OK:
+
+    sudo systemctl start metamaps_delayed_job
+    # ??? how the heck do you check systemd logs??
diff --git a/frontend/src/Metamaps/Util.js b/frontend/src/Metamaps/Util.js
index 1e5c3b66..f35970da 100644
--- a/frontend/src/Metamaps/Util.js
+++ b/frontend/src/Metamaps/Util.js
@@ -1,6 +1,14 @@
 /* global $ */
 
 import { Parser, HtmlRenderer } from 'commonmark'
+import { emojiIndex } from 'emoji-mart'
+import { escapeRegExp } from 'lodash'
+
+const emojiToShortcodes = {}
+Object.keys(emojiIndex.emojis).forEach(key => {
+  const emoji = emojiIndex.emojis[key]
+  emojiToShortcodes[emoji.native] = emoji.colons
+})
 
 const Util = {
   // helper function to determine how many lines are needed
@@ -150,6 +158,29 @@ const Util = {
     canvas.scale(oldAttr.scaleX, oldAttr.scaleY)
     const newAttr = Util.logCanvasAttributes(canvas)
     canvas.translate(newAttr.centreCoords.x - oldAttr.centreCoords.x, newAttr.centreCoords.y - oldAttr.centreCoords.y)
+  },
+  removeEmoji: function(withEmoji) {
+    let text = withEmoji
+    Object.keys(emojiIndex.emojis).forEach(key => {
+      const emoji = emojiIndex.emojis[key]
+      text = text.replace(new RegExp(escapeRegExp(emoji.native), 'g'), emoji.colons)
+    })
+    return text
+  },
+  addEmoji: function(withoutEmoji, opts = { emoticons: false }) {
+    let text = withoutEmoji
+    Object.keys(emojiIndex.emojis).forEach(key => {
+      const emoji = emojiIndex.emojis[key]
+      text = text.replace(new RegExp(escapeRegExp(emoji.colons), 'g'), emoji.native)
+    })
+    if (opts.emoticons) {
+      Object.keys(emojiIndex.emoticons).forEach(emoticon => {
+        const key = emojiIndex.emoticons[emoticon]
+        const emoji = emojiIndex.emojis[key]
+        text = text.replace(new RegExp(escapeRegExp(emoticon), 'g'), emoji.native)
+      })
+    }
+    return text
   }
 }
 
diff --git a/frontend/src/components/MapChat/Message.js b/frontend/src/components/MapChat/Message.js
index b9ddeda1..ae3507ce 100644
--- a/frontend/src/components/MapChat/Message.js
+++ b/frontend/src/components/MapChat/Message.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import Autolinker from 'autolinker'
+import Util from '../../Metamaps/Util'
 
 const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
 
@@ -18,15 +19,19 @@ function formatDate(created_at) {
 }
 
 const Message = props => {
-  const { user_image, user_name, message, created_at } = props
-  const messageHtml = {__html: linker.link(message)}
+  const { user_image, user_name, message, created_at, heading } = props
+  const messageHtml = {__html: linker.link(Util.addEmoji(message, { emoticons: false }))}
+
   return (
     <div className="chat-message">
       <div className="chat-message-user">
-        <img src={user_image} title={user_name} />
+        {heading && <img src={user_image} />}
       </div>
+      {heading && <div className="chat-message-meta">
+        <span className='chat-message-username'>{user_name}</span>&nbsp;
+        <span className='chat-message-time'>{formatDate(created_at)}</span>
+      </div>}
       <div className="chat-message-text" dangerouslySetInnerHTML={messageHtml}></div>
-      <div className="chat-message-time">{formatDate(created_at)}</div>
       <div className="clearfloat"></div>
     </div>
   )
diff --git a/frontend/src/components/MapChat/NewMessage.js b/frontend/src/components/MapChat/NewMessage.js
new file mode 100644
index 00000000..f0cc80a4
--- /dev/null
+++ b/frontend/src/components/MapChat/NewMessage.js
@@ -0,0 +1,70 @@
+import React, { PropTypes, Component } from 'react'
+import { Emoji, Picker } from 'emoji-mart'
+import Util from '../../Metamaps/Util'
+
+class NewMessage extends Component {
+  constructor(props) {
+    super(props)
+
+    this.state = {
+      showEmojiPicker: false
+    }
+  }
+
+  toggleEmojiPicker = () => {
+    this.setState({ showEmojiPicker: !this.state.showEmojiPicker })
+  }
+
+  textAreaValue = () => {
+    return Util.addEmoji(this.props.messageText)
+  }
+
+  handleClick = (emoji, event) => {
+    const { messageText } = this.props
+    this.props.handleChange({ target: {
+      value: messageText + emoji.colons
+    }})
+
+    this.setState({ showEmojiPicker: false })
+    this.props.focusMessageInput()
+  }
+
+  render = () => {
+    return (
+      <div className="new-message-area">
+        <Picker set="emojione"
+          onClick={this.handleClick}
+          style={{
+            display: this.state.showEmojiPicker ? 'block' : 'none',
+            maxWidth: '100%'
+          }}
+          emoji="upside_down_face"
+          title="Emoji"
+        />
+        <div className="extra-message-options">
+          <span className="emoji-picker-button" onClick={this.toggleEmojiPicker}><Emoji size={24} emoji="upside_down_face" /></span>
+        </div>
+        <textarea value={this.textAreaValue()}
+          onChange={this.props.handleChange}
+          {...this.props.textAreaProps}
+        />
+      </div>
+    )
+  }
+}
+
+NewMessage.propTypes = {
+  messageText: PropTypes.string,
+  handleChange: PropTypes.func,
+  focusMessageInput: PropTypes.func,
+  textAreaProps: PropTypes.shape({
+    className: PropTypes.string,
+    ref: PropTypes.func,
+    placeholder: PropTypes.string,
+    onKeyUp: PropTypes.func,
+    onFocus: PropTypes.func,
+    onBlur: PropTypes.func
+  })
+}
+
+export default NewMessage
diff --git a/frontend/src/components/MapChat/index.js b/frontend/src/components/MapChat/index.js
index 101b9755..27bff527 100644
--- a/frontend/src/components/MapChat/index.js
+++ b/frontend/src/components/MapChat/index.js
@@ -2,6 +2,24 @@ import React, { PropTypes, Component } from 'react'
 import Unread from './Unread'
 import Participant from './Participant'
 import Message from './Message'
+import NewMessage from './NewMessage'
+import Util from '../../Metamaps/Util'
+
+function makeList(messages) {
+  let currentHeader
+  return messages ? messages.map(m => {
+    let heading = false
+    if (!currentHeader) {
+      heading = true
+      currentHeader = m
+    } else {
+      // not same user or time diff of greater than 3 minutes
+      heading = m.user_id !== currentHeader.user_id || Math.floor(Math.abs(new Date(currentHeader.created_at) - new Date(m.created_at)) / 60000) > 3
+      currentHeader = heading ? m : currentHeader
+    }
+    return <Message {...m} key={m.id} heading={heading}/>
+  }) : null
+}
 
 class MapChat extends Component {
   constructor(props) {
@@ -13,7 +31,7 @@ class MapChat extends Component {
       messageText: '',
       alertSound: true, // whether to play sounds on arrival of new messages or not
       cursorsShowing: true,
-      videosShowing:  true
+      videosShowing: true
     }
   }
 
@@ -24,7 +42,7 @@ class MapChat extends Component {
       messageText: '',
       alertSound: true, // whether to play sounds on arrival of new messages or not
       cursorsShowing: true,
-      videosShowing:  true
+      videosShowing: true
     })
   }
 
@@ -78,12 +96,17 @@ class MapChat extends Component {
   handleTextareaKeyUp = e => {
     if (e.which === 13) {
       e.preventDefault()
-      const text = this.state.messageText
+      const text = Util.removeEmoji(this.state.messageText)
       this.props.handleInputMessage(text)
       this.setState({ messageText: '' })
     }
   }
 
+  focusMessageInput = () => {
+    if (!this.messageInput) return
+    this.messageInput.focus()
+  }
+
   render = () => {
     const rightOffset = this.state.open ? '0' : '-300px'
     const { conversationLive, isParticipating, participants, messages, inviteACall, inviteToJoin } = this.props
@@ -100,12 +123,12 @@ class MapChat extends Component {
         <div className="participants">
           {conversationLive && <div className="conversation-live">
             LIVE
-	      {isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
-                LEAVE
-              </span>}
-              {!isParticipating && <span className="call-action join"  onClick={this.props.joinCall}>
-                JOIN
-              </span>}
+            {isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
+              LEAVE
+            </span>}
+            {!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
+              JOIN
+            </span>}
           </div>}
           {participants.map(participant => <Participant
             key={participant.id}
@@ -124,17 +147,20 @@ class MapChat extends Component {
           <div className="tooltips">Chat</div>
           <Unread count={unreadMessages} />
         </div>
-        <div className="chat-messages" ref={div => this.messagesDiv = div}>
-          {messages.map(message => <Message key={message.id} {...message} />)}
+        <div className="chat-messages" ref={div => { this.messagesDiv = div }}>
+          {makeList(messages)}
         </div>
-        <textarea className="chat-input"
-          ref={textarea => this.messageInput = textarea}
-          placeholder="Send a message..."
-          value={this.state.messageText}
-          onChange={this.handleChange('messageText')}
-          onKeyUp={this.handleTextareaKeyUp}
-          onFocus={this.props.inputFocus}
-          onBlur={this.props.inputBlur}
+        <NewMessage messageText={this.state.messageText}
+          focusMessageInput={this.focusMessageInput}
+          handleChange={this.handleChange('messageText')}
+          textAreaProps={{
+            className: 'chat-input',
+            ref: textarea => { this.messageInput = textarea },
+            placeholder: 'Send a message...',
+            onKeyUp: this.handleTextareaKeyUp,
+            onFocus: this.props.inputFocus,
+            onBlur: this.props.inputBlur
+          }}
         />
       </div>
     )
@@ -152,7 +178,7 @@ MapChat.propTypes = {
   inviteToJoin: PropTypes.func,
   videoToggleClick: PropTypes.func,
   cursorToggleClick: PropTypes.func,
-  soundToggleClick: PropTypes.func, 
+  soundToggleClick: PropTypes.func,
   participants: PropTypes.arrayOf(PropTypes.shape({
     color: PropTypes.string, // css color
     id: PropTypes.number,
diff --git a/package.json b/package.json
index 92d4ab25..42c2dd31 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,10 @@
     "babel-preset-es2015": "6.18.0",
     "babel-preset-react": "6.16.0",
     "backbone": "1.0.0",
-    "clipboard-js": "git://github.com/devvmh/clipboard.js#patch-1",
+    "clipboard-js": "0.3.2",
     "commonmark": "0.27.0",
     "csv-parse": "1.1.7",
+    "emoji-mart": "^0.3.5",
     "getScreenMedia": "git://github.com/devvmh/getScreenMedia#patch-1",
     "hark": "git://github.com/devvmh/hark#patch-1",
     "howler": "2.0.2",