Bug 1203454 - Backout incomplete feature work from Loop's visual refresh in Fx42 - Backout bug 1183618. a=sledru
authorMark Banner <standard8@mozilla.com>
Thu, 10 Sep 2015 11:13:17 +0100
changeset 289204 1b471080813debbcdf0b18bf5f82cf14d5a85131
parent 289203 fdeacd3d68ad9a1929ffd41856f26821909f6a37
child 289205 01567d5dc281c52ee7c4b3201d01889a1aee3bc3
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssledru
bugs1203454, 1183618
milestone42.0a2
Bug 1203454 - Backout incomplete feature work from Loop's visual refresh in Fx42 - Backout bug 1183618. a=sledru
browser/components/loop/content/css/contacts.css
browser/components/loop/content/css/panel.css
browser/components/loop/content/js/contacts.js
browser/components/loop/content/js/contacts.jsx
browser/components/loop/content/js/panel.js
browser/components/loop/content/js/panel.jsx
browser/components/loop/content/shared/css/common.css
browser/components/loop/content/shared/img/avatars.svg
browser/components/loop/content/shared/img/ellipsis-v.svg
browser/components/loop/content/shared/img/empty_contacts.svg
browser/components/loop/content/shared/img/icons-14x14.svg
browser/components/loop/jar.mn
browser/components/loop/modules/MozLoopAPI.jsm
browser/components/loop/test/desktop-local/contacts_test.js
browser/components/loop/test/desktop-local/panel_test.js
browser/components/loop/ui/fake-mozLoop.js
browser/components/loop/ui/ui-showcase.css
browser/components/loop/ui/ui-showcase.js
browser/components/loop/ui/ui-showcase.jsx
--- a/browser/components/loop/content/css/contacts.css
+++ b/browser/components/loop/content/css/contacts.css
@@ -13,55 +13,53 @@
 }
 
 .content-area input.contact-filter {
   margin-top: 14px;
   border-radius: 10000px;
 }
 
 .contact-list {
+  border-top: 1px solid #ccc;
   overflow-x: hidden;
   overflow-y: auto;
   /* Space for six contacts, not affected by filtering.  This is enough space
      to show the dropdown menu when there is only one contact. */
   height: 306px;
 }
 
-.contact-list-title {
-  padding: 0 1rem;
-  color: #666;
-  font-weight: 500;
-  font-size: .9em;
-}
-
 .contact,
 .contact-separator {
-  padding: .5rem 15px;
+  padding: .5rem 1rem;
   font-size: 13px;
 }
 
 .contact {
   position: relative;
   display: flex;
   flex-direction: row;
   align-items: center;
   color: #666;
 }
 
 .contact-separator {
   background-color: #eee;
   color: #888;
 }
 
+.contact:not(:first-child) {
+  border-top: 1px solid #ddd;
+}
+
 .contact-separator:not(:first-child) {
   border-top: 1px solid #ccc;
 }
 
 .contact:hover {
-  background-color: #E3F7FE;
+  background-color: #eee;
 }
 
 .contact:hover > .icons {
   display: block;
   z-index: 1;
 }
 
 .contact > .details {
@@ -88,111 +86,24 @@
   box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
   background-image: url("../shared/img/audio-call-avatar.svg");
   background-repeat: no-repeat;
   background-color: #4ba6e7;
   background-size: contain;
   -moz-user-select: none;
 }
 
-/*
- * Loop through all 12 default avatars.
- */
-.contact:nth-child(12n + 1) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#blue-avatar");
-  background-color: #4A90E2;
-}
-
-.contact:nth-child(12n + 2) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#orange-avatar");
-  background-color: #F3A35C;
-}
-
-.contact:nth-child(12n + 3) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#mintgreen-avatar");
-  background-color: #50E2C2;
-}
-
-.contact:nth-child(12n + 4) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#lightpink-avatar");
-  background-color: #E364A1;
-}
-
-.contact:nth-child(12n + 5) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#grey-avatar");
-  background-color: #9B9B9B;
-}
-
-.contact:nth-child(12n + 6) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#yellow-avatar");
-  background-color: #F3E968;
-}
-
-.contact:nth-child(12n + 7) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#purple-avatar");
-  background-color: #9C61AF;
-}
-
-.contact:nth-child(12n + 8) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#lightgreen-avatar");
-  background-color: #9AC967;
-}
-
-.contact:nth-child(12n + 9) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#darkblue-avatar");
-  background-color: #607CAE;
-}
-
-.contact:nth-child(12n + 10) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#darkpink-avatar");
-  background-color: #CE4D6E;
-}
-
-.contact:nth-child(12n + 11) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#brown-avatar");
-  background-color: #8A572A;
-}
-
-.contact:nth-child(12n + 12) > .avatar.defaultAvatar {
-  background-image: url("../shared/img/avatars.svg#green-avatar");
-  background-color: #56B397;
-}
-
 .contact > .avatar > img {
   width: 100%;
 }
 
-.contact-list-empty {
-  background-image: url("../shared/img/empty_contacts.svg");
-  background-repeat: no-repeat;
-  background-position: top center;
-  padding-top: 28%;
-  padding-bottom: 5%;
-  text-align: center;
-  color: #4a4a4a;
-  font-weight: lighter;
-}
-
-.panel-text-medium,
-.panel-text-large {
-  margin: 3px;
-}
-
-.panel-text-medium {
-  font-size: 1.6rem;
-}
-
-.panel-text-large {
-  font-size: 2.2rem;
-}
-
 .contact > .details > .username {
-  font-size: 1.3rem;
+  font-size: 12px;
   line-height: 20px;
-  color: #000;
+  color: #222;
 }
 
 .contact.blocked > .details > .username {
   color: #d74345;
 }
 
 .contact > .details > .username > strong {
   font-weight: bold;
@@ -204,88 +115,135 @@
   height: 20px;
   -moz-margin-start: 3px;
   background-image: url("../shared/img/icons-16x16.svg#block-red");
   background-position: center;
   background-size: 10px 10px;
   background-repeat: no-repeat;
 }
 
+.contact > .details > .username > i.icon-google {
+  position: absolute;
+  right: 1rem;
+  top: 35%;
+  width: 14px;
+  height: 14px;
+  border-radius: 50%;
+  background-image: url("../shared/img/icons-16x16.svg#google");
+  background-position: center;
+  background-size: 16px 16px;
+  background-repeat: no-repeat;
+  background-color: #fff;
+}
+
+html[dir="rtl"] .contact > .details > .username > i.icon-google {
+  left: 1rem;
+  right: auto;
+}
+
 .contact > .details > .email {
-  color: #4a4a4a;
+  color: #999;
   font-size: 11px;
-  line-height: 14px;
+  line-height: 16px;
 }
 
 .icons {
   cursor: pointer;
   display: none;
   -moz-margin-start: auto;
+  padding: 10px;
+  border-radius: 2px;
+  background-color: #5bc0a4;
   color: #fff;
   -moz-user-select: none;
 }
 
 .icons:hover {
-  display: block;
+  background-color: #47b396;
+}
+
+.icons:hover:active {
+  background-color: #3aa689;
 }
 
 .icons i {
+  margin: 0 5px;
   display: inline-block;
   background-position: center;
   background-repeat: no-repeat;
 }
 
