diff --git a/public/android-icon-144x144.png b/public/android-icon-144x144.png
new file mode 100644
index 0000000..101f25a
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 0000000..6598164
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 0000000..2c73870
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 0000000..e3e167d
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 0000000..363ae89
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 0000000..f18359d
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 0000000..d928a1d
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 0000000..cbb2631
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 0000000..101f25a
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 0000000..395c7bf
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 0000000..68f9070
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 0000000..ef69c3d
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 0000000..a833697
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 0000000..363ae89
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 0000000..fe344c2
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 0000000..52ec936
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 0000000..52ec936
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 0000000..e1ea7b4
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 0000000..b769ca1
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 0000000..f18359d
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 0000000..68921fd
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 0000000..841809f
--- /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 0000000..03f3088
--- /dev/null
+++ b/public/logo_underscore.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/public/style.css b/public/style.css
index e88c6dd..ba65c40 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 0000000..739c4e8
--- /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 0000000..347800c
--- /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 0000000..acb0354
--- /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 0000000..46d2b7e
--- /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 0000000..db30a77
--- /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 0000000..894e9fe
--- /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 0000000..77a7af1
--- /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 0000000..62aaa3e
--- /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 0000000..46d2b7e
--- /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 0000000..b40cf2f
--- /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 ab18b76..b935cad 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 0000000..d2be1e7
--- /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 0000000..959c016
--- /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 cebc102..e1be899 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 7e685d8..84f5165 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 cbd07da..734c5ca 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 ce071e7..9748bc1 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 b3bdd81..9184fd6 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 4a5144b..5875ac3 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 5faab94..7cce840 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")