diff --git a/app/index.html b/app/index.html
index 72771c3..5bb4e95 100644
--- a/app/index.html
+++ b/app/index.html
@@ -153,9 +153,10 @@
           -->
           Next 
           
-            Why do we need your email? We link your SSL certificates to the
-            email you use so you can manage your certificates in the future,
-            and get important email updates about them.
+            Why do we need your email?
+            We link your SSL certificates to the email you use so that you'll
+            be notified before the certificate expires and so you can manage
+            your certificates in the future.
           
         
 
diff --git a/app/js/app.js b/app/js/app.js
deleted file mode 100644
index 95ab751..0000000
--- a/app/js/app.js
+++ /dev/null
@@ -1,670 +0,0 @@
-(function () {
-'use strict';
-
-  /*global URLSearchParams,Headers*/
-  var BROWSER_SUPPORTS_ECDSA;
-  var $qs = function (s) { return window.document.querySelector(s); };
-  var $qsa = function (s) { return window.document.querySelectorAll(s); };
-  var info = {};
-  var steps = {};
-  var nonce;
-  var kid;
-  var i = 1;
-  var BACME = window.BACME;
-  var PromiseA = window.Promise;
-  var crypto = window.crypto;
-
-  function testEcdsaSupport() {
-    var opts = {
-      type: 'ECDSA'
-    , bitlength: '256'
-    };
-    return BACME.accounts.generateKeypair(opts).then(function (jwk) {
-      return crypto.subtle.importKey(
-        "jwk"
-      , jwk
-      , { name: "ECDSA", namedCurve: "P-256" }
-      , true
-      , ["sign"]
-      ).then(function (privateKey) {
-        return window.crypto.subtle.exportKey("pkcs8", privateKey);
-      });
-    });
-  }
-  function testRsaSupport() {
-    var opts = {
-      type: 'RSA'
-    , bitlength: '2048'
-    };
-    return BACME.accounts.generateKeypair(opts).then(function (jwk) {
-      return crypto.subtle.importKey(
-        "jwk"
-      , jwk
-      , { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } }
-      , true
-      , ["sign"]
-      ).then(function (privateKey) {
-        return window.crypto.subtle.exportKey("pkcs8", privateKey);
-      });
-    });
-  }
-  function testKeypairSupport() {
-    return testEcdsaSupport().then(function () {
-      console.info("[crypto] ECDSA is supported");
-      BROWSER_SUPPORTS_ECDSA = true;
-      localStorage.setItem('version', '1');
-      return true;
-    }).catch(function () {
-      console.warn("[crypto] ECDSA is NOT fully supported");
-      BROWSER_SUPPORTS_ECDSA = false;
-
-      // fix previous firefox browsers
-      if (!localStorage.getItem('version')) {
-        localStorage.clear();
-        localStorage.setItem('version', '1');
-      }
-
-      return false;
-    });
-  }
-  testKeypairSupport().then(function (ecdsaSupport) {
-    if (ecdsaSupport) {
-      return true;
-    }
-
-    return testRsaSupport().then(function () {
-      console.info('[crypto] RSA is supported');
-    }).catch(function (err) {
-      console.error('[crypto] could not use either EC nor RSA.');
-      console.error(err);
-      window.alert("Your browser is cryptography support (neither RSA or EC is usable). Please use Chrome, Firefox, or Safari.");
-    });
-  });
-
-  var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
-  function updateApiType() {
-    console.log("type updated");
-    /*jshint validthis: true */
-    var input = this || Array.prototype.filter.call(
-      $qsa('.js-acme-api-type'), function ($el) { return $el.checked; }
-    )[0];
-    console.log('ACME api type radio:', input.value);
-    $qs('.js-acme-directory-url').value = apiUrl.replace(/{{env}}/g, input.value);
-  }
-  $qsa('.js-acme-api-type').forEach(function ($el) {
-    $el.addEventListener('change', updateApiType);
-  });
-  updateApiType();
-
-  function hideForms() {
-    $qsa('.js-acme-form').forEach(function (el) {
-      el.hidden = true;
-    });
-  }
-
-  function updateProgress(currentStep) {
-    var progressSteps = $qs("#js-progress-bar").children;
-    for(var j = 0; j < progressSteps.length; j++) {
-      if(j < currentStep) {
-        progressSteps[j].classList.add("js-progress-step-complete");
-        progressSteps[j].classList.remove("js-progress-step-started");
-      } else if(j === currentStep) {
-        progressSteps[j].classList.remove("js-progress-step-complete");
-        progressSteps[j].classList.add("js-progress-step-started");
-      } else {
-        progressSteps[j].classList.remove("js-progress-step-complete");
-        progressSteps[j].classList.remove("js-progress-step-started");
-      }
-    }
-  }
-
-  function submitForm(ev) {
-    var j = i;
-    i += 1;
-
-    return PromiseA.resolve(steps[j].submit(ev)).catch(function (err) {
-      console.error(err);
-      window.alert("Something went wrong. It's our fault not yours. Please email aj@rootprojects.org and let him know that 'step " + j + "' failed.");
-    });
-  }
-
-  $qsa('.js-acme-form').forEach(function ($el) {
-    $el.addEventListener('submit', function (ev) {
-      ev.preventDefault();
-      submitForm(ev);
-    });
-  });
-
-  function updateChallengeType() {
-    /*jshint validthis: true*/
-    var input = this || Array.prototype.filter.call(
-      $qsa('.js-acme-challenge-type'), function ($el) { return $el.checked; }
-    )[0];
-    console.log('ch type radio:', input.value);
-    $qs('.js-acme-verification-wildcard').hidden = true;
-    $qs('.js-acme-verification-http-01').hidden = true;
-    $qs('.js-acme-verification-dns-01').hidden = true;
-    if (info.challenges.wildcard) {
-      $qs('.js-acme-verification-wildcard').hidden = false;
-    }
-    if (info.challenges[input.value]) {
-      $qs('.js-acme-verification-' + input.value).hidden = false;
-    }
-  }
-  $qsa('.js-acme-challenge-type').forEach(function ($el) {
-    $el.addEventListener('change', updateChallengeType);
-  });
-
-  function saveContact(email, domains) {
-    // to be used for good, not evil
-    return window.fetch('https://api.rootprojects.org/api/rootprojects.org/public/community', {
-      method: 'POST'
-    , cors: true
-    , headers: new Headers({ 'Content-Type': 'application/json' })
-    , body: JSON.stringify({
-        address: email
-      , project: 'greenlock-domains@rootprojects.org'
-      , domain: domains.join(',')
-      })
-    }).catch(function (err) {
-      console.error(err);
-    });
-  }
-
-  steps[1] = function () {
-    updateProgress(0);
-    hideForms();
-    $qs('.js-acme-form-domains').hidden = false;
-  };
-  steps[1].submit = function () {
-    info.identifiers = $qs('.js-acme-domains').value.split(/\s*,\s*/g).map(function (hostname) {
-      return { type: 'dns', value: hostname.toLowerCase().trim() };
-    }).slice(0,1); //Disable multiple values for now.  We'll just take the first and work with it.
-    info.identifiers.sort(function (a, b) {
-      if (a === b) { return 0; }
-      if (a < b) { return 1; }
-      if (a > b) { return -1; }
-    });
-
-    return BACME.directory({ directoryUrl: $qs('.js-acme-directory-url').value }).then(function (directory) {
-      $qs('.js-acme-tos-url').href = directory.meta.termsOfService;
-      return BACME.nonce().then(function (_nonce) {
-        nonce = _nonce;
-
-        console.log("MAGIC STEP NUMBER in 1 is:", i);
-        steps[i]();
-      });
-    });
-  };
-
-  steps[2] = function () {
-    updateProgress(0);
-    hideForms();
-    $qs('.js-acme-form-account').hidden = false;
-  };
-  steps[2].submit = function () {
-    var email = $qs('.js-acme-account-email').value.toLowerCase().trim();
-
-    info.contact = [ 'mailto:' + email ];
-    info.agree = $qs('.js-acme-account-tos').checked;
-    info.greenlockAgree = $qs('.js-gl-tos').checked;
-    // TODO
-    // options for
-    // * regenerate key
-    // * ECDSA / RSA / bitlength
-
-    // TODO ping with version and account creation
-    setTimeout(saveContact, 100, email, info.identifiers.map(function (ident) { return ident.value; }));
-
-    var jwk = JSON.parse(localStorage.getItem('account:' + email) || 'null');
-    var p;
-
-    function createKeypair() {
-      var opts;
-
-      if(BROWSER_SUPPORTS_ECDSA) {
-        opts = {
-          type: 'ECDSA'
-        , bitlength: '256'
-        };
-      } else {
-        opts = {
-          type: 'RSA'
-        , bitlength: '2048'
-        };
-      }
-
-      return BACME.accounts.generateKeypair(opts).then(function (jwk) {
-        localStorage.setItem('account:' + email, JSON.stringify(jwk));
-        return jwk;
-      });
-    }
-
-    if (jwk) {
-      p = PromiseA.resolve(jwk);
-    } else {
-      p = testKeypairSupport().then(createKeypair);
-    }
-
-    function createAccount(jwk) {
-      console.log('account jwk:');
-      console.log(jwk);
-      delete jwk.key_ops;
-      info.jwk = jwk;
-      return BACME.accounts.sign({
-        jwk: jwk
-      , contacts: [ 'mailto:' + email ]
-      , agree: info.agree
-      , nonce: nonce
-      , kid: kid
-      }).then(function (signedAccount) {
-        return BACME.accounts.set({
-          signedAccount: signedAccount
-        }).then(function (account) {
-          console.log('account:');
-          console.log(account);
-          kid = account.kid;
-          return kid;
-        });
-      });
-    }
-
-    return p.then(function (_jwk) {
-      jwk = _jwk;
-      kid = JSON.parse(localStorage.getItem('account-kid:' + email) || 'null');
-      var p2;
-
-      // TODO save account id rather than always retrieving it
-      if (kid) {
-        p2 = PromiseA.resolve(kid);
-      } else {
-        p2 = createAccount(jwk);
-      }
-
-      return p2.then(function (_kid) {
-        kid = _kid;
-        info.kid = kid;
-        return BACME.orders.sign({
-          jwk: jwk
-        , identifiers: info.identifiers
-        , kid: kid
-        }).then(function (signedOrder) {
-          return BACME.orders.create({
-            signedOrder: signedOrder
-          }).then(function (order) {
-            info.finalizeUrl = order.finalize;
-            info.orderUrl = order.url; // from header Location ???
-            return BACME.thumbprint({ jwk: jwk }).then(function (thumbprint) {
-              return BACME.challenges.all().then(function (claims) {
-                console.log('claims:');
-                console.log(claims);
-                var obj = { 'dns-01': [], 'http-01': [], 'wildcard': [] };
-                info.challenges = obj;
-                var map = {
-                  'http-01': '.js-acme-verification-http-01'
-                , 'dns-01': '.js-acme-verification-dns-01'
-                , 'wildcard': '.js-acme-verification-wildcard'
-                };
-
-                /*
-                var tpls = {};
-                Object.keys(map).forEach(function (k) {
-                  var sel = map[k] + ' tbody';
-                  console.log(sel);
-                  tpls[k] = $qs(sel).innerHTML;
-                  $qs(map[k] + ' tbody').innerHTML = '';
-                });
-                */
-
-                // TODO make Promise-friendly
-                return PromiseA.all(claims.map(function (claim) {
-                  var hostname = claim.identifier.value;
-                  return PromiseA.all(claim.challenges.map(function (c) {
-                    var keyAuth = BACME.challenges['http-01']({
-                      token: c.token
-                    , thumbprint: thumbprint
-                    , challengeDomain: hostname
-                    });
-                    return BACME.challenges['dns-01']({
-                      keyAuth: keyAuth.value
-                    , challengeDomain: hostname
-                    }).then(function (dnsAuth) {
-                      var data = {
-                        type: c.type
-                      , hostname: hostname
-                      , url: c.url
-                      , token: c.token
-                      , keyAuthorization: keyAuth
-                      , httpPath: keyAuth.path
-                      , httpAuth: keyAuth.value
-                      , dnsType: dnsAuth.type
-                      , dnsHost: dnsAuth.host
-                      , dnsAnswer: dnsAuth.answer
-                      };
-
-                      console.log('');
-                      console.log('CHALLENGE');
-                      console.log(claim);
-                      console.log(c);
-                      console.log(data);
-                      console.log('');
-
-                      if (claim.wildcard) {
-                        obj.wildcard.push(data);
-                        let verification = $qs(".js-acme-verification-wildcard");
-                        verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname;
-                        verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost;
-                        verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer;
-
-                      } else if(obj[data.type]) {
-
-                        obj[data.type].push(data);
-
-                        if ('dns-01' === data.type) {
-                          let verification = $qs(".js-acme-verification-dns-01");
-                          verification.querySelector(".js-acme-ver-hostname").innerHTML = data.hostname;
-                          verification.querySelector(".js-acme-ver-txt-host").innerHTML = data.dnsHost;
-                          verification.querySelector(".js-acme-ver-txt-value").innerHTML = data.dnsAnswer;
-                        } else if ('http-01' === data.type) {
-                          $qs(".js-acme-ver-file-location").innerHTML = data.httpPath.split("/").slice(-1);
-                          $qs(".js-acme-ver-content").innerHTML = data.httpAuth;
-                          $qs(".js-acme-ver-uri").innerHTML = data.httpPath;
-                          $qs(".js-download-verify-link").href =
-                            "data:text/octet-stream;base64," + window.btoa(data.httpAuth);
-                          $qs(".js-download-verify-link").download = data.httpPath.split("/").slice(-1);
-                        }
-                      }
-
-                    });
-
-                  }));
-                })).then(function () {
-
-                  // hide wildcard if no wildcard
-                  // hide http-01 and dns-01 if only wildcard
-                  if (!obj.wildcard.length) {
-                    $qs('.js-acme-wildcard-challenges').hidden = true;
-                  }
-                  if (!obj['http-01'].length) {
-                    $qs('.js-acme-challenges').hidden = true;
-                  }
-
-                  updateChallengeType();
-
-                  console.log("MAGIC STEP NUMBER in 2 is:", i);
-                  steps[i]();
-                });
-
-              });
-            });
-          });
-        });
-      });
-    }).catch(function (err) {
-      console.error('Step \'\' Error:');
-      console.error(err, err.stack);
-      window.alert("An error happened at Step " + i + ", but it's not your fault. Email aj@rootprojects.org and let him know.");
-    });
-  };
-
-  steps[3] = function () {
-    updateProgress(1);
-    hideForms();
-    $qs('.js-acme-form-challenges').hidden = false;
-  };
-  steps[3].submit = function () {
-    var chType;
-    Array.prototype.some.call($qsa('.js-acme-challenge-type'), function ($el) {
-      if ($el.checked) {
-        chType = $el.value;
-        return true;
-      }
-    });
-    console.log('chType is:', chType);
-    var chs = [];
-
-    // do each wildcard, if any
-    // do each challenge, by selected type only
-    [ 'wildcard', chType].forEach(function (typ) {
-      info.challenges[typ].forEach(function (ch) {
-        // { jwk, challengeUrl, accountId (kid) }
-        chs.push({
-          jwk: info.jwk
-        , challengeUrl: ch.url
-        , accountId: info.kid
-        });
-      });
-    });
-    console.log("INFO.challenges !!!!!", info.challenges);
-
-    var results = [];
-    function nextChallenge() {
-      var ch = chs.pop();
-      if (!ch) { return results; }
-      return BACME.challenges.accept(ch).then(function (result) {
-        results.push(result);
-        return nextChallenge();
-      });
-    }
-
-    // for now just show the next page immediately (its a spinner)
-    steps[i]();
-    return nextChallenge().then(function (results) {
-      console.log('challenge status:', results);
-      var polls = results.slice(0);
-      var allsWell = true;
-
-      function checkPolls() {
-        return new PromiseA(function (resolve) {
-          setTimeout(resolve, 1000);
-        }).then(function () {
-          return PromiseA.all(polls.map(function (poll) {
-            return BACME.challenges.check({ challengePollUrl: poll.url });
-          })).then(function (polls) {
-            console.log(polls);
-
-            polls = polls.filter(function (poll) {
-              //return 'valid' !== poll.status && 'invalid' !== poll.status;
-              if ('pending' === poll.status) {
-                return true;
-              }
-
-              if ('invalid' === poll.status) {
-                allsWell = false;
-                window.alert("verification failed:" + poll.error.detail);
-                return;
-              }
-
-              if (poll.error) {
-                window.alert("verification failed:" + poll.error.detail);
-                return;
-              }
-
-              if ('valid' !== poll.status) {
-                allsWell = false;
-                console.warn('BAD POLL STATUS', poll);
-                window.alert("unknown error: " + JSON.stringify(poll, null, 2));
-              }
-              // TODO show status in HTML
-            });
-
-            if (polls.length) {
-              return checkPolls();
-            }
-            return true;
-          });
-        });
-      }
-
-      return checkPolls().then(function () {
-        if (allsWell) {
-          return submitForm();
-        }
-      });
-    });
-  };
-
-  // https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format
-  function spkiToPEM(keydata, pemName){
-      var keydataS = arrayBufferToString(keydata);
-      var keydataB64 = window.btoa(keydataS);
-      var keydataB64Pem = formatAsPem(keydataB64, pemName);
-      return keydataB64Pem;
-  }
-
-  function arrayBufferToString( buffer ) {
-      var binary = '';
-      var bytes = new Uint8Array( buffer );
-      var len = bytes.byteLength;
-      for (var i = 0; i < len; i++) {
-          binary += String.fromCharCode( bytes[ i ] );
-      }
-      return binary;
-  }
-
-
-  function formatAsPem(str, pemName) {
-      var finalString = '-----BEGIN ' + pemName + ' PRIVATE KEY-----\n';
-
-      while(str.length > 0) {
-          finalString += str.substring(0, 64) + '\n';
-          str = str.substring(64);
-      }
-
-      finalString = finalString + '-----END ' + pemName + ' PRIVATE KEY-----';
-
-      return finalString;
-  }
-
-  // spinner
-  steps[4] = function () {
-    updateProgress(1);
-    hideForms();
-    $qs('.js-acme-form-poll').hidden = false;
-  };
-  steps[4].submit = function () {
-    console.log('Congrats! Auto advancing...');
-
-    var key = info.identifiers.map(function (ident) { return ident.value; }).join(',');
-    var serverJwk = JSON.parse(localStorage.getItem('server:' + key) || 'null');
-    var p;
-
-    function createKeypair() {
-      var opts;
-
-      if (BROWSER_SUPPORTS_ECDSA) {
-        opts = { type: 'ECDSA', bitlength: '256' };
-      } else {
-        opts = { type: 'RSA', bitlength: '2048' };
-      }
-
-      return BACME.domains.generateKeypair(opts).then(function (serverJwk) {
-        localStorage.setItem('server:' + key, JSON.stringify(serverJwk));
-        return serverJwk;
-      });
-    }
-
-    if (serverJwk) {
-      p = PromiseA.resolve(serverJwk);
-    } else {
-      p = createKeypair();
-    }
-
-    return p.then(function (_serverJwk) {
-      serverJwk = _serverJwk;
-      info.serverJwk = serverJwk;
-      // { serverJwk, domains }
-      return BACME.orders.generateCsr({
-        serverJwk: serverJwk
-      , domains: info.identifiers.map(function (ident) {
-          return ident.value;
-        })
-      }).then(function (csrweb64) {
-        return BACME.orders.finalize({
-          csr: csrweb64
-        , jwk: info.jwk
-        , finalizeUrl: info.finalizeUrl
-        , accountId: info.kid
-        });
-      }).then(function () {
-        function checkCert() {
-          return new PromiseA(function (resolve) {
-            setTimeout(resolve, 1000);
-          }).then(function () {
-            return BACME.orders.check({ orderUrl: info.orderUrl });
-          }).then(function (reply) {
-            if ('processing' === reply) {
-              return checkCert();
-            }
-            return reply;
-          });
-        }
-
-        return checkCert();
-      }).then(function (reply) {
-        return BACME.orders.receive({ certificateUrl: reply.certificate });
-      }).then(function (certs) {
-        console.log('WINNING!');
-        console.log(certs);
-        $qs('#js-fullchain').innerHTML = certs;
-        $qs("#js-download-fullchain-link").href =
-          "data:text/octet-stream;base64," + window.btoa(certs);
-
-        var wcOpts;
-        var pemName;
-        if (/^R/.test(info.serverJwk.kty)) {
-          pemName = 'RSA';
-          wcOpts = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
-        } else {
-          pemName = 'EC';
-          wcOpts = { name: "ECDSA", namedCurve: "P-256" };
-        }
-        return crypto.subtle.importKey(
-          "jwk"
-        , info.serverJwk
-        , wcOpts
-        , true
-        , ["sign"]
-        ).then(function (privateKey) {
-          return window.crypto.subtle.exportKey("pkcs8", privateKey);
-        }).then (function (keydata) {
-          var pem = spkiToPEM(keydata, pemName);
-          $qs('#js-privkey').innerHTML = pem;
-          $qs("#js-download-privkey-link").href =
-            "data:text/octet-stream;base64," + window.btoa(pem);
-          steps[i]();
-        });
-      });
-    }).catch(function (err) {
-      console.error(err.toString());
-      window.alert("An error happened in the final step, but it's not your fault. Email aj@rootprojects.org and let him know.");
-    });
-  };
-
-  steps[5] = function () {
-    updateProgress(2);
-    hideForms();
-    $qs('.js-acme-form-download').hidden = false;
-  };
-  steps[1]();
-
-  var params = new URLSearchParams(window.location.search);
-  var apiType = params.get('acme-api-type') || "staging-v02";
-
-  if(params.has('acme-domains')) {
-    console.log("acme-domains param: ", params.get('acme-domains'));
-    $qs('.js-acme-domains').value = params.get('acme-domains');
-
-    $qsa('.js-acme-api-type').forEach(function(ele) {
-      if(ele.value === apiType) {
-        ele.checked = true;
-      }
-    });
-
-    updateApiType();
-    steps[2]();
-    submitForm();
-  }
-
-  $qs('body').hidden = false;
-}());
diff --git a/app/js/bacme.js b/app/js/bacme.js
deleted file mode 100644
index fb63042..0000000
--- a/app/js/bacme.js
+++ /dev/null
@@ -1,699 +0,0 @@
-/*global CSR*/
-// CSR takes a while to load after the page load
-(function (exports) {
-'use strict';
-
-var BACME = exports.BACME = {};
-var webFetch = exports.fetch;
-var webCrypto = exports.crypto;
-var Promise = exports.Promise;
-
-var directoryUrl = 'https://acme-staging-v02.api.letsencrypt.org/directory';
-var directory;
-
-var nonceUrl;
-var nonce;
-
-var accountKeypair;
-var accountJwk;
-
-var accountUrl;
-
-BACME.challengePrefixes = {
-  'http-01': '/.well-known/acme-challenge'
-, 'dns-01': '_acme-challenge'
-};
-
-BACME._logHeaders = function (resp) {
-  console.log('Headers:');
-  Array.from(resp.headers.entries()).forEach(function (h) { console.log(h[0] + ': ' + h[1]); });
-};
-
-BACME._logBody = function (body) {
-  console.log('Body:');
-  console.log(JSON.stringify(body, null, 2));
-  console.log('');
-};
-
-BACME.directory = function (opts) {
-  return webFetch(opts.directoryUrl || directoryUrl, { mode: 'cors' }).then(function (resp) {
-    BACME._logHeaders(resp);
-    return resp.json().then(function (reply) {
-      if (/error/.test(reply.type)) {
-        return Promise.reject(new Error(reply.detail || reply.type));
-      }
-      directory = reply;
-      nonceUrl = directory.newNonce || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce';
-      accountUrl = directory.newAccount || 'https://acme-staging-v02.api.letsencrypt.org/acme/new-account';
-      orderUrl = directory.newOrder || "https://acme-staging-v02.api.letsencrypt.org/acme/new-order";
-      BACME._logBody(reply);
-      return reply;
-    });
-  });
-};
-
-BACME.nonce = function () {
-  return webFetch(nonceUrl, { mode: 'cors' }).then(function (resp) {
-    BACME._logHeaders(resp);
-    nonce = resp.headers.get('replay-nonce');
-    console.log('Nonce:', nonce);
-    // resp.body is empty
-    return resp.headers.get('replay-nonce');
-  });
-};
-
-BACME.accounts = {};
-
-// type = ECDSA
-// bitlength = 256
-BACME.accounts.generateKeypair = function (opts) {
-  return BACME.generateKeypair(opts).then(function (result) {
-    accountKeypair = result;
-
-    return webCrypto.subtle.exportKey(
-      "jwk"
-    , result.privateKey
-    ).then(function (privJwk) {
-
-      accountJwk = privJwk;
-      console.log('private jwk:');
-      console.log(JSON.stringify(privJwk, null, 2));
-
-      return privJwk;
-      /*
-      return webCrypto.subtle.exportKey(
-        "pkcs8"
-      , result.privateKey
-      ).then(function (keydata) {
-        console.log('pkcs8:');
-        console.log(Array.from(new Uint8Array(keydata)));
-
-        return privJwk;
-        //return accountKeypair;
-      });
-      */
-    });
-  });
-};
-
-// json to url-safe base64
-BACME._jsto64 = function (json) {
-  return btoa(JSON.stringify(json)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
-};
-
-var textEncoder = new TextEncoder();
-
-BACME._importKey = function (jwk) {
-  var alg; // I think the 256 refers to the hash
-  var wcOpts = {};
-  var extractable = true; // TODO make optionally false?
-  var priv = jwk;
-  var pub;
-
-  // ECDSA
-  if (/^EC/i.test(jwk.kty)) {
-    wcOpts.name = 'ECDSA';
-    wcOpts.namedCurve = jwk.crv;
-    alg = 'ES256';
-    pub = {
-      crv: priv.crv
-    , kty: priv.kty
-    , x: priv.x
-    , y: priv.y
-    };
-    if (!priv.d) {
-      priv = null;
-    }
-  }
-
-  // RSA
-  if (/^RS/i.test(jwk.kty)) {
-    wcOpts.name = 'RSASSA-PKCS1-v1_5';
-    wcOpts.hash = { name: "SHA-256" };
-    alg = 'RS256';
-    pub = {
-      e: priv.e
-    , kty: priv.kty
-    , n: priv.n
-    };
-    if (!priv.p) {
-      priv = null;
-    }
-  }
-
-  return window.crypto.subtle.importKey(
-    "jwk"
-  , pub
-  , wcOpts
-  , extractable
-  , [ "verify" ]
-  ).then(function (publicKey) {
-    function give(privateKey) {
-      return {
-        wcPub: publicKey
-      , wcKey: privateKey
-      , wcKeypair: { publicKey: publicKey, privateKey: privateKey }
-      , meta: {
-          alg: alg
-        , name: wcOpts.name
-        , hash: wcOpts.hash
-        }
-      , jwk: jwk
-      };
-    }
-    if (!priv) {
-      return give();
-    }
-    return window.crypto.subtle.importKey(
-      "jwk"
-    , priv
-    , wcOpts
-    , extractable
-    , [ "sign"/*, "verify"*/ ]
-    ).then(give);
-  });
-};
-BACME._sign = function (opts) {
-  var wcPrivKey = opts.abstractKey.wcKeypair.privateKey;
-  var wcOpts = opts.abstractKey.meta;
-  var alg = opts.abstractKey.meta.alg; // I think the 256 refers to the hash
-  var signHash;
-
-  console.log('kty', opts.abstractKey.jwk.kty);
-  signHash = { name: "SHA-" + alg.replace(/[a-z]+/ig, '') };
-
-  var msg = textEncoder.encode(opts.protected64 + '.' + opts.payload64);
-  console.log('msg:', msg);
-  return window.crypto.subtle.sign(
-    { name: wcOpts.name, hash: signHash }
-  , wcPrivKey
-  , msg
-  ).then(function (signature) {
-    //console.log('sig1:', signature);
-    //console.log('sig2:', new Uint8Array(signature));
-    //console.log('sig3:', Array.prototype.slice.call(new Uint8Array(signature)));
-    // convert buffer to urlsafe base64
-    var sig64 = btoa(Array.prototype.map.call(new Uint8Array(signature), function (ch) {
-      return String.fromCharCode(ch);
-    }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
-
-    console.log('[1] URL-safe Base64 Signature:');
-    console.log(sig64);
-
-    var signedMsg = {
-      protected: opts.protected64
-    , payload: opts.payload64
-    , signature: sig64
-    };
-
-    console.log('Signed Base64 Msg:');
-    console.log(JSON.stringify(signedMsg, null, 2));
-
-    return signedMsg;
-  });
-};
-// email = john.doe@gmail.com
-// jwk = { ... }
-// agree = true
-BACME.accounts.sign = function (opts) {
-
-  return BACME._importKey(opts.jwk).then(function (abstractKey) {
-
-    var payloadJson =
-      { termsOfServiceAgreed: opts.agree
-      , onlyReturnExisting: false
-      , contact: opts.contacts || [ 'mailto:' + opts.email ]
-      };
-    console.log('payload:');
-    console.log(payloadJson);
-    var payload64 = BACME._jsto64(
-      payloadJson
-    );
-
-    var protectedJson =
-      { nonce: opts.nonce
-      , url: accountUrl
-      , alg: abstractKey.meta.alg
-      , jwk: null
-      };
-
-    if (/EC/i.test(opts.jwk.kty)) {
-      protectedJson.jwk = {
-        crv: opts.jwk.crv
-      , kty: opts.jwk.kty
-      , x: opts.jwk.x
-      , y: opts.jwk.y
-      };
-    } else if (/RS/i.test(opts.jwk.kty)) {
-      protectedJson.jwk = {
-        e: opts.jwk.e
-      , kty: opts.jwk.kty
-      , n: opts.jwk.n
-      };
-    } else {
-      return Promise.reject(new Error("[acme.accounts.sign] unsupported key type '" + opts.jwk.kty + "'"));
-    }
-
-    console.log('protected:');
-    console.log(protectedJson);
-    var protected64 = BACME._jsto64(
-      protectedJson
-    );
-
-    // Note: this function hashes before signing so send data, not the hash
-    return BACME._sign({
-      abstractKey: abstractKey
-    , payload64: payload64
-    , protected64: protected64
-    });
-  });
-};
-
-var accountId;
-
-BACME.accounts.set = function (opts) {
-  nonce = null;
-  return window.fetch(accountUrl, {
-    mode: 'cors'
-  , method: 'POST'
-  , headers: { 'Content-Type': 'application/jose+json' }
-  , body: JSON.stringify(opts.signedAccount)
-  }).then(function (resp) {
-    BACME._logHeaders(resp);
-    nonce = resp.headers.get('replay-nonce');
-    accountId = resp.headers.get('location');
-    console.log('Next nonce:', nonce);
-    console.log('Location/kid:', accountId);
-
-    if (!resp.headers.get('content-type')) {
-     console.log('Body: ');
-
-     return { kid: accountId };
-    }
-
-    return resp.json().then(function (result) {
-      if (/^Error/i.test(result.detail)) {
-        return Promise.reject(new Error(result.detail));
-      }
-      result.kid = accountId;
-      BACME._logBody(result);
-
-      return result;
-    });
-  });
-};
-
-var orderUrl;
-
-BACME.orders = {};
-
-// identifiers = [ { type: 'dns', value: 'example.com' }, { type: 'dns', value: '*.example.com' } ]
-// signedAccount
-BACME.orders.sign = function (opts) {
-  var payload64 = BACME._jsto64({ identifiers: opts.identifiers });
-
-  return BACME._importKey(opts.jwk).then(function (abstractKey) {
-    var protected64 = BACME._jsto64(
-      { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: orderUrl, kid: opts.kid }
-    );
-    console.log('abstractKey:');
-    console.log(abstractKey);
-    return BACME._sign({
-      abstractKey: abstractKey
-    , payload64: payload64
-    , protected64: protected64
-    }).then(function (sig) {
-      if (!sig) {
-        throw new Error('sig is undefined... nonsense!');
-      }
-      console.log('newsig', sig);
-      return sig;
-    });
-  });
-};
-
-var currentOrderUrl;
-var authorizationUrls;
-var finalizeUrl;
-
-BACME.orders.create = function (opts) {
-  nonce = null;
-  return window.fetch(orderUrl, {
-    mode: 'cors'
-  , method: 'POST'
-  , headers: { 'Content-Type': 'application/jose+json' }
-  , body: JSON.stringify(opts.signedOrder)
-  }).then(function (resp) {
-    BACME._logHeaders(resp);
-    currentOrderUrl = resp.headers.get('location');
-    nonce = resp.headers.get('replay-nonce');
-    console.log('Next nonce:', nonce);
-
-    return resp.json().then(function (result) {
-      if (/^Error/i.test(result.detail)) {
-        return Promise.reject(new Error(result.detail));
-      }
-      authorizationUrls = result.authorizations;
-      finalizeUrl = result.finalize;
-      BACME._logBody(result);
-
-      result.url = currentOrderUrl;
-      return result;
-    });
-  });
-};
-
-BACME.challenges = {};
-BACME.challenges.all = function () {
-  var challenges = [];
-
-  function next() {
-    if (!authorizationUrls.length) {
-      return challenges;
-    }
-
-    return BACME.challenges.view().then(function (challenge) {
-      challenges.push(challenge);
-      return next();
-    });
-  }
-
-  return next();
-};
-BACME.challenges.view = function () {
-  var authzUrl = authorizationUrls.pop();
-  var token;
-  var challengeDomain;
-  var challengeUrl;
-
-  return window.fetch(authzUrl, {
-    mode: 'cors'
-  }).then(function (resp) {
-    BACME._logHeaders(resp);
-
-    return resp.json().then(function (result) {
-      // Note: select the challenge you wish to use
-      var challenge = result.challenges.slice(0).pop();
-      token = challenge.token;
-      challengeUrl = challenge.url;
-      challengeDomain = result.identifier.value;
-
-      BACME._logBody(result);
-
-      return {
-        challenges: result.challenges
-      , expires: result.expires
-      , identifier: result.identifier
-      , status: result.status
-      , wildcard: result.wildcard
-      //, token: challenge.token
-      //, url: challenge.url
-      //, domain: result.identifier.value,
-      };
-    });
-  });
-};
-
-var thumbprint;
-var keyAuth;
-var httpPath;
-var dnsAuth;
-var dnsRecord;
-
-BACME.thumbprint = function (opts) {
-  // https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
-
-  var accountJwk = opts.jwk;
-  var keys;
-
-  if (/^EC/i.test(opts.jwk.kty)) {
-    keys = [ 'crv', 'kty', 'x', 'y' ];
-  } else if (/^RS/i.test(opts.jwk.kty)) {
-    keys = [ 'e', 'kty', 'n' ];
-  }
-
-  var accountPublicStr = '{' + keys.map(function (key) {
-    return '"' + key + '":"' + accountJwk[key] + '"';
-  }).join(',') + '}';
-
-  return window.crypto.subtle.digest(
-    { name: "SHA-256" } // SHA-256 is spec'd, non-optional
-  , textEncoder.encode(accountPublicStr)
-  ).then(function (hash) {
-    thumbprint = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) {
-      return String.fromCharCode(ch);
-    }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
-
-    console.log('Thumbprint:');
-    console.log(opts);
-    console.log(accountPublicStr);
-    console.log(thumbprint);
-
-    return thumbprint;
-  });
-};
-
-// { token, thumbprint, challengeDomain }
-BACME.challenges['http-01'] = function (opts) {
-  // The contents of the key authorization file
-  keyAuth = opts.token + '.' + opts.thumbprint;
-
-  // Where the key authorization file goes
-  httpPath = 'http://' + opts.challengeDomain + '/.well-known/acme-challenge/' + opts.token;
-
-  console.log("echo '" + keyAuth + "' > '" + httpPath + "'");
-
-  return {
-    path: httpPath
-  , value: keyAuth
-  };
-};
-
-// { keyAuth }
-BACME.challenges['dns-01'] = function (opts) {
-  console.log('opts.keyAuth for DNS:');
-  console.log(opts.keyAuth);
-  return window.crypto.subtle.digest(
-    { name: "SHA-256", }
-  , textEncoder.encode(opts.keyAuth)
-  ).then(function (hash) {
-    dnsAuth = btoa(Array.prototype.map.call(new Uint8Array(hash), function (ch) {
-      return String.fromCharCode(ch);
-    }).join('')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
-
-    dnsRecord = '_acme-challenge.' + opts.challengeDomain;
-
-    console.log('DNS TXT Auth:');
-    // The name of the record
-    console.log(dnsRecord);
-    // The TXT record value
-    console.log(dnsAuth);
-
-    return {
-      type: 'TXT'
-    , host: dnsRecord
-    , answer: dnsAuth
-    };
-  });
-};
-
-var challengePollUrl;
-
-// { jwk, challengeUrl, accountId (kid) }
-BACME.challenges.accept = function (opts) {
-  var payload64 = BACME._jsto64({});
-
-  return BACME._importKey(opts.jwk).then(function (abstractKey) {
-    var protected64 = BACME._jsto64(
-      { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.challengeUrl, kid: opts.accountId }
-    );
-    return BACME._sign({
-      abstractKey: abstractKey
-    , payload64: payload64
-    , protected64: protected64
-    });
-  }).then(function (signedAccept) {
-
-    nonce = null;
-    return window.fetch(
-      opts.challengeUrl
-    , { mode: 'cors'
-      , method: 'POST'
-      , headers: { 'Content-Type': 'application/jose+json' }
-      , body: JSON.stringify(signedAccept)
-      }
-    ).then(function (resp) {
-      BACME._logHeaders(resp);
-      nonce = resp.headers.get('replay-nonce');
-      console.log("ACCEPT NONCE:", nonce);
-
-      return resp.json().then(function (reply) {
-        challengePollUrl = reply.url;
-
-        console.log('Challenge ACK:');
-        console.log(JSON.stringify(reply));
-        return reply;
-      });
-    });
-  });
-};
-
-BACME.challenges.check = function (opts) {
-  return window.fetch(opts.challengePollUrl, { mode: 'cors' }).then(function (resp) {
-    BACME._logHeaders(resp);
-
-    return resp.json().then(function (reply) {
-      if (/error/.test(reply.type)) {
-        return Promise.reject(new Error(reply.detail || reply.type));
-      }
-      challengePollUrl = reply.url;
-
-      BACME._logBody(reply);
-
-      return reply;
-    });
-  });
-};
-
-var domainKeypair;
-var domainJwk;
-
-BACME.generateKeypair = function (opts) {
-  var wcOpts = {};
-
-  // ECDSA has only the P curves and an associated bitlength
-  if (/^EC/i.test(opts.type)) {
-    wcOpts.name = 'ECDSA';
-    if (/256/.test(opts.bitlength)) {
-      wcOpts.namedCurve = 'P-256';
-    }
-  }
-
-  // RSA-PSS is another option, but I don't think it's used for Let's Encrypt
-  // I think the hash is only necessary for signing, not generation or import
-  if (/^RS/i.test(opts.type)) {
-    wcOpts.name = 'RSASSA-PKCS1-v1_5';
-    wcOpts.modulusLength = opts.bitlength;
-    if (opts.bitlength < 2048) {
-      wcOpts.modulusLength = opts.bitlength * 8;
-    }
-    wcOpts.publicExponent = new Uint8Array([0x01, 0x00, 0x01]);
-    wcOpts.hash = { name: "SHA-256" };
-  }
-  var extractable = true;
-  return window.crypto.subtle.generateKey(
-    wcOpts
-  , extractable
-  , [ 'sign', 'verify' ]
-  );
-};
-BACME.domains = {};
-// TODO factor out from BACME.accounts.generateKeypair even more
-BACME.domains.generateKeypair = function (opts) {
-  return BACME.generateKeypair(opts).then(function (result) {
-    domainKeypair = result;
-
-    return window.crypto.subtle.exportKey(
-      "jwk"
-    , result.privateKey
-    ).then(function (privJwk) {
-
-      domainJwk = privJwk;
-      console.log('private jwk:');
-      console.log(JSON.stringify(privJwk, null, 2));
-
-      return privJwk;
-    });
-  });
-};
-
-// { serverJwk, domains }
-BACME.orders.generateCsr = function (opts) {
-  return BACME._importKey(opts.serverJwk).then(function (abstractKey) {
-    return Promise.resolve(CSR.generate({ keypair: abstractKey.wcKeypair, domains: opts.domains }));
-  });
-};
-
-var certificateUrl;
-
-// { csr, jwk, finalizeUrl, accountId }
-BACME.orders.finalize = function (opts) {
-  var payload64 = BACME._jsto64(
-    { csr: opts.csr }
-  );
-
-  return BACME._importKey(opts.jwk).then(function (abstractKey) {
-    var protected64 = BACME._jsto64(
-      { nonce: nonce, alg: abstractKey.meta.alg/*'ES256'*/, url: opts.finalizeUrl, kid: opts.accountId }
-    );
-    return BACME._sign({
-      abstractKey: abstractKey
-    , payload64: payload64
-    , protected64: protected64
-    });
-  }).then(function (signedFinal) {
-
-    nonce = null;
-    return window.fetch(
-      opts.finalizeUrl
-    , { mode: 'cors'
-      , method: 'POST'
-      , headers: { 'Content-Type': 'application/jose+json' }
-      , body: JSON.stringify(signedFinal)
-      }
-    ).then(function (resp) {
-      BACME._logHeaders(resp);
-      nonce = resp.headers.get('replay-nonce');
-
-      return resp.json().then(function (reply) {
-        if (/error/.test(reply.type)) {
-          return Promise.reject(new Error(reply.detail || reply.type));
-        }
-        certificateUrl = reply.certificate;
-        BACME._logBody(reply);
-
-        return reply;
-      });
-    });
-  });
-};
-
-BACME.orders.receive = function (opts) {
-  return window.fetch(
-    opts.certificateUrl
-  , { mode: 'cors'
-    , method: 'GET'
-    }
-  ).then(function (resp) {
-    BACME._logHeaders(resp);
-    nonce = resp.headers.get('replay-nonce');
-
-    return resp.text().then(function (reply) {
-      BACME._logBody(reply);
-
-      return reply;
-    });
-  });
-};
-
-BACME.orders.check = function (opts) {
-  return window.fetch(
-    opts.orderUrl
-  , { mode: 'cors'
-    , method: 'GET'
-    }
-  ).then(function (resp) {
-    BACME._logHeaders(resp);
-
-    return resp.json().then(function (reply) {
-      if (/error/.test(reply.type)) {
-        return Promise.reject(new Error(reply.detail || reply.type));
-      }
-      BACME._logBody(reply);
-
-      return reply;
-    });
-  });
-};
-
-}(window));
diff --git a/app/js/greenlock.js b/app/js/greenlock.js
index dafc748..02442a8 100644
--- a/app/js/greenlock.js
+++ b/app/js/greenlock.js
@@ -20,6 +20,15 @@
   var steps = {};
   var i = 1;
   var apiUrl = 'https://acme-{{env}}.api.letsencrypt.org/directory';
+
+  // fix previous browsers
+  var isCurrent = (localStorage.getItem('version') === VERSION);
+  if (!isCurrent) {
+    localStorage.clear();
+    localStorage.setItem('version', VERSION);
+  }
+  localStorage.setItem('version', VERSION);
+
   var challenges = {
     'http-01': {
       set: function (auth) {
@@ -120,13 +129,6 @@
 		return Keypairs.generate(RSA_OPTS);
   }
   function testKeypairSupport() {
-		// fix previous browsers
-		var isCurrent = (localStorage.getItem('version') === VERSION);
-		if (!isCurrent) {
-			localStorage.clear();
-			localStorage.setItem('version', VERSION);
-		}
-		localStorage.setItem('version', VERSION);
 
     return testRsaSupport().then(function () {
       console.info("[crypto] RSA is supported");
@@ -221,7 +223,7 @@
     $qs('.js-acme-form-domains').hidden = false;
   };
   steps[1].submit = function () {
-    info.domains = $qs('.js-acme-domains').value.replace(/https?:\/\//g, ' ').replace(/,/g, ' ').trim().split(/\s+/g);
+    info.domains = $qs('.js-acme-domains').value.replace(/https?:\/\//g, ' ').replace(/[,+]/g, ' ').trim().split(/\s+/g);
     info.identifiers = info.domains.map(function (hostname) {
       return { type: 'dns', value: hostname.toLowerCase().trim() };
     }).slice(0,1); //Disable multiple values for now.  We'll just take the first and work with it.
diff --git a/index.html b/index.html
index 6874cc6..4bd744c 100644
--- a/index.html
+++ b/index.html
@@ -45,7 +45,7 @@
           Greenlock will process the CSR in the browser and request the certificates directly from letsencrypt.org.
           Please enable Javascript before continuing.
         
-