diff --git a/README.md b/README.md
index 4f1b560..c6c02f8 100644
--- a/README.md
+++ b/README.md
@@ -48,10 +48,10 @@ curl -H "Authorization: Bearer $KEY" http://localhost:8080/api/users
The server serves a web UI at `/`: the incident queue, each incident's alerts
and timeline with every action (acknowledge, assign, snooze, note, resolve,
archive), who is on call, the alert feed, and an *Account* tab for your own
-password and the ntfy topic your pages go to. It is built for a phone first. On a phone it has a bottom tab bar and a sticky action
-bar, it follows the system's dark mode, and it can be added to the home screen.
-From 900px wide it switches to a sidebar with the queue and the incident side by
-side. Statistics remain in
+password and the ntfy topic your pages go to. It is built for a phone first. On a phone
+it navigates through a hamburger menu and has a sticky action bar, it follows the
+system's dark mode, and it can be added to the home screen. From 900px wide it switches
+to a sidebar with the queue and the incident side by side. Statistics remain in
[terdut-tui](https://github.com/yeniklas/terdut-tui) for now.
You sign in with a username and password. Users have no password until one is
diff --git a/internal/web/static/app.css b/internal/web/static/app.css
index d7e790f..c6f42c6 100644
--- a/internal/web/static/app.css
+++ b/internal/web/static/app.css
@@ -48,7 +48,11 @@
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--topbar-h: 52px;
- --tabbar-h: 58px;
+ /* No bottom tab bar on any breakpoint any more — mobile uses the hamburger
+ menu in the topbar, desktop the sidebar — so this stays 0. Kept as a
+ variable rather than deleted since .view, .toast and .nav's own height
+ calc still read it. */
+ --tabbar-h: 0px;
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
@@ -196,7 +200,13 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
-webkit-backdrop-filter: saturate(1.4) blur(12px);
border-bottom: 1px solid var(--border);
}
-.topbar-title { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; }
+.topbar-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
+.topbar-title {
+ font-size: 18px; font-weight: 700; letter-spacing: -0.01em;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
+}
+#menu-btn { position: relative; }
+.nav-badge.menu-btn-badge { top: 2px; left: auto; right: 2px; }
.open-pill {
display: inline-flex; align-items: center; gap: 6px;
@@ -209,15 +219,12 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
.open-pill.has-triggered::before { background: var(--crit); }
.open-pill.all-acked::before { background: var(--warn); }
-/* Bottom tab bar on phones. */
+/* Hidden on phones — mobile navigates through the hamburger menu in the
+ topbar instead (see #menu-btn / openNavMenu in app.js). Reappears as the
+ left sidebar from 900px, where the desktop block below redeclares display. */
.nav {
+ display: none;
position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
- /* One column per link, however many there are. This was repeat(4, 1fr) when
- there were four tabs; Team and Admin arriving pushed six items into four
- columns, which on a phone is how they stopped fitting. Auto columns mean
- the next tab cannot break the row either — and Admin is only rendered for
- an administrator, so the count genuinely varies between viewers. */
- display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
height: calc(var(--tabbar-h) + var(--safe-bottom));
padding-bottom: var(--safe-bottom);
background: color-mix(in srgb, var(--surface) 92%, transparent);
@@ -239,16 +246,6 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
}
.nav-link svg { width: 24px; height: 24px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
-/* Narrow phones, where six tabs each get about 55-65px. Tightening is enough:
- the widest label, "On-call", is about 38px at this size, so nothing has to
- be hidden. The ellipsis above is the backstop if a future tab is named
- something longer. */
-@media (max-width: 420px) {
- .nav-link { font-size: 10px; gap: 1px; }
- .nav-link svg { width: 21px; height: 21px; }
- .nav-badge { left: calc(50% + 4px); min-width: 16px; height: 16px; font-size: 10px; line-height: 16px; }
-}
-
.nav-link[aria-current="page"] { color: var(--accent); }
.nav-badge {
position: absolute; top: 6px; left: calc(50% + 6px);
@@ -601,8 +598,6 @@ kbd {
/* ---------- desktop ---------- */
@media (min-width: 900px) {
- :root { --tabbar-h: 0px; }
-
.app { display: grid; grid-template-columns: 220px 1fr; height: 100dvh; }
.nav {
diff --git a/internal/web/static/index.html b/internal/web/static/index.html
index 01960bb..2174927 100644
--- a/internal/web/static/index.html
+++ b/internal/web/static/index.html
@@ -108,7 +108,13 @@
Queue
+