318ff45f727a

fold the navbar links behind a menu button on phones, wrapping them when the script is absent (v3.0.168)

time
2026-10-01 13:50 UTC · 1 week ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
mobile nav menu
parent
a4e1f5407345
3 files package.json+1 −1public/css/kepr.css+68 −1public/js/kepr.js+34 −1
package.json+1 −1
@@ -1,6 +1,6 @@11 {22 	"name": "kepr",3-	"version": "3.0.167",3+	"version": "3.0.168",44 	"private": true,55 	"type": "module",66 	"scripts": {

view file

public/css/kepr.css+68 −1
@@ -135,6 +135,9 @@135135 .nav-logout {136136 	margin: 0;137137 }138+.nav-toggle {139+	display: none;140+}138141 .nav-logout button {139142 	border: 0;140143 	padding: 0;@@ -1259,9 +1262,70 @@12591262 	body > nav {12601263 		padding: 20px 16px;12611264 	}1265+	/* Without the menu script the links wrap in the bar instead of running off it. */12621266 	.nav-links {1263-		gap: 18px;1267+		flex-wrap: wrap;1268+		justify-content: flex-end;1269+		gap: 8px 18px;1270+		min-width: 0;12641271 	}1272+	.nav--menu {1273+		position: relative;1274+	}1275+	.nav--menu .nav-toggle {1276+		display: flex;1277+		flex-direction: column;1278+		justify-content: center;1279+		gap: 4px;1280+		width: 36px;1281+		height: 36px;1282+		margin: -8px -8px -8px auto;1283+		padding: 8px;1284+		border: 0;1285+		background: none;1286+		color: var(--text-secondary);1287+		cursor: pointer;1288+	}1289+	.nav-toggle span {1290+		height: 1.5px;1291+		background: currentColor;1292+		transition:1293+			transform 0.2s ease,1294+			opacity 0.2s ease;1295+	}1296+	.nav--open .nav-toggle {1297+		color: var(--accent);1298+	}1299+	.nav--open .nav-toggle span:nth-child(1) {1300+		transform: translateY(5.5px) rotate(45deg);1301+	}1302+	.nav--open .nav-toggle span:nth-child(2) {1303+		opacity: 0;1304+	}1305+	.nav--open .nav-toggle span:nth-child(3) {1306+		transform: translateY(-5.5px) rotate(-45deg);1307+	}1308+	.nav--menu .nav-links {1309+		display: none;1310+	}1311+	.nav--menu.nav--open .nav-links {1312+		position: absolute;1313+		top: 100%;1314+		left: 0;1315+		right: 0;1316+		z-index: 10;1317+		display: flex;1318+		flex-direction: column;1319+		gap: 0;1320+		padding: 0 16px 8px;1321+		background: var(--bg);1322+		border-bottom: 1px solid var(--border);1323+		font-size: 0.8125rem;1324+	}1325+	.nav--open .nav-links > * {1326+		padding: 14px 0;1327+		border-top: 1px solid var(--border);1328+	}12651329 	main {12661330 		padding: 16px 16px 80px;12671331 	}@@ -2692,6 +2756,9 @@26922756 	.fv-overlay,26932757 	.repo-panel {26942758 		animation: none;2759+	}2760+	.nav-toggle span {2761+		transition: none;26952762 	}26962763 	.hex-col {26972764 		opacity: 0.12;

view file

public/js/kepr.js+34 −1
@@ -1,7 +1,7 @@11 // Kepr's browser islands (§18). Every page is complete without this file; it only adds the22 // ticker's pace, the drifting timeline, copy buttons, the inline panels, history's scrolling,33 // line-range highlighting, the Atlas panel, picker, filter and file viewer, the signed-in4-// editor, and the profile's glow.4+// editor, the phone nav menu, and the profile's glow.55 66 // Kepr 2's save kinds (canvas.js), first match wins: each dot's colour and strength.77 const KINDS = [@@ -644,6 +644,38 @@644644 	}645645 }646646 647+// On a phone the nav links fold behind a menu button (2026-10-01). The button only exists with648+// JavaScript, so without it the links stay in the bar; the CSS hides them only on `.nav--menu`.649+function navMenu() {650+	const nav = document.querySelector("body > nav");651+	const links = nav?.querySelector(".nav-links");652+	if (!links) return;653+	links.id = "nav-links";654+	const button = element("button", "nav-toggle");655+	button.type = "button";656+	button.setAttribute("aria-label", "menu");657+	button.setAttribute("aria-controls", "nav-links");658+	button.setAttribute("aria-expanded", "false");659+	button.append(element("span", ""), element("span", ""), element("span", ""));660+	const open = () => nav.classList.contains("nav--open");661+	const set = (opened) => {662+		nav.classList.toggle("nav--open", opened);663+		button.setAttribute("aria-expanded", String(opened));664+	};665+	button.addEventListener("click", () => set(!open()));666+	document.addEventListener("click", (event) => {667+		if (open() && !nav.contains(event.target)) set(false);668+	});669+	document.addEventListener("keydown", (event) => {670+		if (event.key !== "Escape" || !open()) return;671+		set(false);672+		button.focus();673+	});674+	nav.classList.add("nav--menu");675+	// Before the links, so tabbing from the open button reaches them next.676+	links.before(button);677+}678+647679 function element(tag, className, text) {648680 	const node = document.createElement(tag);649681 	node.className = className;@@ -948,6 +980,7 @@948980 atlasPicker();949981 repoEditor();950982 navbarLogin();983+navMenu();951984 lineRanges();952985 atlas();953986 hexGlow();

view file