~evanj/cms

76738226cae5c57a518db8a20b0525d32223f741 — Evan M Jones 1 year, 4 days ago 299b960
Feat(email+upgrade): MVP support for user emails (after signup) and
upgrading subscription.
M TODO => TODO +3 -0
@@ 13,3 13,6 @@ No zero length varchar
Optional memcached
When editing existing references don't blow away prev inputs
Restrict API requests for free users (limit users<->org)
Forgot password
Upgrade
Cancel

M internal/c/doc/doc.go => internal/c/doc/doc.go +3 -1
@@ 6,6 6,7 @@ import (
	"net/http"

	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/m/tier"
	"git.sr.ht/~evanj/cms/internal/m/user"
	"git.sr.ht/~evanj/cms/internal/v"
)


@@ 48,7 49,8 @@ func (d *Doc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()

	d.HTML(w, r, page, map[string]interface{}{
		"User": user,
		"User":  user,
		"Tiers": tier.Tiers,
		// Only used for signup billing.
		"StripeCheckoutSessionID": q.Get("StripeCheckoutSessionID"),
		"StripePK":                q.Get("StripePK"),

M internal/c/user/user.go => internal/c/user/user.go +54 -0
@@ 32,6 32,7 @@ type dber interface {
	UserNew(username, password, verifyPassword string) (user.User, error)
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	UserSetEmail(u user.User, email string) (user.User, error)
	SpacesPerUser(user user.User, before int) (space.SpaceList, error)
}



@@ 135,6 136,53 @@ func (l *User) home(w http.ResponseWriter, r *http.Request) {
	})
}

func (l *User) updateEmail(w http.ResponseWriter, r *http.Request) {
	u, err := l.GetCookieUser(w, r)
	if err != nil {
		l.Error2(w, r, http.StatusInternalServerError, c.ErrNoLogin)
		return
	}

	if _, err := l.db.UserSetEmail(u, r.FormValue("email")); err != nil {
		l.Error2(w, r, http.StatusInternalServerError, err)
		return
	}

	l.Redirect(w, r, "/page/billing")
}

func (l *User) updateBilling(w http.ResponseWriter, r *http.Request) {
	u, err := l.GetCookieUser(w, r)
	if err != nil {
		l.Error2(w, r, http.StatusInternalServerError, c.ErrNoLogin)
		return
	}

	t, ok := tier.ByName(r.FormValue("tier"))
	if !ok || !(t.Is(tier.Business) || t.Is(tier.Enterprise)) {
		l.Error2(w, r, http.StatusBadRequest, ErrNoTier)
		return
	}

	if t.Is(tier.Free) {
		l.Redirect(w, r, "/")
		return
	}

	stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(u, t)
	if err != nil {
		l.Error2(w, r, http.StatusInternalServerError, err)
		return
	}

	form := url.Values{
		"StripeCheckoutSessionID": {stripeCheckoutSessionID},
		"StripePK":                {stripePK},
	}

	l.Redirect(w, r, "/page/stripe?"+form.Encode())
}

func (l *User) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/":


@@ 149,6 197,12 @@ func (l *User) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	case "/user/signup":
		l.signup(w, r)
		return
	case "/user/update/email":
		l.updateEmail(w, r)
		return
	case "/user/update/billing":
		l.updateBilling(w, r)
		return
	}

	http.NotFound(w, r)

M internal/m/user/user.go => internal/m/user/user.go +2 -0
@@ 7,4 7,6 @@ type User interface {
	Name() string
	Token() string
	Org() org.Org
	HasEmail() bool
	Email() string
}

M internal/s/db/migrations_embed.go => internal/s/db/migrations_embed.go +2 -0
@@ 24,6 24,8 @@ func init() {

	migrations["sql/00005.sql"] = tostring("Q1JFQVRFIFRBQkxFIGNtc19hY3Rpb24gKCAKCUlEIElOVEVHRVIgUFJJTUFSWSBLRVkgQVVUT19JTkNSRU1FTlQsCiAgQVQgVElNRVNUQU1QIE5PVCBOVUxMIERFRkFVTFQgQ1VSUkVOVF9USU1FU1RBTVAsCiAgT1JHX0lEIElOVEVHRVIgTk9UIE5VTEwsCiAgQ09OU1RSQUlOVCBDTVNfQUNUSU9OX1RPX09SR19GSyBGT1JFSUdOIEtFWShPUkdfSUQpIFJFRkVSRU5DRVMgY21zX29yZyhJRCkKKTsK")

	migrations["sql/00006.sql"] = tostring("Q1JFQVRFIFRBQkxFIGNtc19lbWFpbCAoIAoJSUQgSU5URUdFUiBQUklNQVJZIEtFWSBBVVRPX0lOQ1JFTUVOVCwKCUVNQUlMIHZhcmNoYXIoMjU2KSBVTklRVUUgTk9UIE5VTEwsCiAgVVNFUl9JRCBJTlRFR0VSIE5PVCBOVUxMLAogIENPTlNUUkFJTlQgQ01TX0VNQUlMX1RPX1VTRVJfRksgRk9SRUlHTiBLRVkoVVNFUl9JRCkgUkVGRVJFTkNFUyBjbXNfdXNlcihJRCkKKTsK")

}

