diff --git a/public/android-icon-144x144.png b/public/android-icon-144x144.png
new file mode 100644
index 00000000..101f25a2
Binary files /dev/null and b/public/android-icon-144x144.png differ
diff --git a/public/android-icon-192x192.png b/public/android-icon-192x192.png
new file mode 100644
index 00000000..65981642
Binary files /dev/null and b/public/android-icon-192x192.png differ
diff --git a/public/android-icon-36x36.png b/public/android-icon-36x36.png
new file mode 100644
index 00000000..2c73870f
Binary files /dev/null and b/public/android-icon-36x36.png differ
diff --git a/public/android-icon-48x48.png b/public/android-icon-48x48.png
new file mode 100644
index 00000000..e3e167d6
Binary files /dev/null and b/public/android-icon-48x48.png differ
diff --git a/public/android-icon-72x72.png b/public/android-icon-72x72.png
new file mode 100644
index 00000000..363ae897
Binary files /dev/null and b/public/android-icon-72x72.png differ
diff --git a/public/android-icon-96x96.png b/public/android-icon-96x96.png
new file mode 100644
index 00000000..f18359d1
Binary files /dev/null and b/public/android-icon-96x96.png differ
diff --git a/public/apple-icon-114x114.png b/public/apple-icon-114x114.png
new file mode 100644
index 00000000..d928a1d5
Binary files /dev/null and b/public/apple-icon-114x114.png differ
diff --git a/public/apple-icon-120x120.png b/public/apple-icon-120x120.png
new file mode 100644
index 00000000..cbb2631d
Binary files /dev/null and b/public/apple-icon-120x120.png differ
diff --git a/public/apple-icon-144x144.png b/public/apple-icon-144x144.png
new file mode 100644
index 00000000..101f25a2
Binary files /dev/null and b/public/apple-icon-144x144.png differ
diff --git a/public/apple-icon-152x152.png b/public/apple-icon-152x152.png
new file mode 100644
index 00000000..395c7bfe
Binary files /dev/null and b/public/apple-icon-152x152.png differ
diff --git a/public/apple-icon-180x180.png b/public/apple-icon-180x180.png
new file mode 100644
index 00000000..68f9070b
Binary files /dev/null and b/public/apple-icon-180x180.png differ
diff --git a/public/apple-icon-57x57.png b/public/apple-icon-57x57.png
new file mode 100644
index 00000000..ef69c3d1
Binary files /dev/null and b/public/apple-icon-57x57.png differ
diff --git a/public/apple-icon-60x60.png b/public/apple-icon-60x60.png
new file mode 100644
index 00000000..a833697e
Binary files /dev/null and b/public/apple-icon-60x60.png differ
diff --git a/public/apple-icon-72x72.png b/public/apple-icon-72x72.png
new file mode 100644
index 00000000..363ae897
Binary files /dev/null and b/public/apple-icon-72x72.png differ
diff --git a/public/apple-icon-76x76.png b/public/apple-icon-76x76.png
new file mode 100644
index 00000000..fe344c2f
Binary files /dev/null and b/public/apple-icon-76x76.png differ
diff --git a/public/apple-icon-precomposed.png b/public/apple-icon-precomposed.png
new file mode 100644
index 00000000..52ec936e
Binary files /dev/null and b/public/apple-icon-precomposed.png differ
diff --git a/public/apple-icon.png b/public/apple-icon.png
new file mode 100644
index 00000000..52ec936e
Binary files /dev/null and b/public/apple-icon.png differ
diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png
new file mode 100644
index 00000000..e1ea7b48
Binary files /dev/null and b/public/favicon-16x16.png differ
diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png
new file mode 100644
index 00000000..b769ca15
Binary files /dev/null and b/public/favicon-32x32.png differ
diff --git a/public/favicon-96x96.png b/public/favicon-96x96.png
new file mode 100644
index 00000000..f18359d1
Binary files /dev/null and b/public/favicon-96x96.png differ
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 00000000..68921fdc
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/logo_nounder.svg b/public/logo_nounder.svg
new file mode 100644
index 00000000..841809f5
--- /dev/null
+++ b/public/logo_nounder.svg
@@ -0,0 +1,29 @@
+
+
diff --git a/public/logo_underscore.svg b/public/logo_underscore.svg
new file mode 100644
index 00000000..03f30888
--- /dev/null
+++ b/public/logo_underscore.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/public/style.css b/public/style.css
index e88c6ddc..ba65c407 100644
--- a/public/style.css
+++ b/public/style.css
@@ -7253,8 +7253,7 @@ body {
box-sizing: border-box;
font-size: 0.875rem;
line-height: 1.5em;
- font-weight: 400;
- background-image: url("data:image/png;iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAEGGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjcyIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iNzIiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNzIiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjcyIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI5Ni4wIgogICB0aWZmOllSZXNvbHV0aW9uPSI5Ni4wIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjcuMSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+87tjqAAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG/S0JRFMc/amGUYZBDQ4NENVmUgdTSoJQF1WAG/Vr05Y9A7fGeEdIatAoFUUu/hvoLag2ag6AogmgLmotaSl7n+QQl8l7OOZ/7vfcc7j0X7NGMktUbBiCby2uRcNA7v7Dodb7iwCXmwRNTdHV6djxK3fH1gM2Md31mrfrn/h0tKwldAVuT8KiiannhCeGpjbxq8q6wR0nHVoTPhX2aXFD43tTjFr+ZnLL4x2QtGgmBvU3Ym6rheA0raS0rLC+nO5tZVyr3MV/iSuTmZiV2iXWiEyFMEC+TjBEiwCAj4gP04adfVtTJHyjnz7AmuYp4lQIaq6RIk8cn6rpUT0hMip6QmaFg9v9vX/XkkN+q7gpC44thfPSAcwdKRcP4PjaM0gk4nuEqV81fO4LhT9GLVa37ENxbcHFd1eJ7cLkNHU9qTIuVJYeYPZmE9zNoXYD2W2hesnpW2ef0EaKb8lU3sH8AvXLevfwLEU5nv19tQRgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAJSSURBVHic7ZvBbsMgDIaTHKbtnNO0vf/bcW7VS3uoFlWhgAEb//bwrVrzyxjHhq/euu/7fam0z4+v5Xq7JD970llbApQT9aaz5b7A4QyXTghh1fAnm0FouxpC6M74Wn+iDHqN8PV2IUU85QSHzmvm7Pt+p2QSpz9RgM4Pty6OS+cclNYgtfpzBCj3cM3iuHRyQakJUq8/R4BKD1MXx6VTCgo1SL3+REUP7WxyLsythbrVn1mDCpbcjdnin5Y8KFIjXNpNqk7pO9TMKenU+tO1K1q7OlJn6znAnYtnrzN/n5F0NoudZaTO24NizkrBqTnA5RZVczCV1Nne/TFllOBQMomyKIo/I3TWn+/f+3zN0jqzBhV0utu091bfRRQpwbFOJk0RRY1rCzxR1CaT8Ld5bSoASxRRyCQsUUQhk/BEUZtMzhpUMDNEUYtMdhNFboInrVNLJruuCWjZAUUU0UmgKlFE61BQRNEKCeTSqSKKlkggl04zUbTweqgRRfRFQRFF5BbNoZM8KGrNBKLpZDPIO2+mWJRB2jOBaDpRgLTncdB0jgChzASi6RwBQpkJRNOJivB/+tWUojNrUMGS2TFb/NOSB0WtmcBeHe6ZSVdEUSLr3RBFqZlJF7d5yc5rnihKz0yaJoojZibdEEWp12zWoII/7ogid6sX/6/nkWRSYmZyyIyi5WuL2IyiFzIpNh/khQqwzyh6I5PsM4reyKTYjKIXMjlrUMHEZxQtt/hlGTCjaJVMuiSKEjpuiKKUjovbvKSOeaIorWOaKI7QcUMUpXQe+31Pc6xeN0AAAAAASUVORK5CYII="); }
+ font-weight: 400; }
a {
color: #666;
diff --git a/public/themes/dark/accent_bg.svg b/public/themes/dark/accent_bg.svg
new file mode 100644
index 00000000..739c4e8a
--- /dev/null
+++ b/public/themes/dark/accent_bg.svg
@@ -0,0 +1,1482 @@
+
diff --git a/public/themes/dark/accent_top_a.svg b/public/themes/dark/accent_top_a.svg
new file mode 100644
index 00000000..347800c2
--- /dev/null
+++ b/public/themes/dark/accent_top_a.svg
@@ -0,0 +1,912 @@
+
diff --git a/public/themes/dark/empty-avatar.svg b/public/themes/dark/empty-avatar.svg
new file mode 100644
index 00000000..acb0354c
--- /dev/null
+++ b/public/themes/dark/empty-avatar.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/public/themes/dark/gitlab.svg b/public/themes/dark/gitlab.svg
new file mode 100644
index 00000000..46d2b7ee
--- /dev/null
+++ b/public/themes/dark/gitlab.svg
@@ -0,0 +1,77 @@
+
+
\ No newline at end of file
diff --git a/public/themes/dark/patreon_large.svg b/public/themes/dark/patreon_large.svg
new file mode 100644
index 00000000..db30a77c
--- /dev/null
+++ b/public/themes/dark/patreon_large.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/public/themes/light/accent_bg.svg b/public/themes/light/accent_bg.svg
new file mode 100644
index 00000000..894e9fe4
--- /dev/null
+++ b/public/themes/light/accent_bg.svg
@@ -0,0 +1,1482 @@
+
diff --git a/public/themes/light/accent_top_a.svg b/public/themes/light/accent_top_a.svg
new file mode 100644
index 00000000..77a7af12
--- /dev/null
+++ b/public/themes/light/accent_top_a.svg
@@ -0,0 +1,903 @@
+
diff --git a/public/themes/light/empty-avatar.svg b/public/themes/light/empty-avatar.svg
new file mode 100644
index 00000000..62aaa3eb
--- /dev/null
+++ b/public/themes/light/empty-avatar.svg
@@ -0,0 +1,12 @@
+
+
+
diff --git a/public/themes/light/gitlab.svg b/public/themes/light/gitlab.svg
new file mode 100644
index 00000000..46d2b7ee
--- /dev/null
+++ b/public/themes/light/gitlab.svg
@@ -0,0 +1,77 @@
+
+
\ No newline at end of file
diff --git a/public/themes/light/patreon_large.svg b/public/themes/light/patreon_large.svg
new file mode 100644
index 00000000..b40cf2fe
--- /dev/null
+++ b/public/themes/light/patreon_large.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/src/config/types.go b/src/config/types.go
index ab18b765..b935cad0 100644
--- a/src/config/types.go
+++ b/src/config/types.go
@@ -13,6 +13,7 @@ const (
type HMNConfig struct {
Env Environment
Addr string
+ BaseUrl string
Postgres PostgresConfig
}
diff --git a/src/hmnurl/hmnurl.go b/src/hmnurl/hmnurl.go
new file mode 100644
index 00000000..d2be1e78
--- /dev/null
+++ b/src/hmnurl/hmnurl.go
@@ -0,0 +1,46 @@
+package hmnurl
+
+import (
+ "net/url"
+
+ "git.handmade.network/hmn/hmn/src/config"
+)
+
+const StaticPath = "/public"
+const StaticThemePath = "/public/themes"
+
+type Q struct {
+ Name string
+ Value string
+}
+
+func Url(path string, query []Q) string {
+ result := config.Config.BaseUrl + "/" + trim(path)
+ if q := encodeQuery(query); q != "" {
+ result += "?" + q
+ }
+ return result
+}
+
+func StaticUrl(path string, query []Q) string {
+ return Url(StaticPath+"/"+trim(path), query)
+}
+
+func StaticThemeUrl(path string, theme string, query []Q) string {
+ return Url(StaticThemePath+"/"+theme+"/"+trim(path), query)
+}
+
+func trim(path string) string {
+ if path[0] == '/' {
+ return path[1:]
+ }
+ return path
+}
+
+func encodeQuery(query []Q) string {
+ result := url.Values{}
+ for _, q := range query {
+ result.Set(q.Name, q.Value)
+ }
+ return result.Encode()
+}
diff --git a/src/hmnurl/hmnurl_test.go b/src/hmnurl/hmnurl_test.go
new file mode 100644
index 00000000..959c0166
--- /dev/null
+++ b/src/hmnurl/hmnurl_test.go
@@ -0,0 +1,24 @@
+package hmnurl
+
+import (
+ "testing"
+
+ "git.handmade.network/hmn/hmn/src/config"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestUrl(t *testing.T) {
+ defer func(original string) {
+ config.Config.BaseUrl = original
+ }(config.Config.BaseUrl)
+ config.Config.BaseUrl = "http://handmade.test"
+
+ t.Run("no query", func(t *testing.T) {
+ result := Url("/test/foo", nil)
+ assert.Equal(t, "http://handmade.test/test/foo", result)
+ })
+ t.Run("yes query", func(t *testing.T) {
+ result := Url("/test/foo", []Q{{"bar", "baz"}, {"zig??", "zig & zag!!"}})
+ assert.Equal(t, "http://handmade.test/test/foo?bar=baz&zig%3F%3F=zig+%26+zag%21%21", result)
+ })
+}
diff --git a/src/templates/src/include/footer.html b/src/templates/src/include/footer.html
index cebc1028..e1be899d 100644
--- a/src/templates/src/include/footer.html
+++ b/src/templates/src/include/footer.html
@@ -1 +1,35 @@
-I'm a footer~!
\ No newline at end of file
+
diff --git a/src/templates/src/include/header.html b/src/templates/src/include/header.html
index 7e685d8d..84f51657 100644
--- a/src/templates/src/include/header.html
+++ b/src/templates/src/include/header.html
@@ -1 +1,96 @@
-I'm a header!
\ No newline at end of file
+
+
+
diff --git a/src/templates/src/layouts/base.html b/src/templates/src/layouts/base.html
index cbd07daa..734c5cad 100644
--- a/src/templates/src/layouts/base.html
+++ b/src/templates/src/layouts/base.html
@@ -18,7 +18,7 @@
{{ end }}
-
+
{{/* TODO: These are the base64 encodings of bglight.png and bgdark.png. Rather than manually putting the encoding here, it would be nice to automatically calculate it when the server starts up and pass it in. */}}
{{ $bglight := "iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAEGGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjcyIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iNzIiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNzIiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjcyIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI5Ni4wIgogICB0aWZmOllSZXNvbHV0aW9uPSI5Ni4wIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0xMVQyMToyMjozMC0wNjowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0xMVQyMToyMjozMC0wNjowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjcuMSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wMy0xMVQyMToyMjozMC0wNjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+NRFFrAAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG/S0JRFMc/amGUYZBDQ4NENVmUgdTSoJQF1WAG/Vr05Y9A7fGeEdIatAoFUUu/hvoLag2ag6AogmgLmotaSl7n+QQl8l7OOZ/7vfcc7j0X7NGMktUbBiCby2uRcNA7v7Dodb7iwCXmwRNTdHV6djxK3fH1gM2Md31mrfrn/h0tKwldAVuT8KiiannhCeGpjbxq8q6wR0nHVoTPhX2aXFD43tTjFr+ZnLL4x2QtGgmBvU3Ym6rheA0raS0rLC+nO5tZVyr3MV/iSuTmZiV2iXWiEyFMEC+TjBEiwCAj4gP04adfVtTJHyjnz7AmuYp4lQIaq6RIk8cn6rpUT0hMip6QmaFg9v9vX/XkkN+q7gpC44thfPSAcwdKRcP4PjaM0gk4nuEqV81fO4LhT9GLVa37ENxbcHFd1eJ7cLkNHU9qTIuVJYeYPZmE9zNoXYD2W2hesnpW2ef0EaKb8lU3sH8AvXLevfwLEU5nv19tQRgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAJOSURBVHic7ZtRksMgCIaTPOcI3v9MPYBHyHv2obOZTlIVFeSHyltnm38QCehXdo0xnkulHcex7Pue/OxJZ20JUE7Um86W+wKHM1w6IYRVw59sBqHtagihO+Nr/Xlk0GeE930nRTzlBIfOZ+bEGE9KJnH68wjQ/eHWxXHp3IPSGqRWf64A5R6uWRyXTi4oNUHq9ecKUOlh6uK4dEpBoQap159H0UM7m9wLc2uhbvVn1qCCJXdjtvi3JQ+K1AiXdpOqU/oONXNKOrX+dO2K1q6O1Nl6DnD34tnrzP9nJJ3NYmcZqfP1oJizUnBqDnC5RdUcTCV1tm9/TBklOJRMoiyK4s8InfX1ep3zNUvrzBpU0Olu095bfRdRpATHOpk0RRQ1ri3wRFGbTMLf5rWpACxRRCGTsEQRhUzCE0VtMjlrUMHMEEUtMtlNFLkJnrROLZnsuiagZQcUUUQngapEEa1DQRFFKySQS6eKKFoigVw6zUTRwuuhRhTRFwVFFJFbNIdO8qCoNROIppPNIO+8mWKPDNKeCUTTeQRIex4HTecKEMpMIJrOFSCUmUA0nUcR/qVfTSk6swYVLJkds8W/LXlQ1JoJ7NXhnpl0RRQlst4NUZSamXRxm5fsvOaJovTMpGmiOGJm0g1RlHrNZg0q+OOOKHK3evH/eh5JJiVmJofMKFq+tojNKHohk2LzQV6oAPuMojcyyT6j6I1Mis0oeiGTswYVTHxG0XKLX5YBM4pWyaRLoiih44YoSum4uM1L6pgnitI6poniCB03RFFK5w8yFS9yPecAUAAAAABJRU5ErkJggg==" }}
{{ $bgdark := "iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAEGGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjcyIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iNzIiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNzIiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjcyIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI5Ni4wIgogICB0aWZmOllSZXNvbHV0aW9uPSI5Ni4wIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjcuMSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+87tjqAAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG/S0JRFMc/amGUYZBDQ4NENVmUgdTSoJQF1WAG/Vr05Y9A7fGeEdIatAoFUUu/hvoLag2ag6AogmgLmotaSl7n+QQl8l7OOZ/7vfcc7j0X7NGMktUbBiCby2uRcNA7v7Dodb7iwCXmwRNTdHV6djxK3fH1gM2Md31mrfrn/h0tKwldAVuT8KiiannhCeGpjbxq8q6wR0nHVoTPhX2aXFD43tTjFr+ZnLL4x2QtGgmBvU3Ym6rheA0raS0rLC+nO5tZVyr3MV/iSuTmZiV2iXWiEyFMEC+TjBEiwCAj4gP04adfVtTJHyjnz7AmuYp4lQIaq6RIk8cn6rpUT0hMip6QmaFg9v9vX/XkkN+q7gpC44thfPSAcwdKRcP4PjaM0gk4nuEqV81fO4LhT9GLVa37ENxbcHFd1eJ7cLkNHU9qTIuVJYeYPZmE9zNoXYD2W2hesnpW2ef0EaKb8lU3sH8AvXLevfwLEU5nv19tQRgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAJSSURBVHic7ZvBbsMgDIaTHKbtnNO0vf/bcW7VS3uoFlWhgAEb//bwrVrzyxjHhq/euu/7fam0z4+v5Xq7JD970llbApQT9aaz5b7A4QyXTghh1fAnm0FouxpC6M74Wn+iDHqN8PV2IUU85QSHzmvm7Pt+p2QSpz9RgM4Pty6OS+cclNYgtfpzBCj3cM3iuHRyQakJUq8/R4BKD1MXx6VTCgo1SL3+REUP7WxyLsythbrVn1mDCpbcjdnin5Y8KFIjXNpNqk7pO9TMKenU+tO1K1q7OlJn6znAnYtnrzN/n5F0NoudZaTO24NizkrBqTnA5RZVczCV1Nne/TFllOBQMomyKIo/I3TWn+/f+3zN0jqzBhV0utu091bfRRQpwbFOJk0RRY1rCzxR1CaT8Ld5bSoASxRRyCQsUUQhk/BEUZtMzhpUMDNEUYtMdhNFboInrVNLJruuCWjZAUUU0UmgKlFE61BQRNEKCeTSqSKKlkggl04zUbTweqgRRfRFQRFF5BbNoZM8KGrNBKLpZDPIO2+mWJRB2jOBaDpRgLTncdB0jgChzASi6RwBQpkJRNOJivB/+tWUojNrUMGS2TFb/NOSB0WtmcBeHe6ZSVdEUSLr3RBFqZlJF7d5yc5rnihKz0yaJoojZibdEEWp12zWoII/7ogid6sX/6/nkWRSYmZyyIyi5WuL2IyiFzIpNh/khQqwzyh6I5PsM4reyKTYjKIXMjlrUMHEZxQtt/hlGTCjaJVMuiSKEjpuiKKUjovbvKSOeaIorWOaKI7QcUMUpXQe+31Pc6xeN0AAAAAASUVORK5CYII=" }}
@@ -31,18 +31,16 @@
background-size: "{{ . }}";
{{ end }}
{{ else }}
- background-color: #{{ or .ProjectColor "999999" }};
- {{ if eq .Theme "dark" }}
- background-image: url('data:image/png;{{ $bgdark }}');
- {{ else }}
- background-image: url('data:image/png;{{ $bglight }}');
- {{ end }}
+ {{ $bgcolor := or .Project.Color "999999" }}
+ background-color: #{{ eq .Theme "dark" | ternary (darken $bgcolor 0.6) (brighten $bgcolor 0.6) }};
+ background-image: url('data:image/png;base64,{{ eq .Theme "dark" | ternary $bgdark $bglight }}');
+ background-size: auto;
{{ end }}
}
{{ block "extrahead" . }}{{ end }}
-
+
@@ -64,9 +62,9 @@
- {{ template "header.html" }}
+ {{ template "header.html" . }}
based
- {{ template "footer.html" }}
+ {{ template "footer.html" . }}
diff --git a/src/templates/src/project.css b/src/templates/src/project.css
index ce071e73..9748bc1a 100644
--- a/src/templates/src/project.css
+++ b/src/templates/src/project.css
@@ -99,6 +99,7 @@ all of this CSS.
header .menu-bar .hmdev-logo {
background-image:url("{{ static "logo_nounder.svg" }}");
}
+
header .menu-bar .hmdev-logo .underscore {
background-image:url("{{ static "logo_underscore.svg" }}");
}
@@ -107,18 +108,6 @@ header .menu-bar .hmdev-logo.project {
background-image:url("{{ static "logo_net.svg" }}");
}
-.links .thing#projects {
- background-image:url("{{ statictheme .Theme "project_thing.svg" }}");
-}
-
-.links .thing#discussion {
- background-image:url("{{ statictheme .Theme "discuss_thing.svg" }}");
-}
-
-.links .thing#blogs {
- background-image:url("{{ statictheme .Theme "blog_thing.svg" }}");
-}
-
.half.light {
background-image:url("{{ statictheme "light" "accent_top_a.svg" }}");
}
diff --git a/src/templates/templates.go b/src/templates/templates.go
index b3bdd819..9184fd63 100644
--- a/src/templates/templates.go
+++ b/src/templates/templates.go
@@ -4,9 +4,11 @@ import (
"embed"
"fmt"
"html/template"
+ "net/url"
"strings"
"time"
+ "git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/logging"
"github.com/Masterminds/sprig"
"github.com/teacat/noire"
@@ -77,21 +79,38 @@ var HMNTemplateFuncs = template.FuncMap{
}
return noire.NewHex(hexColor).Shade(amount).Hex(), nil
},
- // TODO: Actually put paths in here, duh
+ "projecturl": func(url string) string {
+ return hmnurl.Url(url, nil) // TODO: Use project subdomain
+ },
+ "projecturlq": func(url string, query string) string {
+ absUrl := hmnurl.Url(url, nil)
+ return fmt.Sprintf("%s?%s", absUrl, query) // TODO: Use project subdomain
+ },
+ "query": func(args ...string) string {
+ query := url.Values{}
+ for i := 0; i < len(args); i += 2 {
+ query.Set(args[i], args[i+1])
+ }
+ return query.Encode()
+ },
"static": func(filepath string) string {
- return fmt.Sprintf("A static file at %v, busted with %v", filepath, cachebust)
+ return hmnurl.StaticUrl(filepath, []hmnurl.Q{{"v", cachebust}})
},
"staticnobust": func(filepath string) string {
- return fmt.Sprintf("A static file at %v", filepath)
+ return hmnurl.StaticUrl(filepath, nil)
},
"statictheme": func(theme string, filepath string) string {
- return fmt.Sprintf("A static file for the current theme at %v, busted with %v", filepath, cachebust)
+ return hmnurl.StaticThemeUrl(filepath, theme, []hmnurl.Q{{"v", cachebust}})
},
"staticthemenobust": func(theme string, filepath string) string {
- return fmt.Sprintf("A static file for the current theme at %v", filepath)
+ return hmnurl.StaticThemeUrl(filepath, theme, nil)
},
"url": func(url string) string {
- return "/" + url
+ return hmnurl.Url(url, nil)
+ },
+ "urlq": func(url string, query string) string {
+ absUrl := hmnurl.Url(url, nil)
+ return fmt.Sprintf("%s?%s", absUrl, query)
},
}
diff --git a/src/templates/types.go b/src/templates/types.go
index 4a5144b9..5875ac36 100644
--- a/src/templates/types.go
+++ b/src/templates/types.go
@@ -5,11 +5,24 @@ type BaseData struct {
CanonicalLink string
OpenGraphItems []OpenGraphItem
BackgroundImage BackgroundImage
- ProjectColor string
+ Project Project
Theme string
BodyClasses []string
}
+type Project struct {
+ Name string
+ Subdomain string
+ Color string
+
+ IsHMN bool
+
+ HasBlog bool
+ HasForum bool
+ HasWiki bool
+ HasLibrary bool
+}
+
type OpenGraphItem struct {
Property string
Name string
diff --git a/src/website/routes.go b/src/website/routes.go
index 5faab949..7cce8403 100644
--- a/src/website/routes.go
+++ b/src/website/routes.go
@@ -28,6 +28,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
routes.GET("/", routes.Index)
routes.GET("/project/:id", routes.Project)
routes.GET("/assets/project.css", routes.ProjectCSS)
+ routes.ServeFiles("/public/*filepath", http.Dir("public"))
return routes
}
@@ -42,10 +43,23 @@ This context should also provide a sub-logger with request fields so we can easi
which URLs are having problems.
*/
+// TODO: Make all these routes automatically pull general template data
+// TODO:
+
func (s *websiteRoutes) Index(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
err := templates.Templates["index.html"].Execute(rw, templates.BaseData{
- ProjectColor: "cd4e31",
- Theme: "dark",
+ Project: templates.Project{
+ Name: "Handmade Network",
+ Color: "cd4e31",
+
+ IsHMN: true,
+
+ HasBlog: true,
+ HasForum: true,
+ HasWiki: true,
+ HasLibrary: true,
+ },
+ Theme: "dark",
})
if err != nil {
panic(err)
@@ -80,6 +94,7 @@ func (s *websiteRoutes) ProjectCSS(rw http.ResponseWriter, r *http.Request, p ht
Theme: "dark",
}
+ rw.Header().Add("Content-Type", "text/css")
err := templates.Templates["project.css"].Execute(rw, templateData)
if err != nil {
logging.Error().Err(err).Msg("failed to generate project CSS")