diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd0ff5..247e530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,27 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +### Added + +- The project's mark is now in use rather than only on file. The README opens + with the full stamp; the panel carries the compact one at the left of its + navigation bar, linking to the status page, and the full one above the card + on the two pages that have no navigation — sign-in and first-run setup. The + browser tab icon changes with it, from the earlier envelope drawing to the + stamp's small-size variant, so the tab, the panel and the README are one + identity. The four brand files in `docs/assets/` had their wordmark converted + from live text to outlines: they were set in IBM Plex Sans, which is not + installed on the machines that render them, and the light/semibold contrast + between *Self* and *Post* — the whole of the mark — collapsed into whatever + fallback the viewer happened to have. + ### Changed +- panel: the sign-in and setup pages are now a column the width of their own + card. Both are a single narrow card, which centred itself while the heading + above it stayed at the panel's left edge; adding the mark would have made + that three alignments on a page with four elements. + - panel: the three monitoring pages — Deliveries, Mail queue, System log — are now laid out wider (64rem against the 48rem the rest of the panel keeps). They carry data rather than prose: the send-log's seven columns had no room diff --git a/README.md b/README.md index cccca31..d09ddd3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![SelfPost](docs/assets/selfpost-stamp.svg) + # SelfPost Self-hosted outbound SMTP relay with a web control panel, shipped as a single diff --git a/internal/web/handlers_auth.go b/internal/web/handlers_auth.go index 0682e26..569c99a 100644 --- a/internal/web/handlers_auth.go +++ b/internal/web/handlers_auth.go @@ -98,6 +98,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { // a failing form. s.render(w, http.StatusOK, "login", map[string]any{ "Title": "SelfPost — Sign in", + "Active": "login", "SetupHint": true, }) return @@ -115,9 +116,13 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { } func (s *Server) renderLogin(w http.ResponseWriter, status int, formErr string) { + // Active names the page for the layout even though no navigation is drawn + // here: it is what puts page-login on
, which the stylesheet uses to + // give the signed-out pages a column the width of their own card. s.render(w, status, "login", map[string]any{ - "Title": "SelfPost — Sign in", - "Error": formErr, + "Title": "SelfPost — Sign in", + "Active": "login", + "Error": formErr, }) } diff --git a/internal/web/handlers_setup.go b/internal/web/handlers_setup.go index 020aeeb..1505f75 100644 --- a/internal/web/handlers_setup.go +++ b/internal/web/handlers_setup.go @@ -42,9 +42,10 @@ func (s *Server) handleSetup(w http.ResponseWriter, r *http.Request) { func (s *Server) renderSetupForm(w http.ResponseWriter, status int, token, formErr string) { s.render(w, status, "setup", map[string]any{ - "Title": "SelfPost — Create administrator", - "Token": token, - "Error": formErr, + "Title": "SelfPost — Create administrator", + "Active": "setup", + "Token": token, + "Error": formErr, }) } diff --git a/internal/web/static/favicon.png b/internal/web/static/favicon.png index aa319b8..cd7cc89 100644 Binary files a/internal/web/static/favicon.png and b/internal/web/static/favicon.png differ diff --git a/internal/web/static/favicon.svg b/internal/web/static/favicon.svg new file mode 100644 index 0000000..9e714db --- /dev/null +++ b/internal/web/static/favicon.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/internal/web/static/logo-compact.svg b/internal/web/static/logo-compact.svg new file mode 100644 index 0000000..3c03928 --- /dev/null +++ b/internal/web/static/logo-compact.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/internal/web/static/logo.svg b/internal/web/static/logo.svg new file mode 100644 index 0000000..cb46ba3 --- /dev/null +++ b/internal/web/static/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/internal/web/static/panel.css b/internal/web/static/panel.css index b4d54ba..2cede26 100644 --- a/internal/web/static/panel.css +++ b/internal/web/static/panel.css @@ -17,6 +17,13 @@ body { } main { max-width: 42rem; margin: 0 auto; } h1 { font-size: 1.4rem; margin: 0 0 1rem; } +/* The full mark, on the two pages that have no navigation bar to carry the + compact one. It takes the column's width so its edges line up with the card + below it, capped at that column's own 24rem; height stays automatic because + the stamp's proportions are part of the mark and it is never scaled unevenly. + The subtitle stops resolving below 280px, which a 320px viewport still clears + once the body's padding is taken off. */ +.mark { display: block; width: 100%; max-width: 24rem; height: auto; margin-bottom: 1.4rem; } .card { background: #fff; border: 1px solid #e2e5e9; border-radius: 10px; padding: 1.5rem; margin: 0 auto; @@ -58,6 +65,13 @@ main { max-width: 48rem; } every second line. They get a wider measure; every other page keeps the narrow one. The class comes from the layout, which stamps the page name onto
. */ main.page-deliveries, main.page-mail_queue, main.page-system_log { max-width: 64rem; } +/* The signed-out pages are a single card and nothing else, and .card.narrow + centres itself inside whatever holds it — so at the panel's usual width the + card floated in the middle while the mark and the heading stayed at the far + left, three alignments on a page with four elements. Narrowing the column to + the card's own width makes the three line up and puts the block as a whole in + the middle of the page. */ +main.page-login, main.page-setup { max-width: 24rem; } .card + .card { margin-top: 1.2rem; } .flash { background: #ecfdf3; border: 1px solid #abefc6; color: #067647; padding: 0.7rem 1rem; border-radius: 8px; margin-bottom: 1.2rem; } @media (prefers-color-scheme: dark) { .flash { background: #0d2818 !important; border-color: #1a5336 !important; color: #75d99b !important; } } @@ -149,9 +163,21 @@ details form { margin-top: 0.6rem; } } @media (prefers-color-scheme: dark) { .nav { border-color: #2b3138 !important; } } .nav .links, .nav .session { display: flex; flex-wrap: wrap; gap: 0.2rem 0.9rem; align-items: center; } -/* Pushed to the far edge so the two rows read as two blocks rather than as one +/* The top row: mark at one edge, session block at the other. Splitting them to + the edges is what makes the two rows read as two blocks rather than as one ragged list that happened to wrap. */ +.nav .top { display: flex; align-items: center; justify-content: space-between; gap: 0.9rem; } .nav .session { justify-content: flex-end; } +/* The mark is a link, but not one of the bar's entries: it takes none of the + padding and rounding the entry rule below applies, so its own edge lines up + with the page rather than sitting half a step inside it. The stamp carries + about 4px of field inside the file at this size, which is what puts it level + with the icons of the row underneath. */ +.nav .brand { padding: 0; } +/* width/height are on the element too, so the row reserves the space before the + SVG has loaded; these keep the ratio if the box is ever squeezed. The stamp's + proportions are part of the mark — it is never scaled unevenly. */ +.nav .brand img { display: block; width: 110px; height: auto; } /* Each entry pairs an icon with its label, so the entry itself is a flex row rather than a run of text — that is also why the bar centres its items instead of aligning them on the text baseline. Account is included: it is a diff --git a/internal/web/templates/layout.html b/internal/web/templates/layout.html index b6ce836..5b3249c 100644 --- a/internal/web/templates/layout.html +++ b/internal/web/templates/layout.html @@ -4,7 +4,13 @@ {{.Title}} - +{{/* The tab icon is the stamp's small-size variant — the initials rather than + the two-line wordmark, which stops resolving below 32px (the thresholds are + recorded in docs/assets/selfpost-proof.html). The PNG is declared first and + the SVG second: a browser takes the last icon whose type it understands, so + vector wins wherever it is supported and the raster catches the rest. */}} + + {{/* Unless told otherwise, htmx injects a stylesheet element of its own into the head for the request-indicator classes. The panel uses no @@ -40,15 +46,20 @@ .User and get no nav. */}} {{define "nav"}}