-.icon-contact-video-call {
-  padding: 15px;
+.icons i.icon-video {
+  background-image: url("../shared/img/icons-14x14.svg#video-white");
+  background-size: 14px 14px;
   width: 16px;
   height: 16px;
-  border-radius: 50%;
-  background-color: #5bc0a4;
-  background-image: url("../shared/img/icons-14x14.svg#video-white");
-  background-size: 16px 16px;
-}
-
-.icon-contact-video-call:hover {
-  background-color: #47b396;
 }
 
-.icon-contact-video-call:active {
-  background-color: #3aa689;
-}
-
-.icon-vertical-ellipsis {
-  /* Added padding for a larger click area. */
-  padding: 0 10px;
-  margin: 6px 0;
-  -moz-margin-start: 5px;
-  -moz-margin-end: -8px;
-  width: 4px;
-  height: 20px;
-  background-image: url("../shared/img/ellipsis-v.svg");
-  background-size: contain;
+.icons i.icon-caret-down {
+  background-image: url("../shared/img/icons-10x10.svg#dropdown-white");
+  background-size: 10px 10px;
+  width: 10px;
+  height: 16px;
 }
 
 .contact > .dropdown-menu {
   z-index: 2;
-  top: 37px;
-  right: 22px;
+  top: 10px;
   bottom: auto;
+  right: 3em;
   left: auto;
 }
 
 html[dir="rtl"] .contact > .dropdown-menu {
   right: auto;
-  left: 22px;
+  left: 3em;
 }
 
 .contact > .dropdown-menu-up {
-  bottom: 25px;
+  bottom: 10px;
   top: auto;
 }
 
+.contact > .dropdown-menu > .dropdown-menu-item > .icon {
+  width: 20px;
+  height: 10px;
+  background-position: center left;
+  background-size: 10px 10px;
+  margin-top: 3px;
+}
+
+html[dir="rtl"] .contact > .dropdown-menu > .dropdown-menu-item > .icon {
+  background-position: center right;
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-audio-call {
+  background-image: url("../shared/img/icons-16x16.svg#audio");
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-video-call {
+  background-image: url("../shared/img/icons-16x16.svg#video");
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-edit {
+  background-image: url("../shared/img/icons-16x16.svg#contacts");
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-block {
+  background-image: url("../shared/img/icons-16x16.svg#block");
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-unblock {
+  background-image: url("../shared/img/icons-16x16.svg#unblock");
+}
+
+.contact > .dropdown-menu > .dropdown-menu-item > .icon-remove {
+  background-image: url("../shared/img/icons-16x16.svg#delete");
+}
+
 .contact-form > .button-group {
   margin-top: 1rem;
 }
 
 .contacts-gravatar-promo {
   position: relative;
   border: 1px dashed #c1c1c1;
   border-radius: 2px;
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -61,17 +61,16 @@ body {
   padding: .5rem 1rem;
   border-radius: 3px;
 }
 
 /* Tabs and tab selection buttons */
 
 .tab-view-container {
   background-image: url("../shared/img/beta-ribbon.svg#beta-ribbon");
-  background-color: #fbfbfb;
   background-size: 36px 36px;
   background-repeat: no-repeat;
 }
 
 .tab-view {
   display: flex;
   flex-direction: row;
   padding: 10px 0;
@@ -656,21 +655,16 @@ html[dir="rtl"] .generate-url-spinner {
   height: 16px;
   vertical-align: bottom;
   background-repeat: no-repeat;
   background-size: cover;
   -moz-margin-end: .2rem;
   margin-bottom: -2px;
 }
 
-.dropdown-menu-item.status-available:before,
-.dropdown-menu-item.status-unavailable:before {
-  margin-bottom: 2px;
-}
-
 html[dir="rtl"] .dropdown-menu-item.status-available:before,
 html[dir="rtl"] .dropdown-menu-item.status-unavailable:before {
   margin-right: -3px;
 }
 
 .status-available:before {
   background-image: url("../shared/img/icons-16x16.svg#status-available");
 }
@@ -781,17 +775,17 @@ html[dir="rtl"] .settings-menu .dropdown
 .footer {
   display: flex;
   flex-direction: row;
   flex-wrap: nowrap;
   justify-content: space-between;
   align-content: stretch;
   align-items: center;
   font-size: 1rem;
-  background-color: #fbfbfb;
+  background-color: #fff;
   color: #666666;
   padding: .5rem 15px;
 }
 
 .footer .signin-details {
   align-items: center;
   display: flex;
 }
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -180,39 +180,57 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
     render: function() {
       var cx = React.addons.classSet;
+
       let blockAction = this.props.blocked ? "unblock" : "block";
       let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
                                           : "block_contact_menu_button";
 
       return (
         React.createElement("ul", {className: cx({ "dropdown-menu": true,
                             "dropdown-menu-up": this.state.openDirUp })}, 
           React.createElement("li", {className: cx({ "dropdown-menu-item": true,
+                              "disabled": this.props.blocked }), 
+              "data-action": "video-call", 
+              onClick: this.onItemClick}, 
+            React.createElement("i", {className: "icon icon-video-call"}), 
+            mozL10n.get("video_call_menu_button")
+          ), 
+          React.createElement("li", {className: cx({ "dropdown-menu-item": true,
+                              "disabled": this.props.blocked }), 
+              "data-action": "audio-call", 
+              onClick: this.onItemClick}, 
+            React.createElement("i", {className: "icon icon-audio-call"}), 
+            mozL10n.get("audio_call_menu_button")
+          ), 
+          React.createElement("li", {className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
               "data-action": "edit", 
               onClick: this.onItemClick}, 
-            mozL10n.get("edit_contact_title")
+            React.createElement("i", {className: "icon icon-edit"}), 
+            mozL10n.get("edit_contact_menu_button")
           ), 
           React.createElement("li", {className: "dropdown-menu-item", 
               "data-action": blockAction, 
               onClick: this.onItemClick}, 
+            React.createElement("i", {className: "icon icon-" + blockAction}), 
             mozL10n.get(blockLabel)
           ), 
           React.createElement("li", {className: cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit }), 
-              "data-action": "remove", 
-              onClick: this.onItemClick}, 
-            mozL10n.get("confirm_delete_contact_remove_button")
+               "data-action": "remove", 
+               onClick: this.onItemClick}, 
+            React.createElement("i", {className: "icon icon-remove"}), 
+            mozL10n.get("remove_contact_menu_button2")
           )
         )
       );
     }
   });
 
   const ContactDetail = React.createClass({displayName: "ContactDetail",
     getInitialState: function() {
@@ -272,42 +290,38 @@ loop.contacts = (function(_, mozL10n) {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] != "google";
     },
 
     render: function() {
       let names = getContactNames(this.props.contact);
       let email = getPreferred(this.props.contact, "email");
-      let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
-      let avatarCSSClass = cx({
-        avatar: true,
-        defaultAvatar: !avatarSrc
-      });
 
       return (
         React.createElement("li", {className: contactCSSClass, onMouseLeave: this.hideDropdownMenu}, 
-          React.createElement("div", {className: avatarCSSClass}, 
-            avatarSrc ? React.createElement("img", {src: avatarSrc}) : null
+          React.createElement("div", {className: "avatar"}, 
+            React.createElement("img", {src: navigator.mozLoop.getUserAvatar(email.value)})
           ), 
           React.createElement("div", {className: "details"}, 
             React.createElement("div", {className: "username"}, React.createElement("strong", null, names.firstName), " ", names.lastName, 
+              React.createElement("i", {className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}), 
               React.createElement("i", {className: cx({"icon icon-blocked": this.props.contact.blocked})})
             ), 
             React.createElement("div", {className: "email"}, email.value)
           ), 
           React.createElement("div", {className: "icons"}, 
-            React.createElement("i", {className: "icon icon-contact-video-call", 
+            React.createElement("i", {className: "icon icon-video", 
                onClick: this.handleAction.bind(null, "video-call")}), 
-            React.createElement("i", {className: "icon icon-vertical-ellipsis", 
+            React.createElement("i", {className: "icon icon-caret-down", 
                onClick: this.showDropdownMenu})
           ), 
           this.state.showMenu
             ? React.createElement(ContactDropdown, {blocked: this.props.contact.blocked, 
                                canEdit: this.canEdit(), 
                                handleAction: this.handleAction})
             : null
           
@@ -318,21 +332,20 @@ loop.contacts = (function(_, mozL10n) {
 
   const ContactsList = React.createClass({displayName: "ContactsList",
     mixins: [
       React.addons.LinkedStateMixin,
       loop.shared.mixins.WindowCloseMixin
     ],
 
     propTypes: {
-      mozLoop: React.PropTypes.object.isRequired,
       notifications: React.PropTypes.instanceOf(
-                     loop.shared.models.NotificationCollection).isRequired,
-      // Callback to handle entry to the add/edit contact form.
-      startForm: React.PropTypes.func.isRequired
+        loop.shared.models.NotificationCollection).isRequired,
+        // Callback to handle entry to the add/edit contact form.
+        startForm: React.PropTypes.func.isRequired
     },
 
     /**
      * Contacts collection object
      */
     contacts: null,
 
     /**
@@ -343,17 +356,17 @@ loop.contacts = (function(_, mozL10n) {
     getInitialState: function() {
       return {
         importBusy: false,
         filter: ""
       };
     },
 
     refresh: function(callback = function() {}) {
-      let contactsAPI = this.props.mozLoop.contacts;
+      let contactsAPI = navigator.mozLoop.contacts;
 
       this.handleContactRemoveAll();
 
       contactsAPI.getAll((err, contacts) => {
         if (err) {
           callback(err);
           return;
         }
@@ -375,28 +388,28 @@ loop.contacts = (function(_, mozL10n) {
         addContactsInChunks(contacts);
       });
     },
 
     componentWillMount: function() {
       // Take the time to initialize class variables that are used outside
       // `this.state`.
       this.contacts = {};
-      this._userProfile = this.props.mozLoop.userProfile;
+      this._userProfile = navigator.mozLoop.userProfile;
     },
 
     componentDidMount: function() {
       window.addEventListener("LoopStatusChanged", this._onStatusChanged);
 
       this.refresh(err => {
         if (err) {
           throw err;
         }
 
-        let contactsAPI = this.props.mozLoop.contacts;
+        let contactsAPI = navigator.mozLoop.contacts;
 
         // Listen for contact changes/ updates.
         contactsAPI.on("add", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
         contactsAPI.on("remove", (eventName, contact) => {
           this.handleContactRemove(contact);
         });
@@ -409,17 +422,17 @@ loop.contacts = (function(_, mozL10n) {
       });
     },
 
     componentWillUnmount: function() {
       window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
     },
 
     _onStatusChanged: function() {
-      let profile = this.props.mozLoop.userProfile;
+      let profile = navigator.mozLoop.userProfile;
       let currUid = this._userProfile ? this._userProfile.uid : null;
       let newUid = profile ? profile.uid : null;
       if (currUid != newUid) {
         // On profile change (login, logout), reload all contacts.
         this._userProfile = profile;
         // The following will do a forceUpdate() for us.
         this.refresh();
       }
@@ -447,17 +460,17 @@ loop.contacts = (function(_, mozL10n) {
     handleContactRemoveAll: function() {
       // Do not allow any race conditions when removing all contacts.
       this.contacts = {};
       this.forceUpdate();
     },
 
     handleImportButtonClick: function() {
       this.setState({ importBusy: true });
-      this.props.mozLoop.startImport({
+      navigator.mozLoop.startImport({
         service: "google"
       }, (err, stats) => {
         this.setState({ importBusy: false });
         if (err) {
           console.error("Contact import error", err);
           this.props.notifications.errorL10n("import_contacts_failure_message");
           return;
         }
@@ -473,54 +486,54 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     handleContactAction: function(contact, actionName) {
       switch (actionName) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
         case "remove":
-          this.props.mozLoop.confirm({
+          navigator.mozLoop.confirm({
             message: mozL10n.get("confirm_delete_contact_alert"),
             okButton: mozL10n.get("confirm_delete_contact_remove_button"),
             cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
           }, (error, result) => {
             if (error) {
               throw error;
             }
 
             if (!result) {
               return;
             }
 
-            this.props.mozLoop.contacts.remove(contact._guid, err => {
+            navigator.mozLoop.contacts.remove(contact._guid, err => {
               if (err) {
                 throw err;
               }
             });
           });
           break;
         case "block":
         case "unblock":
           // Invoke the API named like the action.
-          this.props.mozLoop.contacts[actionName](contact._guid, err => {
+          navigator.mozLoop.contacts[actionName](contact._guid, err => {
             if (err) {
               throw err;
             }
           });
           break;
         case "video-call":
           if (!contact.blocked) {
-            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
+            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
             this.closeWindow();
           }
           break;
         case "audio-call":
           if (!contact.blocked) {
-            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
+            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
             this.closeWindow();
           }
           break;
         default:
           console.error("Unrecognized action: " + actionName);
           break;
       }
     },
@@ -536,17 +549,17 @@ loop.contacts = (function(_, mozL10n) {
       if (comp !== 0) {
         return comp;
       }
       // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
-    _renderContactsList: function() {
+    render: function() {
       let cx = React.addons.classSet;
 
       let viewForItem = item => {
         return (
           React.createElement(ContactDetail, {contact: item, 
                          handleContactAction: this.handleContactAction, 
                          key: item._guid})
         );
@@ -569,79 +582,51 @@ loop.contacts = (function(_, mozL10n) {
             shownContacts.available = shownContacts.available.filter(filterFn);
           }
           if (shownContacts.blocked) {
             shownContacts.blocked = shownContacts.blocked.filter(filterFn);
           }
         }
       }
 
-      if (shownContacts.available || shownContacts.blocked) {
-        return (
-          React.createElement("div", null, 
-            React.createElement("div", {className: "contact-list-title"}, 
-              mozL10n.get("contact_list_title")
-            ), 
-            React.createElement("ul", {className: "contact-list"}, 
-              shownContacts.available ?
-                shownContacts.available.sort(this.sortContacts).map(viewForItem) :
-                null, 
-              shownContacts.blocked && shownContacts.blocked.length > 0 ?
-                React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
-                null, 
-              shownContacts.blocked ?
-                shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
-                null
-            )
-          )
-        );
-      }
-
-      return (
-        React.createElement("div", {className: "contact-list-empty"}, 
-          React.createElement("p", {className: "panel-text-large"}, 
-            mozL10n.get("no_contacts_message_heading")
-          ), 
-          React.createElement("p", {className: "panel-text-medium"}, 
-            mozL10n.get("no_contacts_import_or_add")
-          )
-        )
-      );
-    },
-
-    render: function() {
-      let cx = React.addons.classSet;
-      let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
-                       MIN_CONTACTS_FOR_FILTERING;
-
       return (
         React.createElement("div", null, 
           React.createElement("div", {className: "content-area"}, 
             showFilter ?
             React.createElement("input", {className: "contact-filter", 
                    placeholder: mozL10n.get("contacts_search_placesholder"), 
                    valueLink: this.linkState("filter")})
             : null, 
             React.createElement(GravatarPromo, {handleUse: this.handleUseGravatar})
           ), 
-          this._renderContactsList(), 
+          React.createElement("ul", {className: "contact-list"}, 
+            shownContacts.available ?
+              shownContacts.available.sort(this.sortContacts).map(viewForItem) :
+              null, 
+            shownContacts.blocked && shownContacts.blocked.length > 0 ?
+              React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
+              null, 
+            shownContacts.blocked ?
+              shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
+              null
+          ), 
           React.createElement(ButtonGroup, {additionalClass: "contact-controls"}, 
             React.createElement(Button, {additionalClass: "secondary", 
-              caption: this.state.importBusy
-                ? mozL10n.get("importing_contacts_progress_button")
-                : mozL10n.get("import_contacts_button3"), 
-                disabled: this.state.importBusy, 
-                onClick: this.handleImportButtonClick}, 
-                React.createElement("div", {className: cx({"contact-import-spinner": true,
-                                   spinner: true,
-                busy: this.state.importBusy})})
+                    caption: this.state.importBusy
+                             ? mozL10n.get("importing_contacts_progress_button")
+                             : mozL10n.get("import_contacts_button3"), 
+                    disabled: this.state.importBusy, 
+                    onClick: this.handleImportButtonClick}, 
+              React.createElement("div", {className: cx({"contact-import-spinner": true,
+                                  spinner: true,
+                                  busy: this.state.importBusy})})
             ), 
             React.createElement(Button, {additionalClass: "primary", 
-              caption: mozL10n.get("new_contact_button"), 
-              onClick: this.handleAddContactButtonClick})
+                    caption: mozL10n.get("new_contact_button"), 
+                    onClick: this.handleAddContactButtonClick})
           )
         )
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({displayName: "ContactDetailsForm",
     mixins: [React.addons.LinkedStateMixin],
@@ -776,16 +761,14 @@ loop.contacts = (function(_, mozL10n) {
                     onClick: this.handleAcceptButtonClick})
           )
         )
       );
     }
   });
 
   return {
-    ContactDropdown: ContactDropdown,
     ContactsList: ContactsList,
-    ContactDetail: ContactDetail,
     ContactDetailsForm: ContactDetailsForm,
     _getPreferred: getPreferred,
     _setPreferred: setPreferred
   };
 })(_, document.mozL10n);
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -180,39 +180,57 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     onItemClick: function(event) {
       this.props.handleAction(event.currentTarget.dataset.action);
     },
 
     render: function() {
       var cx = React.addons.classSet;
+
       let blockAction = this.props.blocked ? "unblock" : "block";
       let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
                                           : "block_contact_menu_button";
 
       return (
         <ul className={cx({ "dropdown-menu": true,
                             "dropdown-menu-up": this.state.openDirUp })}>
           <li className={cx({ "dropdown-menu-item": true,
+                              "disabled": this.props.blocked })}
+              data-action="video-call"
+              onClick={this.onItemClick}>
+            <i className="icon icon-video-call" />
+            {mozL10n.get("video_call_menu_button")}
+          </li>
+          <li className={cx({ "dropdown-menu-item": true,
+                              "disabled": this.props.blocked })}
+              data-action="audio-call"
+              onClick={this.onItemClick}>
+            <i className="icon icon-audio-call" />
+            {mozL10n.get("audio_call_menu_button")}
+          </li>
+          <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
               data-action="edit"
               onClick={this.onItemClick}>
-            {mozL10n.get("edit_contact_title")}
+            <i className="icon icon-edit" />
+            {mozL10n.get("edit_contact_menu_button")}
           </li>
           <li className="dropdown-menu-item"
               data-action={blockAction}
               onClick={this.onItemClick}>
+            <i className={"icon icon-" + blockAction} />
             {mozL10n.get(blockLabel)}
           </li>
           <li className={cx({ "dropdown-menu-item": true,
                               "disabled": !this.props.canEdit })}
-              data-action="remove"
-              onClick={this.onItemClick}>
-            {mozL10n.get("confirm_delete_contact_remove_button")}
+               data-action="remove"
+               onClick={this.onItemClick}>
+            <i className="icon icon-remove" />
+            {mozL10n.get("remove_contact_menu_button2")}
           </li>
         </ul>
       );
     }
   });
 
   const ContactDetail = React.createClass({
     getInitialState: function() {
@@ -272,42 +290,38 @@ loop.contacts = (function(_, mozL10n) {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] != "google";
     },
 
     render: function() {
       let names = getContactNames(this.props.contact);
       let email = getPreferred(this.props.contact, "email");
-      let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
-      let avatarCSSClass = cx({
-        avatar: true,
-        defaultAvatar: !avatarSrc
-      });
 
       return (
         <li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
-          <div className={avatarCSSClass}>
-            {avatarSrc ? <img src={avatarSrc} /> : null}
+          <div className="avatar">
+            <img src={navigator.mozLoop.getUserAvatar(email.value)} />
           </div>
           <div className="details">
             <div className="username"><strong>{names.firstName}</strong> {names.lastName}
+              <i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
               <i className={cx({"icon icon-blocked": this.props.contact.blocked})} />
             </div>
             <div className="email">{email.value}</div>
           </div>
           <div className="icons">
-            <i className="icon icon-contact-video-call"
+            <i className="icon icon-video"
                onClick={this.handleAction.bind(null, "video-call")} />
-            <i className="icon icon-vertical-ellipsis"
+            <i className="icon icon-caret-down"
                onClick={this.showDropdownMenu} />
           </div>
           {this.state.showMenu
             ? <ContactDropdown blocked={this.props.contact.blocked}
                                canEdit={this.canEdit()}
                                handleAction={this.handleAction} />
             : null
           }
@@ -318,21 +332,20 @@ loop.contacts = (function(_, mozL10n) {
 
   const ContactsList = React.createClass({
     mixins: [
       React.addons.LinkedStateMixin,
       loop.shared.mixins.WindowCloseMixin
     ],
 
     propTypes: {
-      mozLoop: React.PropTypes.object.isRequired,
       notifications: React.PropTypes.instanceOf(
-                     loop.shared.models.NotificationCollection).isRequired,
-      // Callback to handle entry to the add/edit contact form.
-      startForm: React.PropTypes.func.isRequired
+        loop.shared.models.NotificationCollection).isRequired,
+        // Callback to handle entry to the add/edit contact form.
+        startForm: React.PropTypes.func.isRequired
     },
 
     /**
      * Contacts collection object
      */
     contacts: null,
 
     /**
@@ -343,17 +356,17 @@ loop.contacts = (function(_, mozL10n) {
     getInitialState: function() {
       return {
         importBusy: false,
         filter: ""
       };
     },
 
     refresh: function(callback = function() {}) {
-      let contactsAPI = this.props.mozLoop.contacts;
+      let contactsAPI = navigator.mozLoop.contacts;
 
       this.handleContactRemoveAll();
 
       contactsAPI.getAll((err, contacts) => {
         if (err) {
           callback(err);
           return;
         }
@@ -375,28 +388,28 @@ loop.contacts = (function(_, mozL10n) {
         addContactsInChunks(contacts);
       });
     },
 
     componentWillMount: function() {
       // Take the time to initialize class variables that are used outside
       // `this.state`.
       this.contacts = {};
-      this._userProfile = this.props.mozLoop.userProfile;
+      this._userProfile = navigator.mozLoop.userProfile;
     },
 
     componentDidMount: function() {
       window.addEventListener("LoopStatusChanged", this._onStatusChanged);
 
       this.refresh(err => {
         if (err) {
           throw err;
         }
 
-        let contactsAPI = this.props.mozLoop.contacts;
+        let contactsAPI = navigator.mozLoop.contacts;
 
         // Listen for contact changes/ updates.
         contactsAPI.on("add", (eventName, contact) => {
           this.handleContactAddOrUpdate(contact);
         });
         contactsAPI.on("remove", (eventName, contact) => {
           this.handleContactRemove(contact);
         });
@@ -409,17 +422,17 @@ loop.contacts = (function(_, mozL10n) {
       });
     },
 
     componentWillUnmount: function() {
       window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
     },
 
     _onStatusChanged: function() {
-      let profile = this.props.mozLoop.userProfile;
+      let profile = navigator.mozLoop.userProfile;
       let currUid = this._userProfile ? this._userProfile.uid : null;
       let newUid = profile ? profile.uid : null;
       if (currUid != newUid) {
         // On profile change (login, logout), reload all contacts.
         this._userProfile = profile;
         // The following will do a forceUpdate() for us.
         this.refresh();
       }
@@ -447,17 +460,17 @@ loop.contacts = (function(_, mozL10n) {
     handleContactRemoveAll: function() {
       // Do not allow any race conditions when removing all contacts.
       this.contacts = {};
       this.forceUpdate();
     },
 
     handleImportButtonClick: function() {
       this.setState({ importBusy: true });
-      this.props.mozLoop.startImport({
+      navigator.mozLoop.startImport({
         service: "google"
       }, (err, stats) => {
         this.setState({ importBusy: false });
         if (err) {
           console.error("Contact import error", err);
           this.props.notifications.errorL10n("import_contacts_failure_message");
           return;
         }
@@ -473,54 +486,54 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     handleContactAction: function(contact, actionName) {
       switch (actionName) {
         case "edit":
           this.props.startForm("contacts_edit", contact);
           break;
         case "remove":
-          this.props.mozLoop.confirm({
+          navigator.mozLoop.confirm({
             message: mozL10n.get("confirm_delete_contact_alert"),
             okButton: mozL10n.get("confirm_delete_contact_remove_button"),
             cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
           }, (error, result) => {
             if (error) {
               throw error;
             }
 
             if (!result) {
               return;
             }
 
-            this.props.mozLoop.contacts.remove(contact._guid, err => {
+            navigator.mozLoop.contacts.remove(contact._guid, err => {
               if (err) {
                 throw err;
               }
             });
           });
           break;
         case "block":
         case "unblock":
           // Invoke the API named like the action.
-          this.props.mozLoop.contacts[actionName](contact._guid, err => {
+          navigator.mozLoop.contacts[actionName](contact._guid, err => {
             if (err) {
               throw err;
             }
           });
           break;
         case "video-call":
           if (!contact.blocked) {
-            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
+            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
             this.closeWindow();
           }
           break;
         case "audio-call":
           if (!contact.blocked) {
-            this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
+            navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
             this.closeWindow();
           }
           break;
         default:
           console.error("Unrecognized action: " + actionName);
           break;
       }
     },
@@ -536,17 +549,17 @@ loop.contacts = (function(_, mozL10n) {
       if (comp !== 0) {
         return comp;
       }
       // If names are equal, compare against unique ids to make sure we have
       // consistent ordering.
       return contact1._guid - contact2._guid;
     },
 
-    _renderContactsList: function() {
+    render: function() {
       let cx = React.addons.classSet;
 
       let viewForItem = item => {
         return (
           <ContactDetail contact={item}
                          handleContactAction={this.handleContactAction}
                          key={item._guid} />
         );
@@ -569,79 +582,51 @@ loop.contacts = (function(_, mozL10n) {
             shownContacts.available = shownContacts.available.filter(filterFn);
           }
           if (shownContacts.blocked) {
             shownContacts.blocked = shownContacts.blocked.filter(filterFn);
           }
         }
       }
 
-      if (shownContacts.available || shownContacts.blocked) {
-        return (
-          <div>
-            <div className="contact-list-title">
-              {mozL10n.get("contact_list_title")}
-            </div>
-            <ul className="contact-list">
-              {shownContacts.available ?
-                shownContacts.available.sort(this.sortContacts).map(viewForItem) :
-                null}
-              {shownContacts.blocked && shownContacts.blocked.length > 0 ?
-                <div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
-                null}
-              {shownContacts.blocked ?
-                shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
-                null}
-            </ul>
-          </div>
-        );
-      }
-
-      return (
-        <div className="contact-list-empty">
-          <p className="panel-text-large">
-            {mozL10n.get("no_contacts_message_heading")}
-          </p>
-          <p className="panel-text-medium">
-            {mozL10n.get("no_contacts_import_or_add")}
-          </p>
-        </div>
-      );
-    },
-
-    render: function() {
-      let cx = React.addons.classSet;
-      let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
-                       MIN_CONTACTS_FOR_FILTERING;
-
       return (
         <div>
           <div className="content-area">
             {showFilter ?
             <input className="contact-filter"
                    placeholder={mozL10n.get("contacts_search_placesholder")}
                    valueLink={this.linkState("filter")} />
             : null }
             <GravatarPromo handleUse={this.handleUseGravatar}/>
           </div>
-          {this._renderContactsList()}
+          <ul className="contact-list">
+            {shownContacts.available ?
+              shownContacts.available.sort(this.sortContacts).map(viewForItem) :
+              null}
+            {shownContacts.blocked && shownContacts.blocked.length > 0 ?
+              <div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
+              null}
+            {shownContacts.blocked ?
+              shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
+              null}
+          </ul>
           <ButtonGroup additionalClass="contact-controls">
             <Button additionalClass="secondary"
-              caption={this.state.importBusy
-                ? mozL10n.get("importing_contacts_progress_button")
-                : mozL10n.get("import_contacts_button3")}
-                disabled={this.state.importBusy}
-                onClick={this.handleImportButtonClick} >
-                <div className={cx({"contact-import-spinner": true,
-                                   spinner: true,
-                busy: this.state.importBusy})} />
+                    caption={this.state.importBusy
+                             ? mozL10n.get("importing_contacts_progress_button")
+                             : mozL10n.get("import_contacts_button3")}
+                    disabled={this.state.importBusy}
+                    onClick={this.handleImportButtonClick} >
+              <div className={cx({"contact-import-spinner": true,
+                                  spinner: true,
+                                  busy: this.state.importBusy})} />
             </Button>
             <Button additionalClass="primary"
-              caption={mozL10n.get("new_contact_button")}
-              onClick={this.handleAddContactButtonClick} />
+                    caption={mozL10n.get("new_contact_button")}
+                    onClick={this.handleAddContactButtonClick} />
           </ButtonGroup>
         </div>
       );
     }
   });
 
   const ContactDetailsForm = React.createClass({
     mixins: [React.addons.LinkedStateMixin],
@@ -776,16 +761,14 @@ loop.contacts = (function(_, mozL10n) {
                     onClick={this.handleAcceptButtonClick} />
           </ButtonGroup>
         </div>
       );
     }
   });
 
   return {
-    ContactDropdown: ContactDropdown,
     ContactsList: ContactsList,
-    ContactDetail: ContactDetail,
     ContactDetailsForm: ContactDetailsForm,
     _getPreferred: getPreferred,
     _setPreferred: setPreferred
   };
 })(_, document.mozL10n);
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -306,42 +306,35 @@ loop.panel = (function(_, mozL10n) {
   });
 
   /**
    * Panel settings (gear) menu entry.
    */
   var SettingsDropdownEntry = React.createClass({displayName: "SettingsDropdownEntry",
     propTypes: {
       displayed: React.PropTypes.bool,
-      extraCSSClass: React.PropTypes.string,
+      icon: React.PropTypes.string,
       label: React.PropTypes.string.isRequired,
       onClick: React.PropTypes.func.isRequired
     },
 
     getDefaultProps: function() {
       return {displayed: true};
     },
 
     render: function() {
-      var cx = React.addons.classSet;
-
       if (!this.props.displayed) {
         return null;
       }
-
-      var extraCSSClass = {
-        "dropdown-menu-item": true
-      };
-      if (this.props.extraCSSClass) {
-        extraCSSClass[this.props.extraCSSClass] = true;
-      }
-
       return (
-        React.createElement("li", {className: cx(extraCSSClass), onClick: this.props.onClick}, 
-          this.props.label
+        React.createElement("li", {className: "dropdown-menu-item", onClick: this.props.onClick}, 
+          this.props.icon ?
+            React.createElement("i", {className: "icon icon-" + this.props.icon}) :
+            null, 
+          React.createElement("span", null, this.props.label)
         )
       );
     }
   });
 
   /**
    * Panel settings (gear) menu.
    */
@@ -382,43 +375,42 @@ loop.panel = (function(_, mozL10n) {
 
     openGettingStartedTour: function() {
       this.props.mozLoop.openGettingStartedTour("settings-menu");
       this.closeWindow();
     },
 
     render: function() {
       var cx = React.addons.classSet;
-      var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" :
-                                                      "entry-settings-signin";
 
       return (
         React.createElement("div", {className: "settings-menu dropdown"}, 
           React.createElement("button", {className: "button-settings", 
              onClick: this.toggleDropdownMenu, 
              ref: "menu-button", 
              title: mozL10n.get("settings_menu_button_tooltip")}), 
           React.createElement("ul", {className: cx({"dropdown-menu": true, hide: !this.state.showMenu})}, 
-            React.createElement(SettingsDropdownEntry, {
-                displayed: this._isSignedIn() && this.props.mozLoop.fxAEnabled, 
-                extraCSSClass: "entry-settings-account", 
-                label: mozL10n.get("settings_menu_item_account"), 
-                onClick: this.handleClickAccountEntry}), 
             React.createElement(SettingsDropdownEntry, {displayed: false, 
+                                   icon: "settings", 
                                    label: mozL10n.get("settings_menu_item_settings"), 
                                    onClick: this.handleClickSettingsEntry}), 
-            React.createElement(SettingsDropdownEntry, {label: mozL10n.get("tour_label"), 
+            React.createElement(SettingsDropdownEntry, {displayed: this._isSignedIn() && this.props.mozLoop.fxAEnabled, 
+                                   icon: "account", 
+                                   label: mozL10n.get("settings_menu_item_account"), 
+                                   onClick: this.handleClickAccountEntry}), 
+            React.createElement(SettingsDropdownEntry, {icon: "tour", 
+                                   label: mozL10n.get("tour_label"), 
                                    onClick: this.openGettingStartedTour}), 
             React.createElement(SettingsDropdownEntry, {displayed: this.props.mozLoop.fxAEnabled, 
-                                   extraCSSClass: accountEntryCSSClass, 
+                                   icon: this._isSignedIn() ? "signout" : "signin", 
                                    label: this._isSignedIn() ?
                                           mozL10n.get("settings_menu_item_signout") :
                                           mozL10n.get("settings_menu_item_signin"), 
                                    onClick: this.handleClickAuthEntry}), 
-            React.createElement(SettingsDropdownEntry, {extraCSSClass: "entry-settings-help", 
+            React.createElement(SettingsDropdownEntry, {icon: "help", 
                                    label: mozL10n.get("help_label"), 
                                    onClick: this.handleHelpEntry})
           )
         )
       );
     }
   });
 
@@ -950,20 +942,20 @@ loop.panel = (function(_, mozL10n) {
             selectedTab: this.props.selectedTab}, 
             React.createElement(Tab, {name: "rooms"}, 
               React.createElement(RoomList, {dispatcher: this.props.dispatcher, 
                         mozLoop: this.props.mozLoop, 
                         store: this.props.roomStore, 
                         userProfile: this.state.userProfile})
             ), 
             React.createElement(Tab, {name: "contacts"}, 
-              React.createElement(ContactsList, {mozLoop: this.props.mozLoop, 
-                            notifications: this.props.notifications, 
-                            selectTab: this.selectTab, 
-                            startForm: this.startForm})
+              React.createElement(ContactsList, {
+                notifications: this.props.notifications, 
+                selectTab: this.selectTab, 
+                startForm: this.startForm})
             ), 
             React.createElement(Tab, {hidden: true, name: "contacts_add"}, 
               React.createElement(ContactDetailsForm, {
                 mode: "add", 
                 ref: "contacts_add", 
                 selectTab: this.selectTab})
             ), 
             React.createElement(Tab, {hidden: true, name: "contacts_edit"}, 
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -306,42 +306,35 @@ loop.panel = (function(_, mozL10n) {
   });
 
   /**
    * Panel settings (gear) menu entry.
    */
   var SettingsDropdownEntry = React.createClass({
     propTypes: {
       displayed: React.PropTypes.bool,
-      extraCSSClass: React.PropTypes.string,
+      icon: React.PropTypes.string,
       label: React.PropTypes.string.isRequired,
       onClick: React.PropTypes.func.isRequired
     },
 
     getDefaultProps: function() {
       return {displayed: true};
     },
 
     render: function() {
-      var cx = React.addons.classSet;
-
       if (!this.props.displayed) {
         return null;
       }
-
-      var extraCSSClass = {
-        "dropdown-menu-item": true
-      };
-      if (this.props.extraCSSClass) {
-        extraCSSClass[this.props.extraCSSClass] = true;
-      }
-
       return (
-        <li className={cx(extraCSSClass)} onClick={this.props.onClick}>
-          {this.props.label}
+        <li className="dropdown-menu-item" onClick={this.props.onClick}>
+          {this.props.icon ?
+            <i className={"icon icon-" + this.props.icon}></i> :
+            null}
+          <span>{this.props.label}</span>
         </li>
       );
     }
   });
 
   /**
    * Panel settings (gear) menu.
    */
@@ -382,43 +375,42 @@ loop.panel = (function(_, mozL10n) {
 
     openGettingStartedTour: function() {
       this.props.mozLoop.openGettingStartedTour("settings-menu");
       this.closeWindow();
     },
 
     render: function() {
       var cx = React.addons.classSet;
-      var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" :
-                                                      "entry-settings-signin";
 
       return (
         <div className="settings-menu dropdown">
           <button className="button-settings"
              onClick={this.toggleDropdownMenu}
              ref="menu-button"
              title={mozL10n.get("settings_menu_button_tooltip")} />
           <ul className={cx({"dropdown-menu": true, hide: !this.state.showMenu})}>
-            <SettingsDropdownEntry
-                displayed={this._isSignedIn() && this.props.mozLoop.fxAEnabled}
-                extraCSSClass="entry-settings-account"
-                label={mozL10n.get("settings_menu_item_account")}
-                onClick={this.handleClickAccountEntry} />
             <SettingsDropdownEntry displayed={false}
+                                   icon="settings"
                                    label={mozL10n.get("settings_menu_item_settings")}
                                    onClick={this.handleClickSettingsEntry} />
-            <SettingsDropdownEntry label={mozL10n.get("tour_label")}
+            <SettingsDropdownEntry displayed={this._isSignedIn() && this.props.mozLoop.fxAEnabled}
+                                   icon="account"
+                                   label={mozL10n.get("settings_menu_item_account")}
+                                   onClick={this.handleClickAccountEntry} />
+            <SettingsDropdownEntry icon="tour"
+                                   label={mozL10n.get("tour_label")}
                                    onClick={this.openGettingStartedTour} />
             <SettingsDropdownEntry displayed={this.props.mozLoop.fxAEnabled}
-                                   extraCSSClass={accountEntryCSSClass}
+                                   icon={this._isSignedIn() ? "signout" : "signin"}
                                    label={this._isSignedIn() ?
                                           mozL10n.get("settings_menu_item_signout") :
                                           mozL10n.get("settings_menu_item_signin")}
                                    onClick={this.handleClickAuthEntry} />
-            <SettingsDropdownEntry extraCSSClass="entry-settings-help"
+            <SettingsDropdownEntry icon="help"
                                    label={mozL10n.get("help_label")}
                                    onClick={this.handleHelpEntry} />
           </ul>
         </div>
       );
     }
   });
 
@@ -950,20 +942,20 @@ loop.panel = (function(_, mozL10n) {
             selectedTab={this.props.selectedTab}>
             <Tab name="rooms">
               <RoomList dispatcher={this.props.dispatcher}
                         mozLoop={this.props.mozLoop}
                         store={this.props.roomStore}
                         userProfile={this.state.userProfile} />
             </Tab>
             <Tab name="contacts">
-              <ContactsList mozLoop={this.props.mozLoop}
-                            notifications={this.props.notifications}
-                            selectTab={this.selectTab}
-                            startForm={this.startForm} />
+              <ContactsList
+                notifications={this.props.notifications}
+                selectTab={this.selectTab}
+                startForm={this.startForm} />
             </Tab>
             <Tab hidden={true} name="contacts_add">
               <ContactDetailsForm
                 mode="add"
                 ref="contacts_add"
                 selectTab={this.selectTab} />
             </Tab>
             <Tab hidden={true} name="contacts_edit">
--- a/browser/components/loop/content/shared/css/common.css
+++ b/browser/components/loop/content/shared/css/common.css
@@ -331,26 +331,16 @@ p {
 .icon-audio,
 .icon-video {
   background-size: 20px;
   background-repeat: no-repeat;
   vertical-align: top;
   background-position: 80% center;
 }
 
-.pseudo-icon:before {
-  content: "";
-  display: inline-block;
-  background-repeat: no-repeat;
-  width: 14px;
-  height: 14px;
-  vertical-align: top;
-  margin: 0 .7rem;
-}
-
 .icon-small {
   background-size: 10px;
 }
 
 .icon-video {
   background-image: url("../img/video-inverse-14x14.png");
 }
 
@@ -424,61 +414,46 @@ p {
 .dropdown {
   position: relative;
 }
 
 .dropdown-menu {
   position: absolute;
   bottom: 0;
   left: 0;
-  background-color: #fbfbfb;
+  background-color: #fdfdfd;
   box-shadow: 0 1px 3px rgba(0,0,0,.3);
   list-style: none;
   border-radius: 2px;
 }
 
 html[dir="rtl"] .dropdown-menu {
   left: auto;
   right: 0;
 }
 
 .dropdown-menu-item {
   width: 100%;
   text-align: start;
-  padding: .3rem .8rem;
+  padding: .5em 15px;
   cursor: pointer;
   border: 1px solid transparent;
-  font-size: 1.2rem;
-  line-height: 22px;
+  font-size: 1em;
   white-space: nowrap;
-  color: #4a4a4a;
-}
-
-.dropdown-menu-item:first-child {
-  padding-top: .8rem;
-}
-
-.dropdown-menu-item:last-child {
-  padding-bottom: .8rem;
-}
-
-.dropdown-menu-item:first-child:hover {
-  border-top-right-radius: 2px;
-  border-top-left-radius: 2px;
-}
-
-.dropdown-menu-item:last-child {
-  border-bottom-right-radius: 2px;
-  border-bottom-left-radius: 2px;
 }
 
 .dropdown-menu-item:hover {
   background-color: #dbf7ff;
 }
 
+.dropdown-menu-item > .icon {
+  background-repeat: no-repeat;
+  display: inline-block;
+}
+
 .dropdown-menu-separator {
   height: 1px;
   margin: 2px -2px 1px -2px;
   border-top: 1px solid #dedede;
   background-color: #fff;
 }
 
 /* Custom checkbox */
deleted file mode 100644
--- a/browser/components/loop/content/shared/img/avatars.svg
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
-  <style>
-    use:not(:target) {
-      display: none;
-    }
-    use {
-      fill: #ccc;
-    }
-    use[id$="-hover"] {
-      fill: #444;
-    }
-    use[id$="-active"] {
-      fill: #0095dd;
-    }
-    use[id$="-white"] {
-      fill: #fff;
-    }
-  </style>
-  <defs>
-    <g id="blue" transform="translate(-2588 -413) translate(2588 413)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="orange" transform="translate(-2638 -317) translate(2638 317)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="mintgreen" transform="translate(-2588 -317) translate(2588 317)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="lightpink" transform="translate(-2687 -366) translate(2687 366)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="grey" transform="translate(-2736 -366) translate(2736 366)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="yellow" transform="translate(-2732 -317) translate(2732 317)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="purple" transform="translate(-2588 -366) translate(2588 366)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="lightgreen" transform="translate(-2686 -317) translate(2686 317)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="darkblue" transform="translate(-2686 -413) translate(2686 413)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="darkpink" transform="translate(-2638 -413) translate(2638 413)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="brown" transform="translate(-2736 -413) translate(2736 413)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-    <g id="green" transform="translate(-2638 -366) translate(2637.857 366)" fill="none">
-      <path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
-    </g>
-  </defs>
-  <use id="blue-avatar" xlink:href="#blue"/>
-  <use id="orange-avatar" xlink:href="#orange"/>
-  <use id="mintgreen-avatar" xlink:href="#mintgreen"/>
-  <use id="lightpink-avatar" xlink:href="#lightpink"/>
-  <use id="grey-avatar" xlink:href="#grey"/>
-  <use id="yellow-avatar" xlink:href="#yellow"/>
-  <use id="purple-avatar" xlink:href="#purple"/>
-  <use id="lightgreen-avatar" xlink:href="#lightgreen"/>
-  <use id="darkblue-avatar" xlink:href="#darkblue"/>
-  <use id="darkpink-avatar" xlink:href="#darkpink"/>
-  <use id="brown-avatar" xlink:href="#brown"/>
-  <use id="green-avatar" xlink:href="#green"/>
-</svg>
deleted file mode 100644
--- a/browser/components/loop/content/shared/img/ellipsis-v.svg
+++ /dev/null
@@ -1,1 +0,0 @@
-<svg width="4" height="20" viewBox="0 0 4 20" xmlns="http://www.w3.org/2000/svg"><g fill="#3A99DA"><ellipse cx="2" cy="2" rx="2" ry="2"/><ellipse cx="2" cy="10" rx="2" ry="2"/><ellipse cx="2" cy="18" rx="2" ry="2"/></g></svg>
\ No newline at end of file
deleted file mode 100644
--- a/browser/components/loop/content/shared/img/empty_contacts.svg
+++ /dev/null
@@ -1,1 +0,0 @@
-<svg width="117" height="91" viewBox="0 0 117 91" xmlns="http://www.w3.org/2000/svg"><g fill="#D8D8D8"><path d="M116.431 59.357c-4.793 5.459-11.824 8.905-19.66 8.905-7.222 0-13.761-2.927-18.494-7.66l-.278-.282c4.116-6.441 11.33-10.712 19.541-10.712 7.795 0 14.69 3.848 18.891 9.749zm-18.891-12.736c6.799 0 12.311-5.512 12.311-12.311s-5.512-12.311-12.311-12.311-12.311 5.512-12.311 12.311 5.512 12.311 12.311 12.311zM38.431 59.357c-4.793 5.459-11.824 8.905-19.66 8.905-7.222 0-13.761-2.927-18.494-7.66l-.278-.282c4.116-6.441 11.33-10.712 19.541-10.712 7.795 0 14.69 3.848 18.891 9.749zm-18.891-12.736c6.799 0 12.311-5.512 12.311-12.311s-5.512-12.311-12.311-12.311-12.311 5.512-12.311 12.311 5.512 12.311 12.311 12.311z" id="Mask-Copy-5" fill-opacity=".8"/><path d="M91.495 70.608c-8.418 9.588-20.766 15.639-34.528 15.639-12.684 0-24.167-5.141-32.479-13.453l-.488-.495c7.229-11.312 19.898-18.812 34.318-18.812 13.689 0 25.8 6.759 33.177 17.121zm-33.177-22.367c11.941 0 21.621-9.68 21.621-21.621 0-11.941-9.68-21.621-21.621-21.621-11.941 0-21.621 9.68-21.621 21.621 0 11.941 9.68 21.621 21.621 21.621z" stroke="#FBFBFB" stroke-width="4"/></g></svg>
\ No newline at end of file
--- a/browser/components/loop/content/shared/img/icons-14x14.svg
+++ b/browser/components/loop/content/shared/img/icons-14x14.svg
@@ -30,17 +30,17 @@
     <path id="incoming-shape" fill-rule="evenodd" d="M2.745,7.558l0.637,0.669c0.04,0.041,0.085,0.073,0.134,0.1 l3.249,3.313c0.38,0.393,0.915,0.478,1.197,0.186l0.638-0.676c0.281-0.292,0.2-0.848-0.18-1.244L7.097,8.558h3.566 c0.419,0,0.759-0.34,0.759-0.759V6.28c0-0.419-0.34-0.759-0.759-0.759H7.059l1.42-1.443c0.381-0.392,0.461-0.945,0.18-1.234 l-0.637-0.67C7.74,1.883,7.204,1.966,6.824,2.359L3.55,5.688C3.487,5.717,3.43,5.755,3.381,5.806L2.745,6.482 c-0.131,0.137-0.183,0.332-0.162,0.54C2.562,7.229,2.613,7.423,2.745,7.558z"/>
     <path id="link-shape" fill-rule="evenodd" d="M7.359,6.107c0.757-0.757,0.757-1.995,0-2.752 L5.573,1.568c-0.757-0.757-1.995-0.757-2.752,0L1.568,2.82c-0.757,0.757-0.757,1.995,0,2.752l1.787,1.787 c0.757,0.757,1.995,0.757,2.752,0L6.266,7.2L6.8,7.734L6.641,7.893c-0.757,0.757-0.757,1.995,0,2.752l1.787,1.787 c0.757,0.757,1.995,0.757,2.752,0l1.253-1.253c0.757-0.757,0.757-1.995,0-2.752l-1.787-1.787c-0.757-0.757-1.995-0.757-2.752,0 L7.734,6.8L7.2,6.266L7.359,6.107z M9.87,7.868l1.335,1.335c0.294,0.294,0.294,0.774,0,1.068l-0.934,0.934 c-0.294,0.294-0.774,0.294-1.068,0L7.868,9.87c-0.294-0.294-0.294-0.774,0-1.068L8.13,9.064c0.294,0.294,0.744,0.324,1.001,0.067 C9.388,8.874,9.358,8.424,9.064,8.13L8.802,7.868C9.096,7.574,9.577,7.574,9.87,7.868z M4.13,6.132L2.795,4.797 c-0.294-0.294-0.294-0.774,0-1.068l0.934-0.934c0.294-0.294,0.774-0.294,1.068,0L6.132,4.13c0.294,0.294,0.294,0.774,0,1.068 L5.86,4.926C5.567,4.632,5.116,4.602,4.859,4.859C4.602,5.116,4.632,5.567,4.926,5.86l0.272,0.272 C4.904,6.426,4.423,6.426,4.13,6.132z"/>
     <g id="mute-shape">
       <path fill-rule="evenodd" d="M5.186,9.492L5.49,9.188l3.822-3.822l2.354-2.354l-0.848-0.848 L9.312,3.669V3.142C9.312,1.959,8.352,1,7.169,1C5.986,1,5.026,1.959,5.026,3.142v4.715c0,0.032,0.001,0.064,0.002,0.096 L4.643,8.338c-0.03-0.156-0.046-0.317-0.046-0.481V6.142H3.741v1.715c0,0.414,0.073,0.81,0.208,1.176l-1.615,1.615l0.848,0.848 l1.398-1.398v0L5.186,9.492z"/>
       <path fill-rule="evenodd" d="M9.312,7.857V6.045L5.829,9.528C6.196,9.824,6.662,10,7.169,10 C8.352,10,9.312,9.04,9.312,7.857z"/>
       <path fill-rule="evenodd" d="M9.741,7.857c0,1.42-1.151,2.572-2.572,2.572 c-0.625,0-1.199-0.223-1.645-0.595l-0.605,0.605c0.395,0.344,0.87,0.599,1.393,0.734v0.97H5.884c-0.56,0-1.034,0.359-1.212,0.858 h4.994c-0.178-0.499-0.652-0.858-1.212-0.858H8.026v-0.97c1.478-0.38,2.572-1.718,2.572-3.316V6.142H9.741V7.857z"/>
     </g>
     <path id="pause-shape" fill-rule="evenodd" d="M4.75,1h-1.5C2.836,1,2.5,1.336,2.5,1.75v10.5 C2.5,12.664,2.836,13,3.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C5.5,1.336,5.164,1,4.75,1z M10.75,1h-1.5 C8.836,1,8.5,1.336,8.5,1.75v10.5C8.5,12.664,8.836,13,9.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C11.5,1.336,11.164,1,10.75,1 z"/>
-    <path id="video-shape" d="M1.59247473,11.4075253 C1.9956945,11.7983901 2.46237364,12 3.02957694,12 L7.98333957,12 C8.53762636,12 9.01666043,11.7983901 9.40752527,11.4075253 C9.81130663,11.0043055 10,10.5376264 10,9.97042306 L10,8.81074504 L12.8360165,11.6467615 C12.9247473,11.7354923 13.0252714,11.7731187 13.1516286,11.7731187 C13.2145264,11.7731187 13.2650693,11.7607638 13.3279671,11.7354923 C13.517222,11.659678 13.6053912,11.5209659 13.6053912,11.319356 L13.6053912,3.66772744 C13.6053912,3.47903407 13.517222,3.34032198 13.3279671,3.25215275 C13.2650693,3.23923624 13.2145264,3.22688132 13.1516286,3.22688132 C13.0252714,3.22688132 12.9247473,3.26450768 12.8360165,3.3526769 L10,6.17633845 L10,5.01666043 C10,4.46181206 9.81130663,3.98333957 9.40752527,3.59247473 C9.01666043,3.18869337 8.53762636,3 7.98333957,3 L3.02957694,3 C2.46237364,3 1.9956945,3.18869337 1.59247473,3.59247473 C1.20160988,3.98333957 1,4.46181206 1,5.01666043 L1,9.97042306 C1,10.5376264 1.20160988,11.0043055 1.59247473,11.4075253" fill="#fff" fill-rule="evenodd"/>
+    <path id="video-shape" fill-rule="evenodd" d="M12.175,3.347L9.568,5.651V3.905c0-0.657-0.497-1.19-1.111-1.19 H2.111C1.498,2.714,1,3.247,1,3.905v6.191c0,0.658,0.498,1.19,1.111,1.19h6.345c0.614,0,1.111-0.533,1.111-1.19V8.322l2.607,2.305 C12.4,10.867,12.71,10.938,13,10.874V3.099C12.71,3.035,12.4,3.106,12.175,3.347z"/>
     <g id="volume-shape">
       <path fill-rule="evenodd" d="M3.513,4.404H1.896c-0.417,0-0.756,0.338-0.756,0.755v3.679 c0,0.417,0.338,0.755,0.756,0.755H3.51l2.575,2.575c0.261,0.261,0.596,0.4,0.938,0.422V1.409C6.682,1.431,6.346,1.57,6.085,1.831 L3.513,4.404z M8.555,5.995C8.619,6.32,8.653,6.656,8.653,7c0,0.344-0.034,0.679-0.098,1.004l0.218,0.142 C8.852,7.777,8.895,7.393,8.895,7c0-0.394-0.043-0.777-0.123-1.147L8.555,5.995z M12.224,3.6l-0.475,0.31 c0.359,0.962,0.557,2.003,0.557,3.09c0,1.087-0.198,2.128-0.557,3.09l0.475,0.31c0.41-1.054,0.635-2.201,0.635-3.4 C12.859,5.8,12.634,4.654,12.224,3.6z M10.061,5.012C10.25,5.642,10.353,6.308,10.353,7c0,0.691-0.103,1.358-0.293,1.987 l0.351,0.229C10.634,8.517,10.756,7.772,10.756,7c0-0.773-0.121-1.517-0.345-2.216L10.061,5.012z"/>
       <path d="M7.164,12.74l-0.15-0.009c-0.389-0.024-0.754-0.189-1.028-0.463L3.452,9.735H1.896 C1.402,9.735,1,9.333,1,8.838V5.16c0-0.494,0.402-0.896,0.896-0.896h1.558l2.531-2.531C6.26,1.458,6.625,1.293,7.014,1.269 l0.15-0.009V12.74z M1.896,4.545c-0.339,0-0.615,0.276-0.615,0.615v3.679c0,0.339,0.276,0.615,0.615,0.615h1.672l2.616,2.616 c0.19,0.19,0.434,0.316,0.697,0.363V1.568C6.619,1.615,6.375,1.741,6.185,1.931L3.571,4.545H1.896z M12.292,10.612l-0.714-0.467 l0.039-0.105C11.981,9.067,12.165,8.044,12.165,7c0-1.044-0.184-2.067-0.548-3.041l-0.039-0.105l0.714-0.467l0.063,0.162 C12.783,4.649,13,5.81,13,7s-0.217,2.351-0.645,3.451L12.292,10.612z M11.92,10.033l0.234,0.153 c0.374-1.019,0.564-2.09,0.564-3.186s-0.19-2.167-0.564-3.186L11.92,3.966C12.27,4.94,12.447,5.96,12.447,7 C12.447,8.04,12.27,9.059,11.92,10.033z M10.489,9.435L9.895,9.047l0.031-0.101C10.116,8.315,10.212,7.66,10.212,7 c0-0.661-0.096-1.316-0.287-1.947L9.895,4.952l0.594-0.388l0.056,0.176C10.779,5.471,10.897,6.231,10.897,7 c0,0.769-0.118,1.529-0.351,2.259L10.489,9.435z M10.225,8.926l0.106,0.069C10.52,8.348,10.615,7.677,10.615,7 c0-0.677-0.095-1.348-0.284-1.996l-0.106,0.07C10.403,5.699,10.494,6.347,10.494,7C10.494,7.652,10.403,8.3,10.225,8.926z M8.867,8.376L8.398,8.07l0.018-0.093C8.48,7.654,8.512,7.325,8.512,7S8.48,6.345,8.417,6.022L8.398,5.929l0.469-0.306l0.043,0.2 C8.994,6.211,9.036,6.607,9.036,7c0,0.393-0.042,0.789-0.126,1.176L8.867,8.376z"/>
     </g>
   </defs>
   <use id="audio" xlink:href="#audio-shape"/>
   <use id="audio-active" xlink:href="#audio-shape"/>
   <use id="audio-disabled" xlink:href="#audio-shape"/>
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -67,19 +67,16 @@ browser.jar:
   content/browser/loop/shared/img/movistar.png                  (content/shared/img/movistar.png)
   content/browser/loop/shared/img/movistar@2x.png               (content/shared/img/movistar@2x.png)
   content/browser/loop/shared/img/vivo.png                      (content/shared/img/vivo.png)
   content/browser/loop/shared/img/vivo@2x.png                   (content/shared/img/vivo@2x.png)
   content/browser/loop/shared/img/02.png                        (content/shared/img/02.png)
   content/browser/loop/shared/img/02@2x.png                     (content/shared/img/02@2x.png)
   content/browser/loop/shared/img/telefonica.png                (content/shared/img/telefonica.png)
   content/browser/loop/shared/img/telefonica@2x.png             (content/shared/img/telefonica@2x.png)
-  content/browser/loop/shared/img/ellipsis-v.svg                (content/shared/img/ellipsis-v.svg)
-  content/browser/loop/shared/img/empty_contacts.svg            (content/shared/img/empty_contacts.svg)
-  content/browser/loop/shared/img/avatars.svg                   (content/shared/img/avatars.svg)
 
   # Shared scripts
   content/browser/loop/shared/js/actions.js             (content/shared/js/actions.js)
   content/browser/loop/shared/js/conversationStore.js   (content/shared/js/conversationStore.js)
   content/browser/loop/shared/js/store.js               (content/shared/js/store.js)
   content/browser/loop/shared/js/roomStates.js          (content/shared/js/roomStates.js)
   content/browser/loop/shared/js/fxOSActiveRoomStore.js (content/shared/js/fxOSActiveRoomStore.js)
   content/browser/loop/shared/js/activeRoomStore.js     (content/shared/js/activeRoomStore.js)
--- a/browser/components/loop/modules/MozLoopAPI.jsm
+++ b/browser/components/loop/modules/MozLoopAPI.jsm
@@ -893,30 +893,31 @@ function injectLoopAPI(targetWindow) {
 
         request.send();
       }
     },
 
     /**
      * Compose a URL pointing to the location of an avatar by email address.
      * At the moment we use the Gravatar service to match email addresses with
-     * avatars. If no email address is found we return null.
+     * avatars. This might change in the future as avatars might come from another
+     * source.
      *
      * @param {String} emailAddress Users' email address
      * @param {Number} size         Size of the avatar image to return in pixels.
      *                              Optional. Default value: 40.
-     * @return the URL pointing to an avatar matching the provided email address
-     *         or null if this is not available.
+     * @return the URL pointing to an avatar matching the provided email address.
      */
     getUserAvatar: {
       enumerable: true,
       writable: true,
       value: function(emailAddress, size = 40) {
+        const kEmptyGif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
         if (!emailAddress || !MozLoopService.getLoopPref("contacts.gravatars.show")) {
-          return null;
+          return kEmptyGif;
         }
 
         // Do the MD5 dance.
         let hasher = Cc["@mozilla.org/security/hash;1"]
                        .createInstance(Ci.nsICryptoHash);
         hasher.init(Ci.nsICryptoHash.MD5);
         let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
                              .createInstance(Ci.nsIStringInputStream);
--- a/browser/components/loop/test/desktop-local/contacts_test.js
+++ b/browser/components/loop/test/desktop-local/contacts_test.js
@@ -71,23 +71,19 @@ describe("loop.contacts", function() {
     published: 1406798311748,
     updated: 1406798311748
   }];
   var sandbox;
   var fakeWindow;
   var notifications;
   var listView;
   var oldMozLoop = navigator.mozLoop;
-  var mozL10nGetSpy;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
-
-    mozL10nGetSpy = sandbox.spy(document.mozL10n, "get");
-
     navigator.mozLoop = {
       getStrings: function(entityName) {
         var textContentValue = "fakeText";
         if (entityName == "add_contact_button") {
           textContentValue = fakeAddContactButtonText;
         } else if (entityName == "edit_contact_title") {
           textContentValue = fakeEditContactButtonText;
         } else if (entityName == "edit_contact_done_button") {
@@ -110,20 +106,16 @@ describe("loop.contacts", function() {
         }
         return "gravatarsDisabled";
       },
       contacts: {
         getAll: function(callback) {
           callback(null, [].concat(fakeContacts));
         },
         on: sandbox.stub()
-      },
-      calls: {
-        startDirectCall: function() {},
-        clearCallInProgress: function() {}
       }
     };
 
     fakeWindow = {
       close: sandbox.stub()
     };
     loop.shared.mixins.setRootObject(fakeWindow);
 
@@ -149,216 +141,140 @@ describe("loop.contacts", function() {
       // Sanity check the reverse:
       gravatars = node.querySelectorAll(".contact img[src=gravatarsDisabled]");
       expect(gravatars.length).to.equal(enabled ? 0 : fakeContacts.length);
     }
 
     it("should show the gravatars promo box", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.not.equal(null);
 
       checkGravatarContacts(false);
     });
 
     it("should not show the gravatars promo box when the 'contacts.gravatars.promo' pref is set", function() {
-      sandbox.stub(navigator.mozLoop, "getLoopPref", function(pref) {
+      navigator.mozLoop.getLoopPref = function(pref) {
         if (pref == "contacts.gravatars.promo") {
           return false;
         } else if (pref == "contacts.gravatars.show") {
           return true;
         }
         return "";
-      });
-
+      };
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
 
       checkGravatarContacts(true);
     });
 
     it("should hide the gravatars promo box when the 'use' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-accept"));
 
       sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
     });
 
     it("should should set the prefs correctly when the 'use' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-accept"));
 
       sinon.assert.calledTwice(navigator.mozLoop.setLoopPref);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.promo", false);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref, "contacts.gravatars.show", true);
     });
 
     it("should hide the gravatars promo box when the 'close' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-close"));
 
       var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
       expect(promo).to.equal(null);
     });
 
     it("should set prefs correctly when the 'close' button is clicked", function() {
       listView = TestUtils.renderIntoDocument(
         React.createElement(loop.contacts.ContactsList, {
-          mozLoop: navigator.mozLoop,
           notifications: notifications,
           startForm: function() {}
         }));
 
       React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
         ".contacts-gravatar-promo .button-close"));
 
       sinon.assert.calledOnce(navigator.mozLoop.setLoopPref);
       sinon.assert.calledWithExactly(navigator.mozLoop.setLoopPref,
         "contacts.gravatars.promo", false);
     });
   });
 
   describe("ContactsList", function () {
-    var node;
     beforeEach(function() {
-      sandbox.stub(navigator.mozLoop.calls, "startDirectCall");
-      sandbox.stub(navigator.mozLoop.calls, "clearCallInProgress");
-    });
-
-    describe("#RenderNoContacts", function() {
-      beforeEach(function() {
-        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
-          cb(null, []);
-        });
-        listView = TestUtils.renderIntoDocument(
-          React.createElement(loop.contacts.ContactsList, {
-            mozLoop: navigator.mozLoop,
-            notifications: notifications,
-            startForm: function() {}
-          }));
-        node = listView.getDOMNode();
-      });
-
-      it("should not show a contacts title if no contacts", function() {
-        expect(node.querySelector(".contact-list-title")).to.eql(null);
-        sinon.assert.neverCalledWith(mozL10nGetSpy, "contact_list_title");
-      });
+      navigator.mozLoop.calls = {
+        startDirectCall: sandbox.stub(),
+        clearCallInProgress: sandbox.stub()
+      };
+      navigator.mozLoop.contacts = {getAll: sandbox.stub()};
 
-      it("should show the no contacts view", function() {
-        expect(node.querySelector(".contact-list-empty")).to.not.eql(null);
-      });
-
-      it("should display the no contacts strings", function() {
-        sinon.assert.calledWithExactly(mozL10nGetSpy,
-                                       "no_contacts_message_heading");
-        sinon.assert.calledWithExactly(mozL10nGetSpy,
-                                       "no_contacts_import_or_add");
-      });
-    });
-
-    describe("#RenderWithContacts", function() {
-      beforeEach(function() {
-        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
-          cb(null, [].concat(fakeContacts));
-        });
-        listView = TestUtils.renderIntoDocument(
-          React.createElement(loop.contacts.ContactsList, {
-            mozLoop: navigator.mozLoop,
-            notifications: notifications,
-            startForm: function() {}
-          }));
-        node = listView.getDOMNode();
-      });
-
-      it("should show a contacts title", function() {
-        expect(node.querySelector(".contact-list-title")).not.to.eql(null);
-        sinon.assert.calledWithExactly(mozL10nGetSpy, "contact_list_title");
-      });
+      listView = TestUtils.renderIntoDocument(
+        React.createElement(loop.contacts.ContactsList, {
+          notifications: notifications,
+          startForm: function() {}
+        }));
     });
 
     describe("#handleContactAction", function() {
-      beforeEach(function() {
-        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
-          cb(null, []);
-        });
-        listView = TestUtils.renderIntoDocument(
-          React.createElement(loop.contacts.ContactsList, {
-            mozLoop: navigator.mozLoop,
-            notifications: notifications,
-            startForm: function() {}
-          }));
-        node = listView.getDOMNode();
-      });
-
       it("should call window.close when called with 'video-call' action",
         function() {
           listView.handleContactAction({}, "video-call");
 
           sinon.assert.calledOnce(fakeWindow.close);
       });
 
       it("should call window.close when called with 'audio-call' action",
         function() {
           listView.handleContactAction({}, "audio-call");
 
           sinon.assert.calledOnce(fakeWindow.close);
         });
     });
 
     describe("#handleImportButtonClick", function() {
-      beforeEach(function() {
-        sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
-          cb(null, []);
-        });
-        listView = TestUtils.renderIntoDocument(
-          React.createElement(loop.contacts.ContactsList, {
-            mozLoop: navigator.mozLoop,
-            notifications: notifications,
-            startForm: function() {}
-          }));
-        node = listView.getDOMNode();
-      });
-
       it("should notify the end user from a succesful import", function() {
         sandbox.stub(notifications, "successL10n");
         navigator.mozLoop.startImport = function(opts, cb) {
           cb(null, {success: 42});
         };
 
         listView.handleImportButtonClick();
 
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -8,17 +8,16 @@ describe("loop.panel", function() {
   var expect = chai.expect;
   var TestUtils = React.addons.TestUtils;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
 
   var sandbox, notifications;
   var fakeXHR, fakeWindow, fakeMozLoop;
   var requests = [];
-  var mozL10nGetSpy;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     fakeXHR = sandbox.useFakeXMLHttpRequest();
     requests = [];
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function (xhr) {
       requests.push(xhr);
@@ -73,29 +72,29 @@ describe("loop.panel", function() {
       logOutFromFxA: sandbox.stub(),
       notifyUITour: sandbox.stub(),
       openURL: sandbox.stub(),
       getSelectedTabMetadata: sandbox.stub(),
       userProfile: null
     };
 
     document.mozL10n.initialize(navigator.mozLoop);
-    sandbox.stub(document.mozL10n, "get").returns("Fake title");
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     loop.shared.mixins.setRootObject(window);
     sandbox.restore();
   });
 
   describe("#init", function() {
     beforeEach(function() {
       sandbox.stub(React, "render");
       sandbox.stub(document.mozL10n, "initialize");
+      sandbox.stub(document.mozL10n, "get").returns("Fake title");
     });
 
     it("should initalize L10n", function() {
       loop.panel.init();
 
       sinon.assert.calledOnce(document.mozL10n.initialize);
       sinon.assert.calledWithExactly(document.mozL10n.initialize,
         navigator.mozLoop);
@@ -345,79 +344,79 @@ describe("loop.panel", function() {
         beforeEach(function() {
           fakeMozLoop.userProfile = null;
         });
 
         it("should show a signin entry when user is not authenticated",
            function() {
              var view = mountTestComponent();
 
-             expect(view.getDOMNode().querySelectorAll(".entry-settings-signout"))
+             expect(view.getDOMNode().querySelectorAll(".icon-signout"))
                .to.have.length.of(0);
-             expect(view.getDOMNode().querySelectorAll(".entry-settings-signin"))
+             expect(view.getDOMNode().querySelectorAll(".icon-signin"))
                .to.have.length.of(1);
            });
 
         it("should hide any account entry when user is not authenticated",
            function() {
              var view = mountTestComponent();
 
              expect(view.getDOMNode().querySelectorAll(".icon-account"))
                .to.have.length.of(0);
            });
 
         it("should sign in the user on click when unauthenticated", function() {
           navigator.mozLoop.loggedInToFxA = false;
           var view = mountTestComponent();
 
           TestUtils.Simulate.click(view.getDOMNode()
-                                     .querySelector(".entry-settings-signin"));
+                                     .querySelector(".icon-signin"));
 
           sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
         });
       });
 
       it("should show a signout entry when user is authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
 
         var view = mountTestComponent();
 
-        sinon.assert.calledWithExactly(document.mozL10n.get,
-                                       "settings_menu_item_signout");
-        sinon.assert.neverCalledWith(document.mozL10n.get,
-                                     "settings_menu_item_signin");
+        expect(view.getDOMNode().querySelectorAll(".icon-signout"))
+          .to.have.length.of(1);
+        expect(view.getDOMNode().querySelectorAll(".icon-signin"))
+          .to.have.length.of(0);
       });
 
       it("should show an account entry when user is authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
 
         var view = mountTestComponent();
 
-        sinon.assert.calledWithExactly(document.mozL10n.get,
-                                       "settings_menu_item_settings");
+        expect(view.getDOMNode().querySelectorAll(".icon-account"))
+          .to.have.length.of(1);
       });
 
       it("should open the FxA settings when the account entry is clicked",
          function() {
            navigator.mozLoop.userProfile = {email: "test@example.com"};
 
            var view = mountTestComponent();
 
            TestUtils.Simulate.click(view.getDOMNode()
-                                      .querySelector(".entry-settings-account"));
+                                      .querySelector(".icon-account"));
 
            sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
          });
 
       it("should sign out the user on click when authenticated", function() {
         navigator.mozLoop.userProfile = {email: "test@example.com"};
         var view = mountTestComponent();
 
         TestUtils.Simulate.click(view.getDOMNode()
-                                   .querySelector(".entry-settings-signout"));
+                                   .querySelector(".icon-signout"));
 
         sinon.assert.calledOnce(navigator.mozLoop.logOutFromFxA);
       });
     });
 
     describe("Help", function() {
       var view, supportUrl;
 
@@ -438,27 +437,27 @@ describe("loop.panel", function() {
           return "unseen";
         };
       });
 
       it("should open a tab to the support page", function() {
         view = mountTestComponent();
 
         TestUtils.Simulate
-          .click(view.getDOMNode().querySelector(".entry-settings-help"));
+          .click(view.getDOMNode().querySelector(".icon-help"));
 
         sinon.assert.calledOnce(fakeMozLoop.openURL);
         sinon.assert.calledWithExactly(fakeMozLoop.openURL, supportUrl);
       });
 
       it("should close the panel", function() {
         view = mountTestComponent();
 
         TestUtils.Simulate
-          .click(view.getDOMNode().querySelector(".entry-settings-help"));
+          .click(view.getDOMNode().querySelector(".icon-help"));
 
         sinon.assert.calledOnce(fakeWindow.close);
       });
     });
 
     describe("#render", function() {
       it("should not render a ToSView when gettingStarted.seen is true", function() {
         navigator.mozLoop.getLoopPref = function() {
@@ -833,17 +832,17 @@ describe("loop.panel", function() {
        "conversation button",
       function() {
         navigator.mozLoop.userProfile = {email: fakeEmail};
         var view = createTestComponent(false);
 
         TestUtils.Simulate.click(view.getDOMNode().querySelector(".new-room-button"));
 
         sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
-          nameTemplate: "Fake title",
+          nameTemplate: "fakeText",
           roomOwner: fakeEmail
         }));
       });
 
     it("should dispatch a CreateRoom action with context when clicking on the " +
        "Start a conversation button", function() {
       fakeMozLoop.userProfile = {email: fakeEmail};
       var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
@@ -864,17 +863,17 @@ describe("loop.panel", function() {
       var node = view.getDOMNode();
 
       // Select the checkbox
       TestUtils.Simulate.click(node.querySelector(".checkbox-wrapper"));
 
       TestUtils.Simulate.click(node.querySelector(".new-room-button"));
 
       sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
-        nameTemplate: "Fake title",
+        nameTemplate: "fakeText",
         roomOwner: fakeEmail,
         urls: [{
           location: "http://invalid.com",
           description: "fakeSite",
           thumbnail: favicon
         }]
       }));
     });
--- a/browser/components/loop/ui/fake-mozLoop.js
+++ b/browser/components/loop/ui/fake-mozLoop.js
@@ -135,18 +135,18 @@ var fakeContacts = [{
           return false;
       }
     },
     hasEncryptionKey: true,
     setLoopPref: function(){},
     releaseCallData: function() {},
     copyString: function() {},
     getUserAvatar: function(emailAddress) {
-      var avatarUrl = "http://www.gravatar.com/avatar/0a996f0fe2727ef1668bdb11897e4459.jpg?default=blank&s=40";
-      return Math.ceil(Math.random() * 3) === 2 ? avatarUrl : null;
+      return "http://www.gravatar.com/avatar/" + (Math.ceil(Math.random() * 3) === 2 ?
+        "0a996f0fe2727ef1668bdb11897e4459" : "foo") + ".jpg?default=blank&s=40";
     },
     getSelectedTabMetadata: function(callback) {
       callback({
         previews: ["chrome://branding/content/about-logo.png"],
         description: "sample webpage description",
         url: "https://www.example.com"
       });
     },
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -53,18 +53,16 @@ body {
 }
 
 .showcase > section > h1 {
   margin: 1em 0;
   border-bottom: 1px solid #aaa;
 }
 
 .showcase > section .comp {
-  /* contain absolute positioned elements such as dropdowns */
-  position: relative;
   margin: 0 auto; /* width is usually set programmatically */
 }
 
 .showcase > section .comp.dashed,
 .showcase > section .comp > iframe.dashed
 {
   border: 1px dashed #ccc;
 }
@@ -164,12 +162,12 @@ body {
 }
 
 /* Temporary until bug 1168829 is completed */
 .standalone.text-chat-example .text-chat-view {
   height: 400px;
 }
 
 /* Force dropdown menus to display. */
-.force-menu-show .icons,
-.force-menu-show .dropdown-menu {
+.force-menu-show * {
   display: inline-block !important;
 }
+
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -1,30 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* global Frame:false uncaughtError:true fakeContacts:true */
+/* global Frame:false uncaughtError:true */
 
 (function() {
   "use strict";
 
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener("DOMContentLoaded", loop.panel.init);
   document.removeEventListener("DOMContentLoaded", loop.conversation.init);
 
   var sharedActions = loop.shared.actions;
 
   // 1. Desktop components
   // 1.1 Panel
   var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
   var PanelView = loop.panel.PanelView;
   var SignInRequestView = loop.panel.SignInRequestView;
-  var ContactDropdown = loop.contacts.ContactDropdown;
-  var ContactDetail = loop.contacts.ContactDetail;
   // 1.2. Conversation Window
   var AcceptCallView = loop.conversationViews.AcceptCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var OngoingConversationView = loop.conversationViews.OngoingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   // 2. Standalone webapp
@@ -448,27 +446,16 @@
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedInLongEmail.userProfile = {
     email: "reallyreallylongtext@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
-  var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
-
-  var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
-  mozLoopNoContacts.userProfile = {
-    email: "reallyreallylongtext@example.com",
-    uid: "0354b278a381d3cb408bb46ffc01266"
-  };
-  mozLoopNoContacts.contacts.getAll = function(callback) {
-    callback(null, []);
-  };
-
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
@@ -747,24 +734,16 @@
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab long email"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: mockMozLoopLoggedInLongEmail, 
                          notifications: notifications, 
                          roomStore: roomStore, 
                          selectedTab: "contacts"})
             ), 
-            React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab (no contacts)"}, 
-              React.createElement(PanelView, {client: mockClient, 
-                         dispatcher: dispatcher, 
-                         mozLoop: mozLoopNoContacts, 
-                         notifications: notifications, 
-                         roomStore: roomStore, 
-                         selectedTab: "contacts"})
-            ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification"}, 
               React.createElement(PanelView, {client: mockClient, 
                          dispatcher: dispatcher, 
                          mozLoop: navigator.mozLoop, 
                          notifications: errNotifications, 
                          roomStore: roomStore})
             ), 
             React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification - authenticated"}, 
@@ -800,40 +779,16 @@
             ), 
             React.createElement(Example, {cssClass: "force-menu-show", dashed: true, 
                      style: {width: "332px", height: "200px"}, 
                      summary: "AvailabilityDropdown Expanded"}, 
               React.createElement(AvailabilityDropdown, null)
             )
           ), 
 
-          React.createElement(Section, {name: "ContactDetail"}, 
-            React.createElement(Example, {cssClass: "force-menu-show", dashed: true, 
-                     style: {width: "300px", height: "272px"}, 
-                     summary: "ContactDetail"}, 
-              React.createElement(ContactDetail, {contact: fakeContacts[0], 
-                handleContactAction: function() {}})
-            )
-          ), 
-
-          React.createElement(Section, {name: "ContactDropdown"}, 
-            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
-                     summary: "ContactDropdown not blocked can edit"}, 
-              React.createElement(ContactDropdown, {blocked: false, 
-                               canEdit: true, 
-                               handleAction: function () {}})
-            ), 
-            React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
-                     summary: "ContactDropdown blocked can't edit"}, 
-              React.createElement(ContactDropdown, {blocked: true, 
-                               canEdit: false, 
-                               handleAction: function () {}})
-            )
-          ), 
-
           React.createElement(Section, {name: "AcceptCallView"}, 
             React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"}, 
                      summary: "Default / incoming video call"}, 
               React.createElement("div", {className: "fx-embedded"}, 
                 React.createElement(AcceptCallView, {callType: CALL_TYPES.AUDIO_VIDEO, 
                                 callerId: "Mr Smith", 
                                 dispatcher: dispatcher, 
                                 mozLoop: mockMozLoopLoggedIn})
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -1,30 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* global Frame:false uncaughtError:true fakeContacts:true */
+/* global Frame:false uncaughtError:true */
 
 (function() {
   "use strict";
 
   // Stop the default init functions running to avoid conflicts.
   document.removeEventListener("DOMContentLoaded", loop.panel.init);
   document.removeEventListener("DOMContentLoaded", loop.conversation.init);
 
   var sharedActions = loop.shared.actions;
 
   // 1. Desktop components
   // 1.1 Panel
   var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
   var PanelView = loop.panel.PanelView;
   var SignInRequestView = loop.panel.SignInRequestView;
-  var ContactDropdown = loop.contacts.ContactDropdown;
-  var ContactDetail = loop.contacts.ContactDetail;
   // 1.2. Conversation Window
   var AcceptCallView = loop.conversationViews.AcceptCallView;
   var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
   var OngoingConversationView = loop.conversationViews.OngoingConversationView;
   var CallFailedView = loop.conversationViews.CallFailedView;
   var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
 
   // 2. Standalone webapp
@@ -448,27 +446,16 @@
   };
 
   var mockMozLoopLoggedInLongEmail = _.cloneDeep(navigator.mozLoop);
   mockMozLoopLoggedInLongEmail.userProfile = {
     email: "reallyreallylongtext@example.com",
     uid: "0354b278a381d3cb408bb46ffc01266"
   };
 
-  var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
-
-  var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
-  mozLoopNoContacts.userProfile = {
-    email: "reallyreallylongtext@example.com",
-    uid: "0354b278a381d3cb408bb46ffc01266"
-  };
-  mozLoopNoContacts.contacts.getAll = function(callback) {
-    callback(null, []);
-  };
-
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
 
   var mockClient = {
@@ -747,24 +734,16 @@
             <Example dashed={true} style={{width: "332px"}} summary="Contact list tab long email">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={mockMozLoopLoggedInLongEmail}
                          notifications={notifications}
                          roomStore={roomStore}
                          selectedTab="contacts" />
             </Example>
-            <Example dashed={true} style={{width: "332px"}} summary="Contact list tab (no contacts)">
-              <PanelView client={mockClient}
-                         dispatcher={dispatcher}
-                         mozLoop={mozLoopNoContacts}
-                         notifications={notifications}
-                         roomStore={roomStore}
-                         selectedTab="contacts" />
-            </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Error Notification">
               <PanelView client={mockClient}
                          dispatcher={dispatcher}
                          mozLoop={navigator.mozLoop}
                          notifications={errNotifications}
                          roomStore={roomStore} />
             </Example>
             <Example dashed={true} style={{width: "332px"}} summary="Error Notification - authenticated">
@@ -800,40 +779,16 @@
             </Example>
             <Example cssClass="force-menu-show" dashed={true}
                      style={{width: "332px", height: "200px"}}
                      summary="AvailabilityDropdown Expanded">
               <AvailabilityDropdown />
             </Example>
           </Section>
 
-          <Section name="ContactDetail">
-            <Example cssClass="force-menu-show" dashed={true}
-                     style={{width: "300px", height: "272px"}}
-                     summary="ContactDetail">
-              <ContactDetail contact={fakeContacts[0]}
-                handleContactAction={function() {}} />
-            </Example>
-          </Section>
-
-          <Section name="ContactDropdown">
-            <Example dashed={true} style={{width: "300px", height: "272px"}}
-                     summary="ContactDropdown not blocked can edit">
-              <ContactDropdown blocked={false}
-                               canEdit={true}
-                               handleAction={function () {}} />
-            </Example>
-            <Example dashed={true} style={{width: "300px", height: "272px"}}
-                     summary="ContactDropdown blocked can't edit">
-              <ContactDropdown blocked={true}
-                               canEdit={false}
-                               handleAction={function () {}} />
-            </Example>
-          </Section>
-
           <Section name="AcceptCallView">
             <Example dashed={true} style={{width: "300px", height: "272px"}}
                      summary="Default / incoming video call">
               <div className="fx-embedded">
                 <AcceptCallView callType={CALL_TYPES.AUDIO_VIDEO}
                                 callerId="Mr Smith"
                                 dispatcher={dispatcher}
                                 mozLoop={mockMozLoopLoggedIn} />