func Get(name string) (string, bool) {

A internal/s/db/sql/00006.sql => internal/s/db/sql/00006.sql +6 -0
@@ 0,0 1,6 @@
CREATE TABLE cms_email ( 
	ID INTEGER PRIMARY KEY AUTO_INCREMENT,
	EMAIL varchar(256) UNIQUE NOT NULL,
  USER_ID INTEGER NOT NULL,
  CONSTRAINT CMS_EMAIL_TO_USER_FK FOREIGN KEY(USER_ID) REFERENCES cms_user(ID)
);

M internal/s/db/user.go => internal/s/db/user.go +34 -10
@@ 15,6 15,7 @@ type User struct {
	UserName  string
	userHash  string
	userOrgID string
	userEmail sql.NullString
	// Set on read.
	userToken string
	userOrg   org.Org


@@ 23,9 24,19 @@ type User struct {
// SQL QUERIES

var (
	queryCreateNewUser  = `INSERT INTO cms_user (NAME, HASH, ORG_ID) VALUES (?, ?, ?);`
	queryFindUserByID   = `SELECT ID, NAME, HASH, ORG_ID FROM cms_user WHERE ID = ?;`
	queryFindUserByName = `SELECT ID, NAME, HASH, ORG_ID FROM cms_user WHERE NAME = ?;`
	queryCreateNewUser = `INSERT INTO cms_user (NAME, HASH, ORG_ID) VALUES (?, ?, ?)`

	queryFindUserByID = `
		SELECT cms_user.ID, NAME, HASH, ORG_ID, EMAIL FROM cms_user 
		LEFT JOIN cms_email ON cms_email.USER_ID=cms_user.ID 
		WHERE cms_user.ID = ?
	`

	queryFindUserByName = `
		SELECT cms_user.ID, NAME, HASH, ORG_ID, EMAIL FROM cms_user 
		LEFT JOIN cms_email ON cms_email.USER_ID=cms_user.ID 
		WHERE NAME = ?
	`
)

func (db *DB) UserNew(username, password, verifyPassword string) (user.User, error) {


@@ 69,7 80,8 @@ func (db *DB) userNew(t *sql.Tx, username, password, verifyPassword string) (use
	}

	var user User
	if err := t.QueryRow(queryFindUserByID, id).Scan(&user.UserID, &user.UserName, &user.userHash, &user.userOrgID); err != nil {
	if err := t.QueryRow(queryFindUserByID, id).Scan(&user.UserID, &user.UserName, &user.userHash, &user.userOrgID, &user.userEmail); err != nil {
		fmt.Println(err)
		return nil, fmt.Errorf("failed to find user created")
	}



@@ 85,7 97,7 @@ func (db *DB) userNew(t *sql.Tx, username, password, verifyPassword string) (use

func (db *DB) UserGet(username, password string) (user.User, error) {
	var user User
	if err := db.QueryRow(queryFindUserByName, username).Scan(&user.UserID, &user.UserName, &user.userHash, &user.userOrgID); err != nil {
	if err := db.QueryRow(queryFindUserByName, username).Scan(&user.UserID, &user.UserName, &user.userHash, &user.userOrgID, &user.userEmail); err != nil {
		return nil, fmt.Errorf("failed to find user '%s'", username)
	}



@@ 121,7 133,9 @@ func (db *DB) UserGetFromToken(token string) (user.User, error) {
	}

	var user User
	if err := db.QueryRow(queryFindUserByID, id).Scan(&user.UserID, &user.UserName, &user.userHash, &user.userOrgID); err != nil {
	if err := db.QueryRow(queryFindUserByID, id).Scan(&user.UserID, &user.UserName, &user.userHash, &user.userOrgID, &user.userEmail); err != nil {

		fmt.Println(err)
		return nil, fmt.Errorf("failed to find user")
	}



@@ 141,7 155,17 @@ func (db *DB) UserGetFromToken(token string) (user.User, error) {
	return &user, nil
}

func (u *User) ID() string    { return u.UserID }
func (u *User) Name() string  { return u.UserName }
func (u *User) Token() string { return u.userToken }
func (u *User) Org() org.Org  { return u.userOrg }
func (db *DB) UserSetEmail(u user.User, email string) (user.User, error) {
	q := "INSERT INTO cms_email (EMAIL, USER_ID) VALUES (?, ?)"
	if _, err := db.Exec(q, email, u.ID()); err != nil {
		return nil, err
	}
	return db.UserGetFromToken(u.Token())
}

func (u *User) ID() string     { return u.UserID }
func (u *User) Name() string   { return u.UserName }
func (u *User) Token() string  { return u.userToken }
func (u *User) Org() org.Org   { return u.userOrg }
func (u *User) HasEmail() bool { return u.userEmail.Valid }
func (u *User) Email() string  { return u.userEmail.String }

M internal/v/html/_footer.html => internal/v/html/_footer.html +1 -0
@@ 38,6 38,7 @@
                <input type=submit class="text-decoration-none m-0 p-0 btn btn-link text-muted border-0" value=Logout />
              </form>
            </li>
            <li><a class='text-muted' href='/page/billing'>Billing</a></li>
          {{ else }}
            <li><a class='text-muted' href='/'>Home</a></li>
            <li><a class='text-muted' href='/#signup'>Signup</a></li>

M internal/v/html/_header.html => internal/v/html/_header.html +1 -0
@@ 43,6 43,7 @@
              <input type=submit class="btn btn-link nav-link border-0" value=Logout />
            </form>
          </li>
          <li class='nav-item'><a class='nav-link' href='/page/billing'>Billing</a></li>
        {{ else }}
          <li class='nav-item'><a class='nav-link' href='/#signup'>Signup</a></li>
          <li class='nav-item'><a class='nav-link' href='/#login'>Login</a></li>

M internal/v/html/billing.html => internal/v/html/billing.html +57 -5
@@ 11,13 11,65 @@
    <div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
      <h1 class="display-4">Billing</h1>
    </div>
    <div class='container'>
      <div class='row'>
        <div class="col-12 offset-0 col-lg-8 offset-lg-2">
          TODO
    {{if .User}}
      <div class='container'>
        <div class='row'>
          <div class="col-12 col-md-6">
            <div class='text-center'>
              {{if .User.HasEmail}}
              <p>Update your email.</p>
              {{else}}
              <p>Set your email in case you get locked out of your account.</p>
              {{end}}
            </div>
            <form action='/user/update/email' method=POST>
              <label for=email>Email</label>
              <input id=email name=email type=email class="mb-3 form-control" placeholder="email" required {{if .User.HasEmail}}value="{{.User.Email}}"{{end}}>
              <button type="submit" class="btn btn-primary">Go</button>
            </form>
          </div>
          <div class="col-12 col-md-6">
            {{if .User | paid}}
              <p class='text-center mb-5'>Cancel your subscription, {{.User.Org.Tier.Name}} tier.</p>
            {{else}}
              <p class='text-center mb-5'>Upgrade to a paid tier.<br>Get more access.</p>
              <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 mb-5 text-center">
                {{range .Tiers}}
                  {{if not (.|isFree)}}
                    <form action='/user/update/billing' method=POST class="col">
                      <input type=hidden name=tier value="{{.Name}}" />
                      <div class="card mb-4 shadow-sm">
                      <div class="card-header">
                        <h4 class="my-0 font-weight-normal">{{.Name}}</h4>
                      </div>
                      <div class="card-body">
                        <h1 class="card-title pricing-card-title">{{.Price}} <small class="text-muted">/ {{.TimeUnit}}</small></h1>
                        <ul class="list-unstyled mt-3 mb-4">
                          {{range .Opts}}
                            <li>{{.Text}}</li>
                          {{end}}
                        </ul>
                        <button type="submit" class="btn btn-primary w-100">Go</button>
                      </div>
                    </form>
                    </div>
                  {{end}}
                {{end}}
              </div>
            {{end}}
          </div>
        </div>
      </div>
    </div>
    {{else}}
      <div class='container'>
        <div class='row'>
          <div class="col-12">
            <h1>Oops</h1>
            <p>Sorry, our developers are lazy. This should really redirect you.</p>
          </div>
        </div>
      </div>
    {{end}}
    {{ template "html/_footer.html" $ }}
  </main>
  {{ template "html/_scripts.html" }}

M internal/v/tmpls_embed.go => internal/v/tmpls_embed.go +3 -3
@@ 20,15 20,15 @@ func init() {

	tmpls["css/mvp.css"] = tostring("OnJvb3QgewogICAgLS1ib3JkZXItcmFkaXVzOiA1cHg7CiAgICAtLWJveC1zaGFkb3c6IDJweCAycHggMTBweDsKICAgIC0tY29sb3I6ICMxMThiZWU7CiAgICAtLWNvbG9yLWFjY2VudDogIzExOGJlZTBiOwogICAgLS1jb2xvci1iZzogI2ZmZjsKICAgIC0tY29sb3ItYmctc2Vjb25kYXJ5OiAjZTllOWU5OwogICAgLS1jb2xvci1zZWNvbmRhcnk6ICM5MjBkZTk7CiAgICAtLWNvbG9yLXNlY29uZGFyeS1hY2NlbnQ6ICM5MjBkZTkwYjsKICAgIC0tY29sb3Itc2hhZG93OiAjZjRmNGY0OwogICAgLS1jb2xvci10ZXh0OiAjMDAwOwogICAgLS1jb2xvci10ZXh0LXNlY29uZGFyeTogIzk5OTsKICAgIC0taG92ZXItYnJpZ2h0bmVzczogMS4yOwogICAgLS1qdXN0aWZ5LWltcG9ydGFudDogY2VudGVyOwogICAgLS1qdXN0aWZ5LW5vcm1hbDogbGVmdDsKICAgIC0tbGluZS1oZWlnaHQ6IDE1MCU7CiAgICAtLXdpZHRoLWNhcmQ6IDI4NXB4OwogICAgLS13aWR0aC1jYXJkLW1lZGl1bTogNDYwcHg7CiAgICAtLXdpZHRoLWNhcmQtd2lkZTogODAwcHg7CiAgICAtLXdpZHRoLWNvbnRlbnQ6IDEwODBweDsKfQoKLyogTVZQLmNzcyB2MS4wIC0gYnkgQW5keSBCcmV3ZXIgKi8KCi8qIExheW91dCAqLwphcnRpY2xlIGFzaWRlIHsKICAgIGJhY2tncm91bmQ6IHZhcigtLWNvbG9yLXNlY29uZGFyeS1hY2NlbnQpOwogICAgYm9yZGVyLWxlZnQ6IDRweCBzb2xpZCB2YXIoLS1jb2xvci1zZWNvbmRhcnkpOwogICAgcGFkZGluZzogMC4wMXJlbSAwLjhyZW07Cn0KCmJvZHkgewogICAgYmFja2dyb3VuZDogdmFyKC0tY29sb3ItYmcpOwogICAgY29sb3I6IHZhcigtLWNvbG9yLXRleHQpOwogICAgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgIlNlZ29lIFVJIiwgUm9ib3RvLCBPeHlnZW4tU2FucywgVWJ1bnR1LCBDYW50YXJlbGwsICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7CiAgICBsaW5lLWhlaWdodDogdmFyKC0tbGluZS1oZWlnaHQpOwogICAgbWFyZ2luOiAwOwogICAgb3ZlcmZsb3cteDogaGlkZGVuOwogICAgcGFkZGluZzogMXJlbSAwOwp9Cgpmb290ZXIsCmhlYWRlciwKbWFpbiB7CiAgICBtYXJnaW46IDAgYXV0bzsKICAgIG1heC13aWR0aDogdmFyKC0td2lkdGgtY29udGVudCk7CiAgICBwYWRkaW5nOiAycmVtIDFyZW07Cn0KCmhyIHsKICAgIGJhY2tncm91bmQtY29sb3I6IHZhcigtLWNvbG9yLWJnLXNlY29uZGFyeSk7CiAgICBib3JkZXI6IG5vbmU7CiAgICBoZWlnaHQ6IDFweDsKICAgIG1hcmdpbjogNHJlbSAwOwp9CgpzZWN0aW9uIHsKICAgIGRpc3BsYXk6IGZsZXg7CiAgICBmbGV4LXdyYXA6IHdyYXA7CiAgICBqdXN0aWZ5LWNvbnRlbnQ6IHZhcigtLWp1c3RpZnktaW1wb3J0YW50KTsKfQoKc2VjdGlvbiBhc2lkZSB7CiAgICBib3JkZXI6IDFweCBzb2xpZCB2YXIoLS1jb2xvci1iZy1zZWNvbmRhcnkpOwogICAgYm9yZGVyLXJhZGl1czogdmFyKC0tYm9yZGVyLXJhZGl1cyk7CiAgICBib3gtc2hhZG93OiB2YXIoLS1ib3gtc2hhZG93KSB2YXIoLS1jb2xvci1zaGFkb3cpOwogICAgbWFyZ2luOiAxcmVtOwogICAgcGFkZGluZzogMS4yNXJlbTsKICAgIHdpZHRoOiB2YXIoLS13aWR0aC1jYXJkKTsKfQoKc2VjdGlvbiBhc2lkZTpob3ZlciB7CiAgICBib3gtc2hhZG93OiB2YXIoLS1ib3gtc2hhZG93KSB2YXIoLS1jb2xvci1iZy1zZWNvbmRhcnkpOwp9CgpzZWN0aW9uIGFzaWRlIGltZyB7CiAgICBtYXgtd2lkdGg6IDEwMCU7Cn0KCi8qIEhlYWRlcnMgKi8KYXJ0aWNsZSBoZWFkZXIsCmRpdiBoZWFkZXIsCm1haW4gaGVhZGVyIHsKICAgIHBhZGRpbmctdG9wOiAwOwp9CgpoZWFkZXIgewogICAgdGV4dC1hbGlnbjogdmFyKC0tanVzdGlmeS1pbXBvcnRhbnQpOwp9CgpoZWFkZXIgYSBiLApoZWFkZXIgYSBlbSwKaGVhZGVyIGEgaSwKaGVhZGVyIGEgc3Ryb25nIHsKICAgIG1hcmdpbi1sZWZ0OiAxcmVtOwogICAgbWFyZ2luLXJpZ2h0OiAxcmVtOwp9CgpoZWFkZXIgbmF2IGltZyB7CiAgICBtYXJnaW46IDFyZW0gMDsKfQoKc2VjdGlvbiBoZWFkZXIgewogICAgcGFkZGluZy10b3A6IDA7CiAgICB3aWR0aDogMTAwJTsKfQoKLyogTmF2ICovCm5hdiB7CiAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgZGlzcGxheTogZmxleDsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOwogICAgbWFyZ2luLWJvdHRvbTogN3JlbTsKfQoKbmF2IHVsIHsKICAgIGxpc3Qtc3R5bGU6IG5vbmU7CiAgICBwYWRkaW5nOiAwOwp9CgpuYXYgdWwgbGkgewogICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgbWFyZ2luOiAwIDAuNXJlbTsKfQoKLyogVHlwb2dyYXBoeSAqLwpjb2RlIHsKICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgIG1hcmdpbjogMCAwLjFyZW07CiAgICBwYWRkaW5nOiAwcmVtIDAuNXJlbTsKfQoKY29kZSwKc2FtcCB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1jb2xvci1hY2NlbnQpOwogICAgY29sb3I6IHZhcigtLWNvbG9yLXRleHQpOwogICAgYm9yZGVyLXJhZGl1czogdmFyKC0tYm9yZGVyLXJhZGl1cyk7CiAgICB0ZXh0LWFsaWduOiB2YXIoLS1qdXN0aWZ5LW5vcm1hbCk7Cn0KCmgxLApoMiwKaDMsCmg0LApoNSwKaDYgewogICAgbGluZS1oZWlnaHQ6IHZhcigtLWxpbmUtaGVpZ2h0KTsKfQoKbWFyayB7CiAgICBwYWRkaW5nOiAwLjFyZW07Cn0KCm9sIGxpLAp1bCBsaSB7CiAgICBwYWRkaW5nOiAwLjJyZW0gMDsKfQoKcCB7CiAgICBtYXJnaW46IDAuNzVyZW0gMDsKICAgIHBhZGRpbmc6IDA7Cn0KCnNhbXAgewogICAgZGlzcGxheTogYmxvY2s7CiAgICBtYXJnaW46IDFyZW0gMDsKICAgIG1heC13aWR0aDogdmFyKC0td2lkdGgtY2FyZC13aWRlKTsKICAgIHBhZGRpbmc6IDFyZW07Cn0KCnNtYWxsIHsKICAgIGNvbG9yOiB2YXIoLS1jb2xvci10ZXh0LXNlY29uZGFyeSk7Cn0KCnN1cCB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1jb2xvci1zZWNvbmRhcnkpOwogICAgYm9yZGVyLXJhZGl1czogdmFyKC0tYm9yZGVyLXJhZGl1cyk7CiAgICBjb2xvcjogdmFyKC0tY29sb3ItYmcpOwogICAgZm9udC1zaXplOiB4eC1zbWFsbDsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgbWFyZ2luOiAwLjJyZW07CiAgICBwYWRkaW5nOiAwLjJyZW0gMC4zcmVtOwogICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgdG9wOiAtMnB4Owp9CgovKiBMaW5rcyAqLwphIHsKICAgIGNvbG9yOiB2YXIoLS1jb2xvci1zZWNvbmRhcnkpOwogICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7Cn0KCmE6aG92ZXIgewogICAgZmlsdGVyOiBicmlnaHRuZXNzKHZhcigtLWhvdmVyLWJyaWdodG5lc3MpKTsKICAgIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOwp9CgphIGIsCmEgZW0sCmEgaSwKYSBzdHJvbmcsCmJ1dHRvbiB7CiAgICBib3JkZXItcmFkaXVzOiB2YXIoLS1ib3JkZXItcmFkaXVzKTsKICAgIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICAgIGZvbnQtc2l6ZTogbWVkaXVtOwogICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICBtYXJnaW46IDEuNXJlbSAwIDAuNXJlbSAwOwogICAgcGFkZGluZzogMXJlbSAycmVtOwp9CgppbnB1dFt0eXBlPXN1Ym1pdF06aG92ZXIsCmJ1dHRvbjpob3ZlciB7CiAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICBmaWx0ZXI6IGJyaWdodG5lc3ModmFyKC0taG92ZXItYnJpZ2h0bmVzcykpOwp9CgphIGIsCmEgc3Ryb25nLAppbnB1dFt0eXBlPXN1Ym1pdF0sCmJ1dHRvbiB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1jb2xvcik7CiAgICBib3JkZXI6IDJweCBzb2xpZCB2YXIoLS1jb2xvcik7CiAgICBjb2xvcjogdmFyKC0tY29sb3ItYmcpOwp9CgphIGVtLAphIGkgewogICAgYm9yZGVyOiAycHggc29saWQgdmFyKC0tY29sb3IpOwogICAgYm9yZGVyLXJhZGl1czogdmFyKC0tYm9yZGVyLXJhZGl1cyk7CiAgICBjb2xvcjogdmFyKC0tY29sb3IpOwogICAgZGlzcGxheTogaW5saW5lLWJsb2NrOwogICAgcGFkZGluZzogMXJlbSAycmVtOwp9CgovKiBJbWFnZXMgKi8KZmlndXJlIHsKICAgIG1hcmdpbjogMDsKICAgIHBhZGRpbmc6IDA7Cn0KCmZpZ3VyZSBmaWdjYXB0aW9uIHsKICAgIGNvbG9yOiB2YXIoLS1jb2xvci10ZXh0LXNlY29uZGFyeSk7Cn0KCi8qIEZvcm1zICovCmZvcm0gewogICAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tY29sb3ItYmctc2Vjb25kYXJ5KTsKICAgIGJvcmRlci1yYWRpdXM6IHZhcigtLWJvcmRlci1yYWRpdXMpOwogICAgYm94LXNoYWRvdzogdmFyKC0tYm94LXNoYWRvdykgdmFyKC0tY29sb3Itc2hhZG93KTsKICAgIGRpc3BsYXk6IGJsb2NrOwogICAgbWF4LXdpZHRoOiB2YXIoLS13aWR0aC1jYXJkLXdpZGUpOwogICAgbWluLXdpZHRoOiB2YXIoLS13aWR0aC1jYXJkKTsKICAgIHBhZGRpbmc6IDEuNXJlbTsKICAgIHRleHQtYWxpZ246IHZhcigtLWp1c3RpZnktbm9ybWFsKTsKfQoKZm9ybSBoZWFkZXIgewogICAgbWFyZ2luOiAxLjVyZW0gMDsKICAgIHBhZGRpbmc6IDEuNXJlbSAwOwp9CgppbnB1dCwKbGFiZWwsCnNlbGVjdCwKdGV4dGFyZWEgewogICAgZGlzcGxheTogYmxvY2s7CiAgICBmb250LXNpemU6IGluaGVyaXQ7CiAgICBtYXgtd2lkdGg6IHZhcigtLXdpZHRoLWNhcmQtd2lkZSk7Cn0KCmlucHV0LApzZWxlY3QsCnRleHRhcmVhIHsKICAgIG1hcmdpbi1ib3R0b206IDFyZW07Cn0KCmlucHV0LApzZWxlY3QsCnRleHRhcmVhIHsKICAgIGJvcmRlcjogMXB4IHNvbGlkIHZhcigtLWNvbG9yLWJnLXNlY29uZGFyeSk7CiAgICBib3JkZXItcmFkaXVzOiB2YXIoLS1ib3JkZXItcmFkaXVzKTsKICAgIHBhZGRpbmc6IDAuNHJlbSAwLjhyZW07Cn0KCmxhYmVsIHsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgbWFyZ2luLWJvdHRvbTogMC4ycmVtOwp9CgovKiBUYWJsZXMgKi8KdGFibGUgewogICAgYm9yZGVyOiAxcHggc29saWQgdmFyKC0tY29sb3ItYmctc2Vjb25kYXJ5KTsKICAgIGJvcmRlci1yYWRpdXM6IHZhcigtLWJvcmRlci1yYWRpdXMpOwogICAgYm9yZGVyLXNwYWNpbmc6IDA7CiAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICBvdmVyZmxvdzogaGlkZGVuOwogICAgcGFkZGluZzogMDsKfQoKdGFibGUgdGQsCnRhYmxlIHRoLAp0YWJsZSB0ciB7CiAgICBwYWRkaW5nOiAwLjRyZW0gMC44cmVtOwogICAgdGV4dC1hbGlnbjogdmFyKC0tanVzdGlmeS1pbXBvcnRhbnQpOwp9Cgp0YWJsZSB0aGVhZCB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiB2YXIoLS1jb2xvcik7CiAgICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogICAgYm9yZGVyLXJhZGl1czogdmFyKC0tYm9yZGVyLXJhZGl1cyk7CiAgICBjb2xvcjogdmFyKC0tY29sb3ItYmcpOwogICAgbWFyZ2luOiAwOwogICAgcGFkZGluZzogMDsKfQoKdGFibGUgdGhlYWQgdGg6Zmlyc3QtY2hpbGQgewogICAgYm9yZGVyLXRvcC1sZWZ0LXJhZGl1czogdmFyKC0tYm9yZGVyLXJhZGl1cyk7Cn0KCnRhYmxlIHRoZWFkIHRoOmxhc3QtY2hpbGQgewogICAgYm9yZGVyLXRvcC1yaWdodC1yYWRpdXM6IHZhcigtLWJvcmRlci1yYWRpdXMpOwp9Cgp0YWJsZSB0aGVhZCB0aDpmaXJzdC1jaGlsZCwKdGFibGUgdHIgdGQ6Zmlyc3QtY2hpbGQgewogICAgdGV4dC1hbGlnbjogdmFyKC0tanVzdGlmeS1ub3JtYWwpOwp9CgovKiBRdW90ZXMgKi8KYmxvY2txdW90ZSB7CiAgICBkaXNwbGF5OiBibG9jazsKICAgIGZvbnQtc2l6ZTogeC1sYXJnZTsKICAgIGxpbmUtaGVpZ2h0OiB2YXIoLS1saW5lLWhlaWdodCk7CiAgICBtYXJnaW46IDFyZW0gYXV0bzsKICAgIG1heC13aWR0aDogdmFyKC0td2lkdGgtY2FyZC1tZWRpdW0pOwogICAgcGFkZGluZzogMS41cmVtIDFyZW07CiAgICB0ZXh0LWFsaWduOiB2YXIoLS1qdXN0aWZ5LWltcG9ydGFudCk7Cn0KCmJsb2NrcXVvdGUgZm9vdGVyIHsKICAgIGNvbG9yOiB2YXIoLS1jb2xvci10ZXh0LXNlY29uZGFyeSk7CiAgICBkaXNwbGF5OiBibG9jazsKICAgIGZvbnQtc2l6ZTogc21hbGw7CiAgICBsaW5lLWhlaWdodDogdmFyKC0tbGluZS1oZWlnaHQpOwogICAgcGFkZGluZzogMS41cmVtIDA7Cn0KCi8qIEN1c3RvbSBzdHlsZXMgKi8K")

	tmpls["html/_footer.html"] = tostring("PGRpdiBjbGFzcz1jb250YWluZXI+CiAgPGZvb3RlciBjbGFzcz0icHQtNCBteS1tZC01IHB0LW1kLTUgYm9yZGVyLXRvcCI+CiAgICA8ZGl2IGNsYXNzPSJyb3ciPgogICAgICA8ZGl2IGNsYXNzPSJjb2wtNiBjb2wtbWQtMyBvZmZzZXQtbWQtMyB0ZXh0LW1kLXJpZ2h0Ij4KICAgICAgICA8aDU+TmF2aWdhdGlvbjwvaDU+CiAgICAgICAgPHVsIGNsYXNzPSJsaXN0LXVuc3R5bGVkIHRleHQtc21hbGwiPgogICAgICAgICAge3sgaWYgLlNwYWNlIH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy8nPkhvbWU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgLkNvbnRlbnRUeXBlIH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAgICB7eyBlbmQgfX0KICAgICAgICAgIHt7IGlmIC5Ib29rIH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAgICB7eyBlbmQgfX0KICAgICAgICAgIHt7IGlmIC5Db250ZW50IH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9jb250ZW50dHlwZS97eyAuU3BhY2UuSUR9fS97eyAuQ29udGVudFR5cGUuSUQgfX0nPnt7IC5Db250ZW50VHlwZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAgICB7eyBlbmQgfX0KICAgICAgICAgIHt7IGlmIGFuZCAuU3BhY2UgKG5vdCAuQ29udGVudFR5cGUpIChub3QgLkhvb2spIH19CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjY29weU1vZGFsIiBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nIyc+Q29weTwvYT48L2xpPgogICAgICAgICAgICA8bGk+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI3VwZGF0ZU1vZGFsIiBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nIyc+VXBkYXRlPC9hPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgYW5kIC5Db250ZW50VHlwZSAobm90IC5Db250ZW50KSB9fQogICAgICAgICAgICA8bGk+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI3VwZGF0ZU1vZGFsIiBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nIyc+VXBkYXRlPC9hPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgLkNvbnRlbnQgfX0KICAgICAgICAgICAgPGxpPjxpbnB1dCB0eXBlPXN1Ym1pdCBjbGFzcz0idGV4dC1kZWNvcmF0aW9uLW5vbmUgbS0wIHAtMCBidG4gYnRuLWxpbmsgdGV4dC1tdXRlZCBib3JkZXItMCIgdmFsdWU9U2F2ZSAvPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgLkhvb2sgfX0KICAgICAgICAgICAgPGxpPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiNkZWxldGVNb2RhbCIgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9JyMnPkRlbGV0ZTwvYT48L2xpPgogICAgICAgICAge3sgZW5kIH19CiAgICAgICAgICB7eyBpZiAuVXNlciB9fQogICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgPGZvcm0gbWV0aG9kPVBPU1QgYWN0aW9uPScvdXNlci9sb2dvdXQnIGVuY3R5cGU9J211bHRpcGFydC9mb3JtLWRhdGEnPgogICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9c3VibWl0IGNsYXNzPSJ0ZXh0LWRlY29yYXRpb24tbm9uZSBtLTAgcC0wIGJ0biBidG4tbGluayB0ZXh0LW11dGVkIGJvcmRlci0wIiB2YWx1ZT1Mb2dvdXQgLz4KICAgICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICB7eyBlbHNlIH19CiAgICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nLyc+SG9tZTwvYT48L2xpPgogICAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy8jc2lnbnVwJz5TaWdudXA8L2E+PC9saT4KICAgICAgICAgICAgPGxpPjxhIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScvI2xvZ2luJz5Mb2dpbjwvYT48L2xpPgogICAgICAgICAge3sgZW5kfX0KICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nLy9naXQuc3IuaHQvfmV2YW5qL2Ntcyc+U291cmNlPC9hPjwvbGk+CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy8vZ2l0LnNyLmh0L35ldmFuai9jbXMvdHJlZS9tYXN0ZXIvTElDRU5TRSc+TGljZW5zZTwvYT48L2xpPgogICAgICAgIDwvdWw+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjb2wtNiBjb2wtbWQtMyI+CiAgICAgICAgPGg1PlJlc291cmNlczwvaDU+CiAgICAgICAgPHVsIGNsYXNzPSJsaXN0LXVuc3R5bGVkIHRleHQtc21hbGwiPgogICAgICAgICAge3tpZiAuVXNlcn19CiAgICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nL3BhZ2UvYmlsbGluZyc+QmlsbGluZzwvYT48L2xpPgogICAgICAgICAge3tlbHNlfX0KICAgICAgICAgICAgPGxpPjxhIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScvI3ByaWNpbmcnPlByaWNpbmc8L2E+PC9saT4KICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nL3BhZ2UvZG9jJz5Eb2NzPC9hPjwvbGk+CiAgICAgICAgICA8bGk+PGEgY2xhc3M9InRleHQtbXV0ZWQiIGhyZWY9Ii9wYWdlL2ZhcSI+RkFRPC9hPjwvbGk+CiAgICAgICAgICA8bGk+PGEgY2xhc3M9InRleHQtbXV0ZWQiIGhyZWY9Ii9wYWdlL3Rlcm1zIj5UZXJtczwvYT48L2xpPgogICAgICAgICAgPGxpPjxhIGNsYXNzPSJ0ZXh0LW11dGVkIiBocmVmPSIvcGFnZS9wcml2YWN5Ij5Qcml2YWN5PC9hPjwvbGk+CiAgICAgICAgICA8bGk+PGEgY2xhc3M9InRleHQtbXV0ZWQiIGhyZWY9Ii9wYWdlL2NvbnRhY3QiPkNvbnRhY3Q8L2E+PC9saT4KICAgICAgICA8L3VsPgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz1yb3c+CiAgICAgIDxwIGNsYXNzPSd0ZXh0LW11dGVkIHRleHQtY2VudGVyIG10LTUnPnYue3suQnVpbGR9fTwvcD4KICAgIDwvZGl2PgogIDwvZm9vdGVyPgo8L2Rpdj4Ke3tpZiAuQX19CjxpbWcgc3R5bGU9J3Bvc2l0aW9uOiBmaXhlZDsgYm90dG9tOiAwOyByaWdodDogMDsnIHNyYz0iLy9za2lwcGVyY21zLmdvYXRjb3VudGVyLmNvbS9jb3VudD9wPXt7LkEuUGF0aH19e3tpZiAuQS5SZWZlcnJlcn19JnI9e3suQS5SZWZlcnJlcn19e3tlbmR9fSZybmQ9e3suQS5STkR9fSI+Cnt7ZW5kfX0K")
	tmpls["html/_footer.html"] = tostring("PGRpdiBjbGFzcz1jb250YWluZXI+CiAgPGZvb3RlciBjbGFzcz0icHQtNCBteS1tZC01IHB0LW1kLTUgYm9yZGVyLXRvcCI+CiAgICA8ZGl2IGNsYXNzPSJyb3ciPgogICAgICA8ZGl2IGNsYXNzPSJjb2wtNiBjb2wtbWQtMyBvZmZzZXQtbWQtMyB0ZXh0LW1kLXJpZ2h0Ij4KICAgICAgICA8aDU+TmF2aWdhdGlvbjwvaDU+CiAgICAgICAgPHVsIGNsYXNzPSJsaXN0LXVuc3R5bGVkIHRleHQtc21hbGwiPgogICAgICAgICAge3sgaWYgLlNwYWNlIH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy8nPkhvbWU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgLkNvbnRlbnRUeXBlIH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAgICB7eyBlbmQgfX0KICAgICAgICAgIHt7IGlmIC5Ib29rIH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAgICB7eyBlbmQgfX0KICAgICAgICAgIHt7IGlmIC5Db250ZW50IH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9jb250ZW50dHlwZS97eyAuU3BhY2UuSUR9fS97eyAuQ29udGVudFR5cGUuSUQgfX0nPnt7IC5Db250ZW50VHlwZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAgICB7eyBlbmQgfX0KICAgICAgICAgIHt7IGlmIGFuZCAuU3BhY2UgKG5vdCAuQ29udGVudFR5cGUpIChub3QgLkhvb2spIH19CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjY29weU1vZGFsIiBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nIyc+Q29weTwvYT48L2xpPgogICAgICAgICAgICA8bGk+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI3VwZGF0ZU1vZGFsIiBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nIyc+VXBkYXRlPC9hPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgYW5kIC5Db250ZW50VHlwZSAobm90IC5Db250ZW50KSB9fQogICAgICAgICAgICA8bGk+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI3VwZGF0ZU1vZGFsIiBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nIyc+VXBkYXRlPC9hPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgLkNvbnRlbnQgfX0KICAgICAgICAgICAgPGxpPjxpbnB1dCB0eXBlPXN1Ym1pdCBjbGFzcz0idGV4dC1kZWNvcmF0aW9uLW5vbmUgbS0wIHAtMCBidG4gYnRuLWxpbmsgdGV4dC1tdXRlZCBib3JkZXItMCIgdmFsdWU9U2F2ZSAvPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICAgIHt7IGVuZCB9fQogICAgICAgICAge3sgaWYgLkhvb2sgfX0KICAgICAgICAgICAgPGxpPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiNkZWxldGVNb2RhbCIgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9JyMnPkRlbGV0ZTwvYT48L2xpPgogICAgICAgICAge3sgZW5kIH19CiAgICAgICAgICB7eyBpZiAuVXNlciB9fQogICAgICAgICAgICA8bGk+CiAgICAgICAgICAgICAgPGZvcm0gbWV0aG9kPVBPU1QgYWN0aW9uPScvdXNlci9sb2dvdXQnIGVuY3R5cGU9J211bHRpcGFydC9mb3JtLWRhdGEnPgogICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9c3VibWl0IGNsYXNzPSJ0ZXh0LWRlY29yYXRpb24tbm9uZSBtLTAgcC0wIGJ0biBidG4tbGluayB0ZXh0LW11dGVkIGJvcmRlci0wIiB2YWx1ZT1Mb2dvdXQgLz4KICAgICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nL3BhZ2UvYmlsbGluZyc+QmlsbGluZzwvYT48L2xpPgogICAgICAgICAge3sgZWxzZSB9fQogICAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy8nPkhvbWU8L2E+PC9saT4KICAgICAgICAgICAgPGxpPjxhIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScvI3NpZ251cCc+U2lnbnVwPC9hPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nLyNsb2dpbic+TG9naW48L2E+PC9saT4KICAgICAgICAgIHt7IGVuZH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy8vZ2l0LnNyLmh0L35ldmFuai9jbXMnPlNvdXJjZTwvYT48L2xpPgogICAgICAgICAgPGxpPjxhIGNsYXNzPSd0ZXh0LW11dGVkJyBocmVmPScvL2dpdC5zci5odC9+ZXZhbmovY21zL3RyZWUvbWFzdGVyL0xJQ0VOU0UnPkxpY2Vuc2U8L2E+PC9saT4KICAgICAgICA8L3VsPgogICAgICA8L2Rpdj4KICAgICAgPGRpdiBjbGFzcz0iY29sLTYgY29sLW1kLTMiPgogICAgICAgIDxoNT5SZXNvdXJjZXM8L2g1PgogICAgICAgIDx1bCBjbGFzcz0ibGlzdC11bnN0eWxlZCB0ZXh0LXNtYWxsIj4KICAgICAgICAgIHt7aWYgLlVzZXJ9fQogICAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9wYWdlL2JpbGxpbmcnPkJpbGxpbmc8L2E+PC9saT4KICAgICAgICAgIHt7ZWxzZX19CiAgICAgICAgICAgIDxsaT48YSBjbGFzcz0ndGV4dC1tdXRlZCcgaHJlZj0nLyNwcmljaW5nJz5QcmljaW5nPC9hPjwvbGk+CiAgICAgICAgICB7e2VuZH19CiAgICAgICAgICA8bGk+PGEgY2xhc3M9J3RleHQtbXV0ZWQnIGhyZWY9Jy9wYWdlL2RvYyc+RG9jczwvYT48L2xpPgogICAgICAgICAgPGxpPjxhIGNsYXNzPSJ0ZXh0LW11dGVkIiBocmVmPSIvcGFnZS9mYXEiPkZBUTwvYT48L2xpPgogICAgICAgICAgPGxpPjxhIGNsYXNzPSJ0ZXh0LW11dGVkIiBocmVmPSIvcGFnZS90ZXJtcyI+VGVybXM8L2E+PC9saT4KICAgICAgICAgIDxsaT48YSBjbGFzcz0idGV4dC1tdXRlZCIgaHJlZj0iL3BhZ2UvcHJpdmFjeSI+UHJpdmFjeTwvYT48L2xpPgogICAgICAgICAgPGxpPjxhIGNsYXNzPSJ0ZXh0LW11dGVkIiBocmVmPSIvcGFnZS9jb250YWN0Ij5Db250YWN0PC9hPjwvbGk+CiAgICAgICAgPC91bD4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9cm93PgogICAgICA8cCBjbGFzcz0ndGV4dC1tdXRlZCB0ZXh0LWNlbnRlciBtdC01Jz52Lnt7LkJ1aWxkfX08L3A+CiAgICA8L2Rpdj4KICA8L2Zvb3Rlcj4KPC9kaXY+Cnt7aWYgLkF9fQo8aW1nIHN0eWxlPSdwb3NpdGlvbjogZml4ZWQ7IGJvdHRvbTogMDsgcmlnaHQ6IDA7JyBzcmM9Ii8vc2tpcHBlcmNtcy5nb2F0Y291bnRlci5jb20vY291bnQ/cD17ey5BLlBhdGh9fXt7aWYgLkEuUmVmZXJyZXJ9fSZyPXt7LkEuUmVmZXJyZXJ9fXt7ZW5kfX0mcm5kPXt7LkEuUk5EfX0iPgp7e2VuZH19Cg==")

	tmpls["html/_head.html"] = tostring("PG1ldGEgY2hhcnNldD0ndXRmLTgnPgo8bWV0YSBuYW1lPSd2aWV3cG9ydCcgY29udGVudD0nd2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEnPgo8bGluayByZWw9J2ljb24nIHR5cGU9J2ltYWdlL3gtaWNvbicgaHJlZj0naHR0cHM6Ly9mYXZpY29uLmV2YW5qb24uZXMvMC8xMDUvMjE3LzMyL2Zhdmljb24uaWNvJyAvPgo8bGluayByZWw9J3N0eWxlc2hlZXQnIGhyZWY9Jy9zdGF0aWMvY3NzL2Jvb3RzdHJhcC5taW4uY3NzJyAvPgo=")

	tmpls["html/_header.html"] = tostring("PGhlYWRlciBjbGFzcz0nYmctcHJpbWFyeSc+CiAgPG5hdiBjbGFzcz0nY29udGFpbmVyIG5hdmJhciBuYXZiYXItZXhwYW5kLWxnIG5hdmJhci1kYXJrJz4KICAgIDxhIGNsYXNzPSduYXZiYXItYnJhbmQnIGhyZWY9Jy8nPgogICAgICA8aW1nIHdpZHRoPTUwIGhlaWdodD01MCBzcmM9Jy9zdGF0aWMvaW1nL2xvZ28td2hpdGUuc3ZnJz4KICAgICAgPHNwYW4gY2xhc3M9J2Qtbm9uZSBkLWxnLWlubGluZSc+U2tpcHBlciBDTVM8L3NwYW4+CiAgICA8L2E+CiAgICA8YnV0dG9uIGNsYXNzPSduYXZiYXItdG9nZ2xlcicgdHlwZT0nYnV0dG9uJyBkYXRhLXRvZ2dsZT0nY29sbGFwc2UnIGRhdGEtdGFyZ2V0PScjbmF2YmFyU3VwcG9ydGVkQ29udGVudCcgYXJpYS1jb250cm9scz0nbmF2YmFyU3VwcG9ydGVkQ29udGVudCcgYXJpYS1leHBhbmRlZD0nZmFsc2UnIGFyaWEtbGFiZWw9J1RvZ2dsZSBuYXZpZ2F0aW9uJz4KICAgICAgPHNwYW4gY2xhc3M9J25hdmJhci10b2dnbGVyLWljb24nPjwvc3Bhbj4KICAgIDwvYnV0dG9uPgogICAgPGRpdiBjbGFzcz0nY29sbGFwc2UgbmF2YmFyLWNvbGxhcHNlJyBpZD0nbmF2YmFyU3VwcG9ydGVkQ29udGVudCc+CiAgICAgIDx1bCBjbGFzcz0nbmF2YmFyLW5hdiBtbC1hdXRvJz4KICAgICAgICB7eyBpZiAuU3BhY2UgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy8nPkhvbWU8L2E+PC9saT4KICAgICAgICB7eyBlbmQgfX0KICAgICAgICB7eyBpZiAuQ29udGVudFR5cGUgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgLkhvb2sgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgLkNvbnRlbnQgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy9jb250ZW50dHlwZS97eyAuU3BhY2UuSUR9fS97eyAuQ29udGVudFR5cGUuSUQgfX0nPnt7IC5Db250ZW50VHlwZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgYW5kIC5TcGFjZSAobm90IC5Db250ZW50VHlwZSkgKG5vdCAuSG9vaykgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiNjb3B5TW9kYWwiIGNsYXNzPSduYXYtbGluaycgaHJlZj0nIyc+Q29weTwvYT48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI3VwZGF0ZU1vZGFsIiBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9JyMnPlVwZGF0ZTwvYT48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI2RlbGV0ZU1vZGFsIiBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9JyMnPkRlbGV0ZTwvYT48L2xpPgogICAgICAgIHt7IGVuZCB9fQogICAgICAgIHt7IGlmIGFuZCAuQ29udGVudFR5cGUgKG5vdCAuQ29udGVudCkgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiN1cGRhdGVNb2RhbCIgY2xhc3M9J25hdi1saW5rJyBocmVmPScjJz5VcGRhdGU8L2E+PC9saT4KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiNkZWxldGVNb2RhbCIgY2xhc3M9J25hdi1saW5rJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICB7eyBlbmQgfX0KICAgICAgICB7eyBpZiAuQ29udGVudCB9fQogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGlucHV0IHR5cGU9c3VibWl0IGNsYXNzPSJidG4gYnRuLWxpbmsgbmF2LWxpbmsgYm9yZGVyLTAiIHZhbHVlPVNhdmUgLz48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI2RlbGV0ZU1vZGFsIiBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9JyMnPkRlbGV0ZTwvYT48L2xpPgogICAgICAgIHt7IGVuZCB9fQogICAgICAgIHt7IGlmIC5Ib29rIH19CiAgICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSduYXYtbGluaycgaHJlZj0nIyc+RGVsZXRlPC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgLlVzZXIgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPgogICAgICAgICAgICA8Zm9ybSBtZXRob2Q9UE9TVCBhY3Rpb249Jy91c2VyL2xvZ291dCcgZW5jdHlwZT0nbXVsdGlwYXJ0L2Zvcm0tZGF0YSc+CiAgICAgICAgICAgICAgPGlucHV0IHR5cGU9c3VibWl0IGNsYXNzPSJidG4gYnRuLWxpbmsgbmF2LWxpbmsgYm9yZGVyLTAiIHZhbHVlPUxvZ291dCAvPgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICA8L2xpPgogICAgICAgIHt7IGVsc2UgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGNsYXNzPSduYXYtbGluaycgaHJlZj0nLyNzaWdudXAnPlNpZ251cDwvYT48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgY2xhc3M9J25hdi1saW5rJyBocmVmPScvI2xvZ2luJz5Mb2dpbjwvYT48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgY2xhc3M9J25hdi1saW5rJyBocmVmPScvI3ByaWNpbmcnPlByaWNpbmc8L2E+PC9saT4KICAgICAgICB7eyBlbmR9fQogICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGNsYXNzPSduYXYtbGluaycgaHJlZj0nL3BhZ2UvZG9jJz5Eb2NzPC9hPjwvbGk+CiAgICAgIDwvdWw+CiAgICA8L2Rpdj4KICA8L25hdj4KPC9oZWFkZXI+Cg==")
	tmpls["html/_header.html"] = tostring("PGhlYWRlciBjbGFzcz0nYmctcHJpbWFyeSc+CiAgPG5hdiBjbGFzcz0nY29udGFpbmVyIG5hdmJhciBuYXZiYXItZXhwYW5kLWxnIG5hdmJhci1kYXJrJz4KICAgIDxhIGNsYXNzPSduYXZiYXItYnJhbmQnIGhyZWY9Jy8nPgogICAgICA8aW1nIHdpZHRoPTUwIGhlaWdodD01MCBzcmM9Jy9zdGF0aWMvaW1nL2xvZ28td2hpdGUuc3ZnJz4KICAgICAgPHNwYW4gY2xhc3M9J2Qtbm9uZSBkLWxnLWlubGluZSc+U2tpcHBlciBDTVM8L3NwYW4+CiAgICA8L2E+CiAgICA8YnV0dG9uIGNsYXNzPSduYXZiYXItdG9nZ2xlcicgdHlwZT0nYnV0dG9uJyBkYXRhLXRvZ2dsZT0nY29sbGFwc2UnIGRhdGEtdGFyZ2V0PScjbmF2YmFyU3VwcG9ydGVkQ29udGVudCcgYXJpYS1jb250cm9scz0nbmF2YmFyU3VwcG9ydGVkQ29udGVudCcgYXJpYS1leHBhbmRlZD0nZmFsc2UnIGFyaWEtbGFiZWw9J1RvZ2dsZSBuYXZpZ2F0aW9uJz4KICAgICAgPHNwYW4gY2xhc3M9J25hdmJhci10b2dnbGVyLWljb24nPjwvc3Bhbj4KICAgIDwvYnV0dG9uPgogICAgPGRpdiBjbGFzcz0nY29sbGFwc2UgbmF2YmFyLWNvbGxhcHNlJyBpZD0nbmF2YmFyU3VwcG9ydGVkQ29udGVudCc+CiAgICAgIDx1bCBjbGFzcz0nbmF2YmFyLW5hdiBtbC1hdXRvJz4KICAgICAgICB7eyBpZiAuU3BhY2UgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy8nPkhvbWU8L2E+PC9saT4KICAgICAgICB7eyBlbmQgfX0KICAgICAgICB7eyBpZiAuQ29udGVudFR5cGUgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgLkhvb2sgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy9zcGFjZS97eyAuU3BhY2UuSUQgfX0nPnt7IC5TcGFjZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgLkNvbnRlbnQgfX0KICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy9jb250ZW50dHlwZS97eyAuU3BhY2UuSUR9fS97eyAuQ29udGVudFR5cGUuSUQgfX0nPnt7IC5Db250ZW50VHlwZS5OYW1lIH19PC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgYW5kIC5TcGFjZSAobm90IC5Db250ZW50VHlwZSkgKG5vdCAuSG9vaykgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiNjb3B5TW9kYWwiIGNsYXNzPSduYXYtbGluaycgaHJlZj0nIyc+Q29weTwvYT48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI3VwZGF0ZU1vZGFsIiBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9JyMnPlVwZGF0ZTwvYT48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI2RlbGV0ZU1vZGFsIiBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9JyMnPkRlbGV0ZTwvYT48L2xpPgogICAgICAgIHt7IGVuZCB9fQogICAgICAgIHt7IGlmIGFuZCAuQ29udGVudFR5cGUgKG5vdCAuQ29udGVudCkgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiN1cGRhdGVNb2RhbCIgY2xhc3M9J25hdi1saW5rJyBocmVmPScjJz5VcGRhdGU8L2E+PC9saT4KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPjxhIGRhdGEtdG9nZ2xlPSJtb2RhbCIgZGF0YS10YXJnZXQ9IiNkZWxldGVNb2RhbCIgY2xhc3M9J25hdi1saW5rJyBocmVmPScjJz5EZWxldGU8L2E+PC9saT4KICAgICAgICB7eyBlbmQgfX0KICAgICAgICB7eyBpZiAuQ29udGVudCB9fQogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGlucHV0IHR5cGU9c3VibWl0IGNsYXNzPSJidG4gYnRuLWxpbmsgbmF2LWxpbmsgYm9yZGVyLTAiIHZhbHVlPVNhdmUgLz48L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgZGF0YS10b2dnbGU9Im1vZGFsIiBkYXRhLXRhcmdldD0iI2RlbGV0ZU1vZGFsIiBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9JyMnPkRlbGV0ZTwvYT48L2xpPgogICAgICAgIHt7IGVuZCB9fQogICAgICAgIHt7IGlmIC5Ib29rIH19CiAgICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBkYXRhLXRvZ2dsZT0ibW9kYWwiIGRhdGEtdGFyZ2V0PSIjZGVsZXRlTW9kYWwiIGNsYXNzPSduYXYtbGluaycgaHJlZj0nIyc+RGVsZXRlPC9hPjwvbGk+CiAgICAgICAge3sgZW5kIH19CiAgICAgICAge3sgaWYgLlVzZXIgfX0KICAgICAgICAgIDxsaSBjbGFzcz0nbmF2LWl0ZW0nPgogICAgICAgICAgICA8Zm9ybSBtZXRob2Q9UE9TVCBhY3Rpb249Jy91c2VyL2xvZ291dCcgZW5jdHlwZT0nbXVsdGlwYXJ0L2Zvcm0tZGF0YSc+CiAgICAgICAgICAgICAgPGlucHV0IHR5cGU9c3VibWl0IGNsYXNzPSJidG4gYnRuLWxpbmsgbmF2LWxpbmsgYm9yZGVyLTAiIHZhbHVlPUxvZ291dCAvPgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICA8L2xpPgogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgY2xhc3M9J25hdi1saW5rJyBocmVmPScvcGFnZS9iaWxsaW5nJz5CaWxsaW5nPC9hPjwvbGk+CiAgICAgICAge3sgZWxzZSB9fQogICAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgY2xhc3M9J25hdi1saW5rJyBocmVmPScvI3NpZ251cCc+U2lnbnVwPC9hPjwvbGk+CiAgICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy8jbG9naW4nPkxvZ2luPC9hPjwvbGk+CiAgICAgICAgICA8bGkgY2xhc3M9J25hdi1pdGVtJz48YSBjbGFzcz0nbmF2LWxpbmsnIGhyZWY9Jy8jcHJpY2luZyc+UHJpY2luZzwvYT48L2xpPgogICAgICAgIHt7IGVuZH19CiAgICAgICAgPGxpIGNsYXNzPSduYXYtaXRlbSc+PGEgY2xhc3M9J25hdi1saW5rJyBocmVmPScvcGFnZS9kb2MnPkRvY3M8L2E+PC9saT4KICAgICAgPC91bD4KICAgIDwvZGl2PgogIDwvbmF2Pgo8L2hlYWRlcj4K")

	tmpls["html/_scripts.html"] = tostring("PHNjcmlwdCBzcmM9Jy9zdGF0aWMvanMvcG9wcGVyLm1pbi5qcyc+PC9zY3JpcHQ+CjxzY3JpcHQgc3JjPScvc3RhdGljL2pzL2Jvb3RzdHJhcC5taW4uanMnPjwvc2NyaXB0Pgo=")

	tmpls["html/billing.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgogIHt7IHRlbXBsYXRlICJodG1sL19oZWFkLmh0bWwiIH19CiAgPHRpdGxlPkNNUyB8IEJpbGxpbmc8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5IGNsYXNzPSdwYWdlIGJnLWxpZ2h0Jz4KICA8c3R5bGU+e3sgdGVtcGxhdGUgImNzcy9tYWluLmNzcyIgfX08L3N0eWxlPgogIDxtYWluPgogICAge3sgdGVtcGxhdGUgImh0bWwvX2hlYWRlci5odG1sIiAkIH19CiAgICA8ZGl2IGNsYXNzPSJwcmljaW5nLWhlYWRlciBweC0zIHB5LTMgcHQtbWQtNSBwYi1tZC00IG14LWF1dG8gdGV4dC1jZW50ZXIiPgogICAgICA8aDEgY2xhc3M9ImRpc3BsYXktNCI+QmlsbGluZzwvaDE+CiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9J2NvbnRhaW5lcic+CiAgICAgIDxkaXYgY2xhc3M9J3Jvdyc+CiAgICAgICAgPGRpdiBjbGFzcz0iY29sLTEyIG9mZnNldC0wIGNvbC1sZy04IG9mZnNldC1sZy0yIj4KICAgICAgICAgIFRPRE8KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICAgIHt7IHRlbXBsYXRlICJodG1sL19mb290ZXIuaHRtbCIgJCB9fQogIDwvbWFpbj4KICB7eyB0ZW1wbGF0ZSAiaHRtbC9fc2NyaXB0cy5odG1sIiB9fQo8L2JvZHk+CjwvaHRtbD4K")
	tmpls["html/billing.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgogIHt7IHRlbXBsYXRlICJodG1sL19oZWFkLmh0bWwiIH19CiAgPHRpdGxlPkNNUyB8IEJpbGxpbmc8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5IGNsYXNzPSdwYWdlIGJnLWxpZ2h0Jz4KICA8c3R5bGU+e3sgdGVtcGxhdGUgImNzcy9tYWluLmNzcyIgfX08L3N0eWxlPgogIDxtYWluPgogICAge3sgdGVtcGxhdGUgImh0bWwvX2hlYWRlci5odG1sIiAkIH19CiAgICA8ZGl2IGNsYXNzPSJwcmljaW5nLWhlYWRlciBweC0zIHB5LTMgcHQtbWQtNSBwYi1tZC00IG14LWF1dG8gdGV4dC1jZW50ZXIiPgogICAgICA8aDEgY2xhc3M9ImRpc3BsYXktNCI+QmlsbGluZzwvaDE+CiAgICA8L2Rpdj4KICAgIHt7aWYgLlVzZXJ9fQogICAgICA8ZGl2IGNsYXNzPSdjb250YWluZXInPgogICAgICAgIDxkaXYgY2xhc3M9J3Jvdyc+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2wtMTIgY29sLW1kLTYiPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSd0ZXh0LWNlbnRlcic+CiAgICAgICAgICAgICAge3tpZiAuVXNlci5IYXNFbWFpbH19CiAgICAgICAgICAgICAgPHA+VXBkYXRlIHlvdXIgZW1haWwuPC9wPgogICAgICAgICAgICAgIHt7ZWxzZX19CiAgICAgICAgICAgICAgPHA+U2V0IHlvdXIgZW1haWwgaW4gY2FzZSB5b3UgZ2V0IGxvY2tlZCBvdXQgb2YgeW91ciBhY2NvdW50LjwvcD4KICAgICAgICAgICAgICB7e2VuZH19CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8Zm9ybSBhY3Rpb249Jy91c2VyL3VwZGF0ZS9lbWFpbCcgbWV0aG9kPVBPU1Q+CiAgICAgICAgICAgICAgPGxhYmVsIGZvcj1lbWFpbD5FbWFpbDwvbGFiZWw+CiAgICAgICAgICAgICAgPGlucHV0IGlkPWVtYWlsIG5hbWU9ZW1haWwgdHlwZT1lbWFpbCBjbGFzcz0ibWItMyBmb3JtLWNvbnRyb2wiIHBsYWNlaG9sZGVyPSJlbWFpbCIgcmVxdWlyZWQge3tpZiAuVXNlci5IYXNFbWFpbH19dmFsdWU9Int7LlVzZXIuRW1haWx9fSJ7e2VuZH19PgogICAgICAgICAgICAgIDxidXR0b24gdHlwZT0ic3VibWl0IiBjbGFzcz0iYnRuIGJ0bi1wcmltYXJ5Ij5HbzwvYnV0dG9uPgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC0xMiBjb2wtbWQtNiI+CiAgICAgICAgICAgIHt7aWYgLlVzZXIgfCBwYWlkfX0KICAgICAgICAgICAgICA8cCBjbGFzcz0ndGV4dC1jZW50ZXIgbWItNSc+Q2FuY2VsIHlvdXIgc3Vic2NyaXB0aW9uLCB7ey5Vc2VyLk9yZy5UaWVyLk5hbWV9fSB0aWVyLjwvcD4KICAgICAgICAgICAge3tlbHNlfX0KICAgICAgICAgICAgICA8cCBjbGFzcz0ndGV4dC1jZW50ZXIgbWItNSc+VXBncmFkZSB0byBhIHBhaWQgdGllci48YnI+R2V0IG1vcmUgYWNjZXNzLjwvcD4KICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJyb3cgcm93LWNvbHMtMSByb3ctY29scy1tZC0yIHJvdy1jb2xzLWxnLTMgbWItNSB0ZXh0LWNlbnRlciI+CiAgICAgICAgICAgICAgICB7e3JhbmdlIC5UaWVyc319CiAgICAgICAgICAgICAgICAgIHt7aWYgbm90ICgufGlzRnJlZSl9fQogICAgICAgICAgICAgICAgICAgIDxmb3JtIGFjdGlvbj0nL3VzZXIvdXBkYXRlL2JpbGxpbmcnIG1ldGhvZD1QT1NUIGNsYXNzPSJjb2wiPgogICAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9aGlkZGVuIG5hbWU9dGllciB2YWx1ZT0ie3suTmFtZX19IiAvPgogICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZCBtYi00IHNoYWRvdy1zbSI+CiAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxoNCBjbGFzcz0ibXktMCBmb250LXdlaWdodC1ub3JtYWwiPnt7Lk5hbWV9fTwvaDQ+CiAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxoMSBjbGFzcz0iY2FyZC10aXRsZSBwcmljaW5nLWNhcmQtdGl0bGUiPnt7LlByaWNlfX0gPHNtYWxsIGNsYXNzPSJ0ZXh0LW11dGVkIj4vIHt7LlRpbWVVbml0fX08L3NtYWxsPjwvaDE+CiAgICAgICAgICAgICAgICAgICAgICAgIDx1bCBjbGFzcz0ibGlzdC11bnN0eWxlZCBtdC0zIG1iLTQiPgogICAgICAgICAgICAgICAgICAgICAgICAgIHt7cmFuZ2UgLk9wdHN9fQogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGxpPnt7LlRleHR9fTwvbGk+CiAgICAgICAgICAgICAgICAgICAgICAgICAge3tlbmR9fQogICAgICAgICAgICAgICAgICAgICAgICA8L3VsPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tcHJpbWFyeSB3LTEwMCI+R288L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZm9ybT4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAge3tlbmR9fQogICAgICAgICAgICAgICAge3tlbmR9fQogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICB7e2VuZH19CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICB7e2Vsc2V9fQogICAgICA8ZGl2IGNsYXNzPSdjb250YWluZXInPgogICAgICAgIDxkaXYgY2xhc3M9J3Jvdyc+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJjb2wtMTIiPgogICAgICAgICAgICA8aDE+T29wczwvaDE+CiAgICAgICAgICAgIDxwPlNvcnJ5LCBvdXIgZGV2ZWxvcGVycyBhcmUgbGF6eS4gVGhpcyBzaG91bGQgcmVhbGx5IHJlZGlyZWN0IHlvdS48L3A+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICB7e2VuZH19CiAgICB7eyB0ZW1wbGF0ZSAiaHRtbC9fZm9vdGVyLmh0bWwiICQgfX0KICA8L21haW4+CiAge3sgdGVtcGxhdGUgImh0bWwvX3NjcmlwdHMuaHRtbCIgfX0KPC9ib2R5Pgo8L2h0bWw+Cg==")

	tmpls["html/contact.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgogIHt7IHRlbXBsYXRlICJodG1sL19oZWFkLmh0bWwiIH19CiAgPHRpdGxlPkNNUyB8IENvbnRhY3Q8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5IGNsYXNzPSdwYWdlIGJnLWxpZ2h0Jz4KICA8c3R5bGU+e3sgdGVtcGxhdGUgImNzcy9tYWluLmNzcyIgfX08L3N0eWxlPgogIDxtYWluPgogICAge3sgdGVtcGxhdGUgImh0bWwvX2hlYWRlci5odG1sIiAkIH19CiAgICA8ZGl2IGNsYXNzPSJwcmljaW5nLWhlYWRlciBweC0zIHB5LTMgcHQtbWQtNSBwYi1tZC00IG14LWF1dG8gdGV4dC1jZW50ZXIiPgogICAgICA8aDEgY2xhc3M9ImRpc3BsYXktNCI+Q29udGFjdDwvaDE+CiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9J2NvbnRhaW5lcic+CiAgICAgIDxkaXYgY2xhc3M9J3Jvdyc+CiAgICAgICAgPGRpdiBjbGFzcz0iY29sLTEyIG9mZnNldC0wIGNvbC1sZy04IG9mZnNldC1sZy0yIj4KICAgICAgICAgIFRPRE8KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICAgIHt7IHRlbXBsYXRlICJodG1sL19mb290ZXIuaHRtbCIgJCB9fQogIDwvbWFpbj4KICB7eyB0ZW1wbGF0ZSAiaHRtbC9fc2NyaXB0cy5odG1sIiB9fQo8L2JvZHk+CjwvaHRtbD4K")


M internal/v/v.go => internal/v/v.go +4 -3
@@ 16,9 16,10 @@ func MustParse(name string) *template.Template {
	if all == nil {

		fns := template.FuncMap{
			"inc":   func(i int) int { return i + 1 },
			"title": func(str string) string { return strings.Title(str) },
			"paid":  func(u user.User) bool { return u.Org().Tier().Is(tier.Business) || u.Org().Tier().Is(tier.Enterprise) },
			"inc":    func(i int) int { return i + 1 },
			"title":  func(str string) string { return strings.Title(str) },
			"paid":   func(u user.User) bool { return u.Org().Tier().Is(tier.Business) || u.Org().Tier().Is(tier.Enterprise) },
			"isFree": func(t tier.Tier) bool { return t.Is(tier.Free) },
		}

		all = template.New("cms")