diff --git a/.gitignore b/.gitignore
index ff44ccd0c0be5f6e7288f1282a2f0a853f06dc72..5c48f0e0b97b6d82bf93880858cd51d2db92a22b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@
 /npm-debug.log
 /resources/static/build
 /resources/static/production
+.DS_Store
\ No newline at end of file
diff --git a/ChangeLog b/ChangeLog
index e30219c1821e21c9a0f791e3a317adf012ec93a2..8e07fa26f09ef9b8313c0e2d0816dc00689b0c30 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
-train-2012.06.22: (in progress)
-  *
+train-2012.07.06: (in progress)
+
+train-2012.06.22:
+  * browserid.org now redirects to login.persona.org, all URLs are updated: #1743
+  * Websites can now provide their name and logo (requires SSL) to be displayed in the dialog: #1098, #1761
+  * A user is now sent back to the site they were visiting after verification (requires .watch() API): #385
+  * Fix .watch() API under IE8: #1637
+  * For dev and ephemeral deployments, move to awsbox, and new URLs: #1394, #1046, #1741
+  * Fix the scroll bar appearing on the main site's index page if it is not needed: #1693
+  * Clear the password if the user types a password then changes the email address: #1540
+  * New watch API now requires invocation with proper context (navigator.id.foo, not var foo = navigator.id.foo)
+  * Router fixes: #1713
+  * Serve fonts locally, don't pull resources from google: #1695
+  * Optimize images: #1747
+  * Fix flashes when verifying an email address: #1734
+  * Unit test added which runs jshint: #1731
+  * Fix submit occurring when selecting an email address in Firefox from the auto-complete list: #1780
+  * For KPI data, round timestamp to nearest 10 minutes, making correlation improbable: #1732
+  * Code cleanup: #1701, #1703, #1000, #1248, #1759, #1733, #1792
+  * Breaking API change: Persona now returns pubkey from generateKeypair to IdPs as a string
 
 train-2012.06.08:
   * rebrand from 'browserid' to 'persona': (including regressions #1711 #1706 #1716 #1719)
diff --git a/bin/router b/bin/router
index 56db15569691be00b3498ca77ddffe1171575f22..dc409f51267387c05c08a9f0f6c540854cf6c25c 100755
--- a/bin/router
+++ b/bin/router
@@ -49,7 +49,10 @@ if (!config.get('browserid_url')) {
 
 // #1 - Setup health check / heartbeat middleware.
 // This is in front of logging on purpose.  see issue #537
-heartbeat.setup(app);
+var browserid_url = urlparse(config.get('browserid_url')).validate().normalize().originOnly();
+heartbeat.setup(app, {
+  dependencies: [browserid_url]
+});
 
 // #2 - logging!  all requests other than __heartbeat__ are logged
 app.use(express.logger({
@@ -119,7 +122,6 @@ wsapi.setup({
 }, app);
 
 // Forward all leftover requests to browserid
-var browserid_url = urlparse(config.get('browserid_url')).validate().normalize().originOnly();
 app.use(function(req, res, next) {
   forward(
     browserid_url+req.url, req, res,
diff --git a/lib/heartbeat.js b/lib/heartbeat.js
index 666e77767ac78f4fb8fdb74355a42a4bf870dadc..e5e309fd4c990366e7cb198bb549744e179ea550 100644
--- a/lib/heartbeat.js
+++ b/lib/heartbeat.js
@@ -4,20 +4,74 @@
 
 const
 urlparse = require('urlparse'),
-logger = require('./logging.js').logger;
+logger = require('./logging.js').logger,
+url = require('url');
 
 // the path that heartbeats live at
 exports.path = '/__heartbeat__';
 
+const checkTimeout = 5000;
+
 // a helper function to set up a heartbeat check
-exports.setup = function(app, cb) {
+exports.setup = function(app, options, cb) {
+  var dependencies = [];
+
+  if (typeof options == 'function') {
+    cb = options;
+  } else if (options && options.dependencies) {
+    dependencies = options.dependencies;
+  }
+  var count = dependencies.length;
+
   app.use(function(req, res, next) {
-    if (req.method === 'GET' && req.path === exports.path) {
-      function ok(yeah) {
-        res.writeHead(yeah ? 200 : 500);
-        res.write(yeah ? 'ok' : 'not ok');
-        res.end();
+    if (req.method !== 'GET' || req.path !== exports.path) {
+      return next();
+    }
+
+    var checked = 0;
+    var query = url.parse(req.url, true).query;
+    var deep = typeof query.deep != 'undefined';
+    var notOk = [];
+
+    // callback for checking a dependency
+    function checkCB (num) {
+      return function (err, isOk) {
+        checked++;
+        if (err) {
+          notOk.push(dependencies[num] + ': '+ err);
+        }
+
+        // if all dependencies have been checked
+        if (checked == count) {
+          if (notOk.length === 0) {
+            try {
+              if (cb) cb(ok);
+              else ok(true);
+            } catch(e) {
+              logger.error("Exception caught in heartbeat handler: " + e.toString());
+              ok(false, e);
+            }
+          } else {
+            logger.warn("heartbeat failed due to dependencies - " + notOk.join(', '));
+            ok(false, '\n' + notOk.join('\n') + '\n');
+          }
+        }
+      };
+    }
+
+    function ok(yeah, msg) {
+      res.writeHead(yeah ? 200 : 500);
+      res.write(yeah ? 'ok' : 'bad');
+      if (msg) res.write(msg);
+      res.end();
+    }
+
+    // check all dependencies if deep
+    if (deep && count) {
+      for (var i = 0; i < count; i++) {
+        check(dependencies[i] + exports.path, checkCB(i));
       }
+    } else {
       try {
         if (cb) cb(ok);
         else ok(true);
@@ -25,29 +79,39 @@ exports.setup = function(app, cb) {
         logger.error("Exception caught in heartbeat handler: " + e.toString());
         ok(false);
       }
-    } else {
-      return next();
     }
   });
 };
 
+
 // a function to check the heartbeat of a remote server
-exports.check = function(url, cb) {
+var check = exports.check = function(url, cb) {
   if (typeof url === 'string') url = urlparse(url).normalize().validate();
   else if (typeof url !== 'object') throw "url string or object required as argumnet to heartbeat.check";
   if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443;
 
   var shortname = url.host + ':' + url.port;
 
-  require(url.scheme).get({
+  var timeoutHandle = setTimeout(function() {
+    req.abort();
+  }, checkTimeout);
+
+  var req = require(url.scheme).get({
     host: url.host,
     port: url.port,
     path: exports.path
   }, function (res) {
-    if (res.statusCode === 200) cb(true);
-    else logger.error("non-200 response from " + shortname + ".  fatal! (" + res.statusCode + ")");
-  }, function (e) {
-    logger.error("can't communicate with " + shortname + ".  fatal: " + e);
-    cb(false);
+    clearTimeout(timeoutHandle);
+    if (res.statusCode === 200) cb(null, true);
+    else {
+      logger.warn("heartbeat failure: non-200 response from " + shortname + ".  fatal! (" +
+                  res.statusCode + ")");
+      cb("response code " + res.statusCode);
+    }
+  });
+  req.on('error', function (e) {
+    clearTimeout(timeoutHandle);
+    logger.warn("heartbeat failure: can't communicate with " + shortname + ".  fatal: " + e);
+    cb(e ? e : "unknown error");
   });
 };
diff --git a/lib/static_resources.js b/lib/static_resources.js
index 9c0b1e951dd482c52c874ce01f7b83971ac7e54f..b994c3842d6b8abe2f939c53caf349150c4e521c 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -68,7 +68,8 @@ var browserid_js = und.flatten([
     '/pages/forgot.js',
     '/pages/manage_account.js',
     '/pages/signin.js',
-    '/pages/signup.js'
+    '/pages/signup.js',
+    '/pages/about.js'
   ]
 ]);
 
diff --git a/resources/static/css/common.css b/resources/static/css/common.css
index c12bdae416fdfb37a4ca365559162111d5418ab9..11f1f79ee52db1129100d736c9765cecb79de34d 100644
--- a/resources/static/css/common.css
+++ b/resources/static/css/common.css
@@ -128,6 +128,10 @@ input[type=password]:disabled {
      * issue #1311 */
     -webkit-text-fill-color: #4f4f4f;
     opacity: 1;
+    /* Remove the box-shadow and border-color that come with a focused input
+     * field */
+    box-shadow: none;
+    border-color: #b2b2b2;
 }
 
 input[type=radio],
@@ -359,7 +363,12 @@ footer .help {
 
 #wait, #delay, #error {
   background-color: #dadee1;
-  background-image: url("/i/grain.png"), -moz-linear-gradient(top, #dadee1, #c7ccd0);
+  background-image: url("/i/grain.png"), -webkit-gradient(linear, left top, left bottom, from(#dadee1), to(#c7ccd0));
+  background-image: url("/i/grain.png"), -webkit-linear-gradient(top, #dadee1, #c7ccd0);
+  background-image: url("/i/grain.png"),    -moz-linear-gradient(top, #dadee1, #c7ccd0);
+  background-image: url("/i/grain.png"),     -ms-linear-gradient(top, #dadee1, #c7ccd0);
+  background-image: url("/i/grain.png"),      -o-linear-gradient(top, #dadee1, #c7ccd0);
+  background-image: url("/i/grain.png"),         linear-gradient(top, #dadee1, #c7ccd0);
 }
 
 #wait, #delay {
diff --git a/resources/static/css/m.css b/resources/static/css/m.css
index 8325d9e0bf567610fb120ad1941dc9c7cc4640fc..50943635fe29a7aa2366788155d2265970643603 100644
--- a/resources/static/css/m.css
+++ b/resources/static/css/m.css
@@ -45,7 +45,7 @@
 /*
  * 620 catches most mobile devices in landscape mode.  The purpose of this is
  * to make sure the right hand nav menu does not drop partially below the
- * persona logo.
+ * persona logo. This also adjusts the boxes on the "How It Works" page.
  */
 @media screen and (max-width: 620px) {
   header ul {
@@ -54,6 +54,51 @@
     display: block;
     text-align: center;
   }
+
+  .blurb.half {
+    width: 100%;
+    float: none;
+    min-height: 0 !important;
+  }
+  .blurb.half.first {
+    margin-right: 0;
+  }
+
+  .blurb {
+    display: -webkit-box;
+    display: box;
+    -webkit-box-orient: vertical;
+    box-orient: vertical;
+  }
+  .blurb .info {
+    -webkit-box-ordinal-group: 2;
+    -moz-box-ordinal-group: 2;
+    -ms-box-ordinal-group: 2;
+    box-ordinal-group: 2;
+  }
+  .blurb h1{
+    font-size: 20px;
+  }
+  .blurb.flexible .graphic {
+    margin: 0 0 30px;
+  }
+  .blurb .first {
+    padding-right: 0;
+  }
+  .blurb .info, .blurb .graphic {
+    float: none;
+    width: 100%;
+  }
+
+  h2.title {
+    font-size: 32px;
+    padding-bottom: 15px;
+  }
+
+  .privacy{
+    margin: 60px 0 30px;
+    padding-bottom: 30px;
+  }
 }
 
 /*
@@ -78,11 +123,6 @@
     padding: 10px;
   }
 
-  #about {
-    padding: 10px;
-    border-radius: 5px;
-  }
-
   .headline-main {
     font-size: 37px;
     text-align: center;
@@ -159,39 +199,6 @@
     list-style-position: inside;
   }
 
-  #about .video,
-  #about .video img {
-    width: 300px;
-    height: auto;
-  }
-
-  .row {
-    padding: 20px 20px 0;
-    margin: 0;
-  }
-
-  .row div {
-    width: auto;
-    height: auto;
-    vertical-align: inherit;
-    display: block;
-    padding: 20px 0;
-  }
-
-  .row img {
-    float: none;
-    width: 260px;
-    height: auto;
-  }
-
-  .row p {
-    float: none;
-    display: block;
-    width: 260px;
-    text-indent: -33px;
-    padding-left: 33px;
-  }
-
   #signUpFormWrap {
     margin: 122px 10px 122px;
   }
diff --git a/resources/static/css/style.css b/resources/static/css/style.css
index 94aac066d5eea89dbaef6c4d9df944a96b34ffaa..6ac4fb9fd4c9c7f5c8eac819133263d8ed2883eb 100644
--- a/resources/static/css/style.css
+++ b/resources/static/css/style.css
@@ -106,15 +106,6 @@ body {
   padding: 50px 0;
 }
 
-#about {
-  color: #444;
-  text-shadow: 1px 1px 0 rgba(255,255,255,0.5);
-  padding: 50px 75px;
-  background-color: #fff;
-  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
-  border-radius: 5px;
-}
-
 h1 {
   margin-bottom: 35px;
 }
@@ -123,65 +114,6 @@ h1 {
   font-weight: 300;
 }
 
-.row {
-  margin: 25px 0 0 0;
-  padding: 0 0 25px 0;
-  position: relative;
-  border-bottom: 1px solid #eee;
-}
-
-
-
-
-.row:last-child {
-  padding-bottom: 0;
-  border-bottom: none;
-}
-
-.row div {
-  height: 140px;
-  width: 500px;
-  padding: 0 0 0 20px;
-  display: table-cell;
-  vertical-align: middle;
-}
-
-.row p {
-  width: 380px;
-  text-shadow: 1px 1px 0 rgba(255,255,255,0.5);
-  float: left;
-}
-
-.row img {
-  float: left;
-}
-
-.row button, .row .button {
-  float: right;
-  display: inline-block;
-}
-
-div.steps {
-  width: 24px;
-  height: 24px;
-  vertical-align: bottom;
-  margin-right: 10px;
-  background-image: url('/i/count.png');
-  float: left;
-}
-
-.one .steps {
-  background-position: left 0;
-}
-
-.two .steps {
-  background-position: left -24px;
-}
-
-.three .steps {
-  background-position: left -48px;
-}
-
 
 #legal {
   padding: 75px 125px;
@@ -659,3 +591,141 @@ footer ul li:first-child a:hover {
   display: block;
 }
 
+/*  How It Works
+ ***************/
+
+ h2.title {
+  font-size: 48px;
+  font-weight: normal;
+  color: #fff;
+  text-shadow: 0 1px rgba(0, 0, 0, 0.5);
+  text-align: center;
+  letter-spacing: -2px;
+  padding-bottom: 30px;
+  margin: 0;
+}
+
+.blurb, a.developers {
+  -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.13);
+  -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.13);
+  -ms-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.13);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.13);
+  background: #63727d;
+  background: rgba(13, 28, 41, 0.1);
+  font-size: 14px;
+  color: #fff;
+}
+
+.blurb {
+  zoom: 1;
+  margin-top: 30px;
+  padding: 30px;
+  text-align: left;
+  line-height: 1.5;
+}
+.blurb:before, .blurb:after {
+  content: "";
+  display: table;
+}
+.blurb:after {
+  clear: both;
+}
+
+.blurb h1, .blurb p, .blurb a, a.developers{
+  text-shadow: 0 1px rgba(0, 0, 0, 0.5);
+  font-weight: normal;
+}
+
+.blurb img{
+  max-width: 100%;
+  vertical-align: bottom;
+}
+
+.blurb a {
+  color: #fff;
+  border-bottom: 1px dotted #fff;
+  font-weight: normal;
+}
+.blurb a:hover {
+  color: #53b7fb;
+}
+.blurb.half {
+  width: 48%;
+  float: left;
+}
+.blurb.half.first {
+  margin-right: 4%;
+}
+.blurb .info, .blurb .graphic {
+  width: 50%;
+  float: left;
+}
+.blurb .first {
+  padding-right: 30px;
+}
+.blurb .graphic {
+  text-align: center;
+}
+.blurb h1 {
+  font-size: 32px;
+  font-weight: normal;
+  letter-spacing: -1px;
+  line-height: 1.1;
+  margin-bottom: 20px;
+}
+.blurb p {
+  margin-bottom: 1em;
+}
+.blurb p:last-of-type {
+  margin-bottom: 0;
+}
+
+.privacy {
+  -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1);
+  -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1);
+  -ms-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1);
+  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1);
+  zoom: 1;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+  padding-bottom: 60px;
+  margin: 100px 0 60px;
+}
+.privacy:before, .privacy:after {
+  content: "";
+  display: table;
+}
+.privacy:after {
+  clear: both;
+}
+
+a.developers {
+  -webkit-transition: all 300ms ease;
+  -moz-transition: all 300ms ease;
+  -ms-transition: all 300ms ease;
+  transition: all 300ms ease;
+  display: block;
+  padding: 13px 15px;
+  line-height: 1.4;
+  text-align: center;
+}
+a.developers:hover {
+  background: #3b4e5c;
+  background: rgba(13, 28, 41, 0.2);
+}
+a.developers img {
+  margin: 0 5px -7px 0;
+}
+a.developers span {
+  color: #53b7fb;
+  font-weight: bold;
+  margin-right: 10px;
+  display: inline-block;
+}
+
+article.flexible {
+  padding-bottom: 0;
+}
+article.flexible .info {
+  margin-bottom: 30px;
+}
+
diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js
index 80435e5220f6bafe3e5856ef3aeb358bc715a098..3a95c89155d98f12c71b9f8eb11231f0a1f82bfe 100644
--- a/resources/static/dialog/controllers/actions.js
+++ b/resources/static/dialog/controllers/actions.js
@@ -28,7 +28,6 @@ BrowserID.Modules.Actions = (function() {
     }
 
     mediator.publish("service", { name: name });
-    bid.resize();
 
     return module;
   }
diff --git a/resources/static/dialog/controllers/add_email.js b/resources/static/dialog/controllers/add_email.js
index c9a8c638ca85ddc7f7566fb1c80e8dd90fa85d89..da642e0d2ced87d3cdf769b0d2389e0b2ad46e9a 100644
--- a/resources/static/dialog/controllers/add_email.js
+++ b/resources/static/dialog/controllers/add_email.js
@@ -7,17 +7,38 @@ BrowserID.Modules.AddEmail = (function() {
   "use strict";
 
   var bid = BrowserID,
+      dom = bid.DOM,
       helpers = bid.Helpers,
       dialogHelpers = helpers.Dialog,
       errors = bid.Errors,
       complete = helpers.complete,
-      tooltip = bid.Tooltip;
+      tooltip = bid.Tooltip,
+      hints = ["addressInfo"],
+      ANIMATION_TIME = 250;
+
+  function hideHint(selector) {
+    $("." + selector).hide();
+  }
+
+  function showHint(selector, callback) {
+    _.each(hints, function(className) {
+      if (className !== selector) {
+        hideHint(className);
+      }
+    });
+
+    $("." + selector).fadeIn(ANIMATION_TIME, function() {
+      dom.fireEvent(window, "resize");
+      complete(callback);
+    });
+  }
 
   function addEmail(callback) {
     var email = helpers.getAndValidateEmail("#newEmail"),
         self=this;
 
     if (email) {
+      showHint("addressInfo");
       dialogHelpers.addEmail.call(self, email, callback);
     }
     else {
@@ -39,6 +60,7 @@ BrowserID.Modules.AddEmail = (function() {
           });
 
       self.renderDialog("add_email", templateData);
+      hideHint("addressInfo");
 
       self.click("#cancel", cancelAddEmail);
       Module.sc.start.call(self, options);
diff --git a/resources/static/dialog/controllers/authenticate.js b/resources/static/dialog/controllers/authenticate.js
index ac413aa99a82cb3fd6573580dee2a04d4e83e99c..c316e55a5e5822825c2ad2bb69f1b32a6bf106fa 100644
--- a/resources/static/dialog/controllers/authenticate.js
+++ b/resources/static/dialog/controllers/authenticate.js
@@ -43,6 +43,7 @@ BrowserID.Modules.Authenticate = (function() {
 
     if (!email) return;
 
+    dom.setAttr('#email', 'disabled', 'disabled');
     if(info && info.type) {
       onAddressInfo(info);
     }
@@ -54,6 +55,7 @@ BrowserID.Modules.Authenticate = (function() {
 
     function onAddressInfo(info) {
       addressInfo = info;
+      dom.removeAttr('#email', 'disabled');
 
       if(info.type === "primary") {
         self.close("primary_user", info, info);
diff --git a/resources/static/dialog/css/m.css b/resources/static/dialog/css/m.css
index 2a757809b174111c8050bdb28fcc7a5a5cd8d9cf..ff2ee544b1ecc1f17c11781afb4dfa69e4e2313b 100644
--- a/resources/static/dialog/css/m.css
+++ b/resources/static/dialog/css/m.css
@@ -159,14 +159,6 @@
     line-height: 40px;
   }
 
-  #error {
-    position: static;
-  }
-
-  #error .vertical, #delay .vertical, #wait .vertical {
-    height: 250px;
-  }
-
   #error .vertical {
     width: auto;
   }
diff --git a/resources/static/dialog/resources/screen_size_hacks.js b/resources/static/dialog/resources/screen_size_hacks.js
index 33685cb756f36123246e781af5600ff6b586e4c8..c304ca0e4cd3cac9d242d47d07a78f3993e78e30 100644
--- a/resources/static/dialog/resources/screen_size_hacks.js
+++ b/resources/static/dialog/resources/screen_size_hacks.js
@@ -106,6 +106,8 @@
         contentHeight = Math.max(100, contentHeight, formHeight);
         contentEl.css("min-height", contentHeight + "px");
 
+        // Remove the explicit static position we added to let this go back to
+        // the position specified in CSS.
         $("section,#signIn").css("position", "");
 
         favIconHeight = $("#favicon").outerHeight();
diff --git a/resources/static/dialog/views/add_email.ejs b/resources/static/dialog/views/add_email.ejs
index 5af78c55bba4147120393579d08484e2be5ebbb2..9e183be4c20c66d881def28e9617382b19731f1a 100644
--- a/resources/static/dialog/views/add_email.ejs
+++ b/resources/static/dialog/views/add_email.ejs
@@ -27,6 +27,10 @@
                 <%= gettext('That address is already added to your account!') %>
               </div>
           </li>
+
+          <li id="hint_section" class="addressInfo">
+              <%= gettext("Please hold on while we get information about your email provider.") %>
+          </li>
       </ul>
 
       <div class="submit cf">
diff --git a/resources/static/i/developers-link.png b/resources/static/i/developers-link.png
new file mode 100644
index 0000000000000000000000000000000000000000..e730af8df49fcf06234eb9361ec5b894870fd922
Binary files /dev/null and b/resources/static/i/developers-link.png differ
diff --git a/resources/static/i/flexible-graphic.png b/resources/static/i/flexible-graphic.png
new file mode 100644
index 0000000000000000000000000000000000000000..c94d2cc1e3e86106a155785b26f6b2c151a19a11
Binary files /dev/null and b/resources/static/i/flexible-graphic.png differ
diff --git a/resources/static/i/one-password-graphic.png b/resources/static/i/one-password-graphic.png
new file mode 100644
index 0000000000000000000000000000000000000000..380579a0ed0599b8fc206d1331817d175202b5ab
Binary files /dev/null and b/resources/static/i/one-password-graphic.png differ
diff --git a/resources/static/pages/about.js b/resources/static/pages/about.js
new file mode 100644
index 0000000000000000000000000000000000000000..9741062539480bfb5b450d9eac79235427d75c5e
--- /dev/null
+++ b/resources/static/pages/about.js
@@ -0,0 +1,45 @@
+/*globals BrowserID:true, $:true*/
+/* 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/. */
+
+BrowserID.about = (function() {
+  "use strict";
+
+  var bid = BrowserID;
+
+  function resize() {
+    // Get tallest blurb
+    var tallestBlurb = 0
+
+    $('.half').each(function(index) {
+      var $this = $(this);
+
+      if (index == 0) {
+        tallestBlurb = $this.height();
+      } else {
+
+        if ($this.height() < tallestBlurb) {
+          $this.css('min-height', tallestBlurb);
+        } else {
+          $('.half.first').css('min-height', $this.height());
+        }
+
+      }
+    });
+  }
+
+  var Module = bid.Modules.PageModule.extend({
+    start: function(options) {
+      var self=this;
+
+      Module.sc.start.call(self, options);
+      resize();
+
+      // The half heights can change every time there is a window resize.
+      self.bind(window, "resize", resize);
+    }
+  });
+
+  return Module;
+}());
diff --git a/resources/static/pages/signin.js b/resources/static/pages/signin.js
index 8a487f82fff0b6a0cbb9914ac7fb311e698a7b87..edc58501195871df8b021adfdcd325f87ba6357d 100644
--- a/resources/static/pages/signin.js
+++ b/resources/static/pages/signin.js
@@ -48,7 +48,9 @@ BrowserID.signIn = (function() {
         email = helpers.getAndValidateEmail("#email");
 
     if(email) {
+      dom.setAttr('#email', 'disabled', 'disabled');
       user.addressInfo(email, function(info) {
+        dom.removeAttr('#email', 'disabled');
         addressInfo = info;
 
         if(info.type === "secondary") {
diff --git a/resources/static/pages/signup.js b/resources/static/pages/signup.js
index 3fef51badbfe646ad8f2d9ec523afff178dfd896..d7d4049ba49380eddc94045312655fd0685d1c5f 100644
--- a/resources/static/pages/signup.js
+++ b/resources/static/pages/signup.js
@@ -99,15 +99,17 @@ BrowserID.signUp = (function() {
           self = this;
 
       if (email) {
-
+        dom.setAttr('#email', 'disabled', 'disabled');
         user.isEmailRegistered(email, function(isRegistered) {
           if(isRegistered) {
+            dom.removeAttr('#email', 'disabled');
             $('#registeredEmail').html(email);
             showNotice(".alreadyRegistered");
             oncomplete && oncomplete(false);
           }
           else {
             user.addressInfo(email, function(info) {
+              dom.removeAttr('#email', 'disabled');
               if(info.type === "primary") {
                 createPrimaryUser.call(self, info, oncomplete);
               }
diff --git a/resources/static/pages/start.js b/resources/static/pages/start.js
index f6bb1b258897ed2588cd7c148d8445065db60d09..62f92b774bed66ebba614798c1d1303912b51670 100644
--- a/resources/static/pages/start.js
+++ b/resources/static/pages/start.js
@@ -90,7 +90,7 @@ $(function() {
     // footer remains at the bottom of the screen.
     var paddingTop = 0, paddingBottom = 0;
 
-    if(paddingAddedToMinHeight()) {
+    if (paddingAddedToMinHeight()) {
       paddingTop = parseInt($("#content").css("padding-top") || 0, 10);
       paddingBottom = parseInt($("#content").css("padding-bottom") || 0, 10);
     }
@@ -107,7 +107,7 @@ $(function() {
   moduleManager.register("development", Development);
   moduleManager.start("development");
 
-  if(shouldCheckCookies(path)) {
+  if (shouldCheckCookies(path)) {
     // do a cookie check on every page except the main page.
     moduleManager.register("cookie_check", CookieCheck);
     moduleManager.start("cookie_check", { ready: start });
@@ -120,7 +120,7 @@ $(function() {
   function start(status) {
     // If cookies are disabled, do not run any of the page specific code and
     // instead just show the error message.
-    if(!status) return;
+    if (!status) return;
 
 
     if (!path || path === "/") {
@@ -144,13 +144,17 @@ $(function() {
         verifyFunction: "verifyEmail"
       });
     }
-    else if(path === "/verify_email_address") {
+    else if (path === "/verify_email_address") {
       var module = bid.verifySecondaryAddress.create();
       module.start({
         token: token,
         verifyFunction: "verifyUser"
       });
     }
+    else if (path === "/about") {
+      var module = bid.about.create();
+      module.start({});
+    }
     else {
       // Instead of throwing a hard error here, adding a message to the console
       // to let developers know something is up.
diff --git a/resources/static/shared/error-display.js b/resources/static/shared/error-display.js
index c607eac8f9c37b1a0ecaf07af2b7e50a47699338..7777364d2242006aca12f7c98c08d099466c586e 100644
--- a/resources/static/shared/error-display.js
+++ b/resources/static/shared/error-display.js
@@ -14,7 +14,12 @@ BrowserID.ErrorDisplay = (function() {
     /**
      * XXX What a big steaming pile, use CSS animations for this!
      */
-    $("#moreInfo").slideDown();
+    $("#moreInfo").slideDown(function() {
+      // The expanded info may be partially obscured on mobile devices in
+      // landscape mode.  Force the screen size hacks to account for the new
+      // expanded size.
+      dom.fireEvent(window, "resize");
+    });
     $("#openMoreInfo").css({visibility: "hidden"});
   }
 
diff --git a/resources/static/shared/modules/page_module.js b/resources/static/shared/modules/page_module.js
index 02f7a3012e4ce61d1c5923493a882fd74a592588..36aa78c7c29a72aae98d2d6552b08100467c54b2 100644
--- a/resources/static/shared/modules/page_module.js
+++ b/resources/static/shared/modules/page_module.js
@@ -15,16 +15,6 @@ BrowserID.Modules.PageModule = (function() {
       cancelEvent = helpers.cancelEvent,
       mediator = bid.Mediator;
 
-   function onKeyup(event) {
-    if (event.which === 13) {
-      // IE8 does not trigger the submit event when hitting enter. Submit the
-      // form if the key press was an enter and prevent the default action so
-      // the form is not submitted twice.
-      event.preventDefault();
-      this.submit();
-    }
-   }
-
    function onSubmit() {
      if (!dom.hasClass("body", "submit_disabled") && this.validate()) {
        this.submit();
@@ -69,7 +59,6 @@ BrowserID.Modules.PageModule = (function() {
       self.options = options || {};
 
       self.bind("form", "submit", cancelEvent(onSubmit));
-      self.bind("input", "keyup", onKeyup);
     },
 
     stop: function() {
diff --git a/resources/static/shared/screens.js b/resources/static/shared/screens.js
index 0ce5cd13392214a46ad7c22c839945c284b854cd..e45daaea63ce87b41b4759c3497852602b78fbe3 100644
--- a/resources/static/shared/screens.js
+++ b/resources/static/shared/screens.js
@@ -15,11 +15,13 @@ BrowserID.Screens = (function() {
       show: function(template, vars) {
         renderer.render(target + " .contents", template, vars);
         dom.addClass(BODY, className);
+        dom.fireEvent(window, "resize");
         this.visible = true;
       },
 
       hide: function() {
         dom.removeClass(BODY, className);
+        dom.fireEvent(window, "resize");
         this.visible = false;
       }
     }
diff --git a/resources/static/test/cases/pages/about.js b/resources/static/test/cases/pages/about.js
new file mode 100644
index 0000000000000000000000000000000000000000..298642ce39fc66e35c73f04e9a6be107b781ebdd
--- /dev/null
+++ b/resources/static/test/cases/pages/about.js
@@ -0,0 +1,33 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global test: true, start: true, module: true, ok: true, equal: true, BrowserID:true */
+/* 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/. */
+(function() {
+  "use strict";
+
+  var bid = BrowserID,
+      testHelpers = bid.TestHelpers,
+      controller;
+
+  module("pages/about", {
+    setup: function() {
+      testHelpers.setup();
+      bid.Renderer.render("#page_head", "site/about", {});
+    },
+    teardown: function() {
+      testHelpers.teardown();
+    }
+  });
+
+  function createController(options, callback) {
+    controller = BrowserID.about.create();
+    controller.start(options);
+  }
+
+  test("start - no errors", function() {
+    createController({});
+    ok(controller, "controller created");
+  });
+
+}());
diff --git a/resources/static/test/cases/shared/modules/page_module.js b/resources/static/test/cases/shared/modules/page_module.js
index 96c2d441b079d0142f0a162979a0894987cbb1eb..ebd90fdba6b1f573a564ed409aca4196a781926a 100644
--- a/resources/static/test/cases/shared/modules/page_module.js
+++ b/resources/static/test/cases/shared/modules/page_module.js
@@ -200,34 +200,5 @@
     equal(submitCalled, true, "submit permitted to complete");
   });
 
-  test("form is submitted on 'enter keyup' event", function() {
-    createController();
-    controller.renderDialog("test_template_with_input", {
-      title: "Test title",
-      message: "Test message"
-    });
-
-    controller.start();
-
-
-    var submitCalled = 0;
-    controller.submit = function() {
-      submitCalled++;
-    };
-
-    // synthesize the entire series of key* events so we replicate the behavior
-    // of keyboard interaction.  The order of events is keydown, keypress,
-    // keyup (http://unixpapa.com/js/key.html).
-    var e = jQuery.Event("keydown", { keyCode: 13, which: 13 });
-    $("#templateInput").trigger(e);
-
-    var e = jQuery.Event("keypress", { keyCode: 13, which: 13 });
-    $("#templateInput").trigger(e);
-
-    var e = jQuery.Event("keyup", { keyCode: 13, which: 13 });
-    $("#templateInput").trigger(e);
-
-    equal(submitCalled, 1, "submit called a single time");
-  });
 }());
 
diff --git a/resources/views/about.ejs b/resources/views/about.ejs
index a7eb8dea4e292cea91e2080126587a176305d933..dfdeba036ae1e32c4470c9530e9b96b6a31d2792 100644
--- a/resources/views/about.ejs
+++ b/resources/views/about.ejs
@@ -3,38 +3,46 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <div id="content" class="display_always">
-    <div id="about">
-
-        <h1 class="center">
-          <strong>Persona</strong> is an <strong>easier</strong> way to sign in
-        </h1>
-
-
-        <div class="row one cf">
-            <img src="/i/tutorial_1.png">
-            <div>
-                <div class="steps"></div> On your favorite website that supports Persona, Click the &lsquo;Sign In&rsquo; button
-            </div>
-        </div>
-
-        <div class="row two cf">
-            <img src="/i/tutorial_2.png">
-            <div>
-                <div class="steps"></div> Select your preferred Email
-            </div>
-
-        </div>
-
-        <div class="row three cf">
-            <img src="/i/tutorial_3.png">
-            <div>
-                <div class="steps"></div> No passwords necessary, you're done!
-            </div>
-        </div>
-
-        <div class="row cf center">
-            If you need some help with Persona, head over to our <a href="https://support.mozilla.com/en-US/kb/what-browserid-and-how-does-it-work" target="_blank">support center</a>.
-        </div>
-    </div>
+    <div class="about">
+        <section class="simple-signon">
+            <h2 class="title">Simplified sign-on.</h2>
+            <article class="blurb">
+                <div class="info first">
+                    <h1>Persona replaces multiple passwords</h1>
+                    <p>Sites such as <a href="http://crossword.thetimes.co.uk/">The Times Crossword</a>, <a href="http://openphoto.net/">OpenPhoto</a> and <a href="https://www.voo.st/">Voost</a> use Persona instead of usernames to sign you in.</p><p>This means you only need one password to sign in to many sites.</p>
+                </div>
+
+                <div class="graphic">
+                    <img src="i/one-password-graphic.png" alt="One password to rule them all.">
+                </div>
+            </article>
+
+            <article class="blurb flexible">
+                <div class="graphic first">
+                    <img src="i/flexible-graphic.png" alt="Use multiple email addresses">
+                </div>
+
+                <div class="info">
+                    <h1>Persona is flexible</h1>
+                    <p>Within Persona, your identity is your email address. You can use as many email addresses as you want, but you still only need one password.</p>
+                </div>
+            </article>
+        </section>
+
+        <section class="privacy">
+            <h2 class="title">Real privacy.</h2>
+
+            <article class="blurb half first" style="min-height: 195px; ">
+                <h1>Persona is proudly non-profit for you</h1>
+                <p>Persona is developed by Mozilla, a not-for-profit company trusted throughout the Web community. Our goal is to create technologies that balance an open Web platform with people’s privacy.</p>
+            </article>
+            <article class="blurb half">
+                <h1>Persona preserves your privacy</h1>
+                <p>Persona does not track your activity around the Web. It creates a wall between signing you in and what you do once you’re there. The history of what sites you visit is stored only on your own computer.</p>
+            </article>
+        </section>
+
+        <a href="https://developer.mozilla.org/en/BrowserID/Quick_Setup" class="developers"><img src="i/developers-link.png" alt="Persona for developers"><span>Implement Persona on your site </span>Developer guides and API documentation</a>
+    </div><!-- #dashboard -->
 </div>
 
diff --git a/resources/views/test.ejs b/resources/views/test.ejs
index 7f05abc2301be6417ad5444f2932d358047a7d19..da1332fe11b80a0fe7d2547c457495081ea4b377 100644
--- a/resources/views/test.ejs
+++ b/resources/views/test.ejs
@@ -143,6 +143,7 @@
     <script src="/pages/manage_account.js"></script>
     <script src="/pages/signin.js"></script>
     <script src="/pages/signup.js"></script>
+    <script src="/pages/about.js"></script>
 
     <script src="testHelpers/helpers.js"></script>
 
@@ -179,6 +180,7 @@
     <script src="cases/pages/signin.js"></script>
     <script src="cases/pages/signup.js"></script>
     <script src="cases/pages/manage_account.js"></script>
+    <script src="cases/pages/about.js"></script>
 
     <script src="cases/resources/internal_api.js"></script>
     <script src="cases/resources/helpers.js"></script>
diff --git a/scripts/browserid.spec b/scripts/browserid.spec
index 5429472cb4112474e621c0da403e096cd0e73664..6bb044ea89e314de69c2ad4b6b31dcbdb7bfdcd2 100644
--- a/scripts/browserid.spec
+++ b/scripts/browserid.spec
@@ -1,7 +1,7 @@
 %define _rootdir /opt/browserid
 
 Name:          browserid-server
-Version:       0.2012.06.22
+Version:       0.2012.07.06
 Release:       1%{?dist}_%{svnrev}
 Summary:       BrowserID server
 Packager:      Pete Fritchman <petef@mozilla.com>
diff --git a/scripts/run_locally.js b/scripts/run_locally.js
index 2a0b9bca58e6d832abee7774b5fa4f1696f58f9a..346fa7dda7dff3152af234e9f9fef5261e0d01fd 100755
--- a/scripts/run_locally.js
+++ b/scripts/run_locally.js
@@ -112,11 +112,19 @@ function runDaemon(daemon, cb) {
   });
 };
 
+// start all daemons except the router in parallel
 var daemonNames = Object.keys(daemonsToRun);
-function runNextDaemon() {
-  if (daemonNames.length) runDaemon(daemonNames.shift(), runNextDaemon);
-}
-runNextDaemon();
+daemonNames.splice(daemonNames.indexOf('router'), 1);
+
+var numDaemonsRun = 0;
+daemonNames.forEach(function(dn) {
+  runDaemon(dn, function() {
+    if (++numDaemonsRun === daemonNames.length) {
+      // after all daemons are up and running, start the router
+      runDaemon('router', function() { });
+    }
+  });
+});
 
 process.on('SIGINT', function () {
   console.log('\nSIGINT recieved! trying to shut down gracefully...');
diff --git a/tests/conformance-test.js b/tests/conformance-test.js
old mode 100644
new mode 100755
diff --git a/tests/heartbeat-test.js b/tests/heartbeat-test.js
new file mode 100755
index 0000000000000000000000000000000000000000..815f97120003b062da83fe9d898acc0fc16ff471
--- /dev/null
+++ b/tests/heartbeat-test.js
@@ -0,0 +1,122 @@
+#!/usr/bin/env node
+
+/* 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/. */
+
+require('./lib/test_env.js');
+
+const assert =
+require('assert'),
+vows = require('vows'),
+start_stop = require('./lib/start-stop.js'),
+wsapi = require('./lib/wsapi.js'),
+db = require('../lib/db.js'),
+config = require('../lib/configuration.js'),
+bcrypt = require('bcrypt'),
+http = require('http');
+
+var suite = vows.describe('heartbeat');
+
+// disable vows (often flakey?) async error behavior
+suite.options.error = false;
+
+start_stop.addStartupBatches(suite);
+
+// test deep and shallow heartbeats work for all processes
+[ 10004, 10002, 10003, 10004, 10007 ].forEach(function(port) {
+  [ true, false ].forEach(function(shallow) {
+    var testName = "shallow heartbeat check for 127.0.0.1:" + port;
+    suite.addBatch({
+      testName: {
+        topic: function() {
+          var self = this;
+
+          var req = http.get({
+            host: '127.0.0.1',
+            port: port,
+            path: '/__heartbeat__' + ( shallow ? "" : "?deep=true")
+          }, function(res) {
+            self.callback(null, res.statusCode);
+            req.abort();
+          }).on('error', function(e) {
+            self.callback(e, null);
+            req.abort();
+          });
+        },
+        "works":     function(err, code) {
+          assert.strictEqual(err, null);
+          assert.equal(code, 200);
+        }
+      }
+    });
+  });
+});
+
+// now let's SIGSTOP the browserid process and verify that the router's
+// deep heartbeat fails within 11s
+suite.addBatch({
+  "stopping the browserid process": {
+    topic: function() {
+      process.kill(parseInt(process.env['BROWSERID_PID'], 10), 'SIGSTOP');      
+      this.callback();
+    },
+    "then doing a deep __heartbeat__ on router": {
+      topic: function() {
+        var self = this;
+        var start = new Date();
+        var req = http.get({
+          host: '127.0.0.1',
+          port: 10002,
+          path: '/__heartbeat__?deep=true'
+        }, function(res) {
+          self.callback(null, res.statusCode, start);
+          req.abort();
+        }).on('error', function(e) {
+          self.callback(e, null);
+          req.abort();
+        });
+      },
+      "fails": function(e, code, start) {
+        assert.ok(!e);
+        assert.strictEqual(500, code);
+      },
+      "takes about 5s": function(e, code, start) {
+        assert.ok(!e);
+        var elapsedMS = new Date() - start;
+        assert.ok(3000 < elapsedMS < 7000);
+      },
+      "but upon SIGCONT": {
+        topic: function(e, code) {
+          process.kill(parseInt(process.env['BROWSERID_PID'], 10), 'SIGCONT');      
+          this.callback();
+        },
+        "a deep heartbeat": {
+          topic: function() {
+            var self = this;
+            var req = http.get(
+              { host: '127.0.0.1', port: 10002, path: '/__heartbeat__?deep=true'},
+              function(res) {
+                self.callback(null, res.statusCode);
+                req.abort();
+              }).on('error', function(e) {
+                self.callback(e, null);
+                req.abort();
+              });
+          },
+          "works": function(err, code) {
+            assert.ok(!err);
+            assert.strictEqual(200, code);
+          }
+        }
+      }
+    }
+  }
+});
+
+
+start_stop.addShutdownBatches(suite);
+
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);
diff --git a/tests/lib/start-stop.js b/tests/lib/start-stop.js
index b73aa2ea8f4951a1c615788e750259ab93bd7ece..742fb032745d0607052ff7b19de83157dff95e8a 100644
--- a/tests/lib/start-stop.js
+++ b/tests/lib/start-stop.js
@@ -46,10 +46,13 @@ function setupProc(proc) {
         }
       }
       var tokenRegex = new RegExp('token=([A-Za-z0-9]+)$', 'm');
+      var pidRegex = new RegExp('^spawned (\\w+) \\(.*\\) with pid ([0-9]+)$');
 
       if (!sentReady && /^router.*127\.0\.0\.1:10002$/.test(x)) {
         exports.browserid.emit('ready');
         sentReady = true;
+      } else if (!sentReady && (m = pidRegex.exec(x))) {
+        process.env[m[1].toUpperCase() + "_PID"] = m[2]; 
       } else if (m = tokenRegex.exec(x)) {
         if (!(/forwarding request:/.test(x))) {
           tokenStack.push(m[1]);
diff --git a/tests/simple-stage-user-utf8-password.js b/tests/simple-stage-user-utf8-password.js
old mode 100644
new mode 100755