diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a82d1c..d1f9833 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version `/mail-queue` (was `/queue`), System log at `/system-log` (was `/logtail`). Bookmarks to the old paths stop working. +- panel: each entry in the navigation bar now carries an icon beside its label, + so the bar is scannable at a glance instead of a row of similar-length words. + The icons are inline SVG drawn in the entry's own colour — no extra request, + no exemption from the panel's Content-Security-Policy — and are hidden from + screen readers, which still announce the label alone. + ## [0.2.0] - 2026-08-03 - panel: every authenticated page now ends with the running version diff --git a/internal/web/static/panel.css b/internal/web/static/panel.css index e1c4b8d..123964a 100644 --- a/internal/web/static/panel.css +++ b/internal/web/static/panel.css @@ -89,19 +89,29 @@ details form { margin-top: 0.6rem; } /* Panel navigation: rendered once from the layout, so it is present on every authenticated page without each content template having to include it. */ .nav { - display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; + display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 0.4rem 1rem; margin-bottom: 1.2rem; padding-bottom: 0.6rem; border-bottom: 1px solid #e2e5e9; } @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: baseline; } -.nav .links a, .nav .links [aria-current] { padding: 0.2rem 0.5rem; border-radius: 6px; } -.nav .links [aria-current] { +.nav .links, .nav .session { display: flex; flex-wrap: wrap; gap: 0.2rem 0.9rem; align-items: center; } +/* 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 + page like the others and would otherwise be the one bare word in the bar. */ +.nav a, .nav [aria-current] { + display: inline-flex; align-items: center; gap: 0.4rem; + padding: 0.2rem 0.5rem; border-radius: 6px; +} +.nav [aria-current] { font-weight: 600; color: #1b1f24; background: #e6ebf5; box-shadow: inset 0 -2px 0 #2563eb; } @media (prefers-color-scheme: dark) { - .nav .links [aria-current] { color: #e6e8eb !important; background: #22303f !important; } + .nav [aria-current] { color: #e6e8eb !important; background: #22303f !important; } } +/* The icons draw in the entry's own colour, so the active entry's darker text + and a link's blue carry through without a second rule per state. */ +.nav .icon { width: 1rem; height: 1rem; flex: none; } /* Status badges: one vocabulary (ok/warn/error/unknown) shared by the server status page and the per-domain DNS checks, so a colour means the same thing everywhere. The class suffix is the check's own status value. */ diff --git a/internal/web/templates/layout.html b/internal/web/templates/layout.html index 808a40f..da2d362 100644 --- a/internal/web/templates/layout.html +++ b/internal/web/templates/layout.html @@ -36,19 +36,33 @@ {{define "nav"}} {{end}} + +{{/* Navigation icons. Inline SVG rather than an icon font or sprite file: they + inherit the link's colour through currentColor, cost no extra request, and + need no exemption from the panel's "default-src 'self'" policy. Each is + aria-hidden because the entry's own text is already the accessible name; + the icon is a landmark for the eye, not a second label. Kept as separate + templates so the nav above stays one readable line per page. */}} +{{define "icon-status"}}{{end}} +{{define "icon-domains"}}{{end}} +{{define "icon-deliveries"}}{{end}} +{{define "icon-mail-queue"}}{{end}} +{{define "icon-system-log"}}{{end}} +{{define "icon-backup"}}{{end}} +{{define "icon-account"}}{{end}} diff --git a/internal/web/templates_test.go b/internal/web/templates_test.go index 7db70c0..0d1083e 100644 --- a/internal/web/templates_test.go +++ b/internal/web/templates_test.go @@ -105,7 +105,9 @@ func TestNavMarksActivePage(t *testing.T) { t.Fatalf("execute nav: %v", err) } out := buf.String() - if !strings.Contains(out, `Mail queue`) { + // The label is checked apart from the opening tag because each entry now + // carries an icon between the two. + if !strings.Contains(out, ``) || !strings.Contains(out, `Mail queue`) { t.Errorf("active page is not marked:\n%s", out) } if strings.Contains(out, `href="/mail-queue"`) { @@ -129,7 +131,7 @@ func TestNavLeadsWithStatusAndPointsDomainsAtItsOwnPath(t *testing.T) { t.Fatalf("execute nav: %v", err) } out := buf.String() - if !strings.Contains(out, `Status`) { + if !strings.Contains(out, ``) || !strings.Contains(out, `Status`) { t.Errorf("the status page is not marked active:\n%s", out) } if !strings.Contains(out, `href="/domains"`) {