From 747861351a14c9dd9bee0c6118c92e76a53a1af3 Mon Sep 17 00:00:00 2001 From: Ekremzhan Date: Tue, 13 Oct 2026 00:32:12 -0400 Subject: [PATCH 1/8] Increase desktop room detail spacing --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index 65769ff..be9b3ea 100644 --- a/index.html +++ b/index.html @@ -1015,6 +1015,7 @@ } body:is(.has-bar) #sheet { bottom: calc(var(--bar-h, 0px) + 1rem); } #sheet .handle { display: none; } + #room h2 { padding-top: 1.5rem; } #back, #menu, body.waying #menu { left: 1.5rem; } .m-panel, body.waying .m-panel { left: 1.5rem; } #way-plate { From 91875835ae7ffdb54aa3d0f3c7ecad73031640ec Mon Sep 17 00:00:00 2001 From: Ekremzhan Date: Tue, 13 Oct 2026 00:32:28 -0400 Subject: [PATCH 2/8] Keep desktop room navigation consistent --- README.md | 2 +- index.html | 34 ++++++++++++++++++++++------ js/app.js | 42 ++++++++++++++++++++++------------- scripts/test/screens.test.mjs | 14 ++++++++---- sw.js | 5 +++-- 5 files changed, 67 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 382c883..a32eb87 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ sits at the bottom, then **Add to Home Screen**. iOS 26 defaults to that Compact layout, which is why the app offers you both. On Android, Chrome has **Install app** in its menu. -Installed, the whole app is 164 KB of shell and about 112 KB of schedule, gzipped, and +Installed, the whole app is 165 KB of shell and about 112 KB of schedule, gzipped, and none of it is fetched again to answer a question. Turn the network off, open it, and it still ranks rooms. That matters because the moment you want it most is the moment you are in a basement with one bar. diff --git a/index.html b/index.html index be9b3ea..6674218 100644 --- a/index.html +++ b/index.html @@ -537,7 +537,7 @@ "in the 3rd picture the hamburger icon like the 3 lines is different, make image 3 match image 2". The drop shadow is what the disc was for, and it works over a photograph and a night map alike. */ - body.carding #menu, body.waying #menu { + body.carding #menu, body.waying #menu, body.rooming #menu { border: 0; background: none; padding: 0; min-width: 60px; min-height: 60px; width: 60px; height: 60px; top: calc(var(--safe-t) + 1.1rem); @@ -547,14 +547,17 @@ hamburger sat at x=17 while the 512px column started at x=694. */ left: max(1.1rem, env(safe-area-inset-left, 0px), calc(50% - var(--col) / 2 + .5rem)); } - body.carding #menu .ico, body.waying #menu .ico { + body.carding #menu .ico, body.waying #menu .ico, body.rooming #menu .ico { width: 40px; height: 40px; stroke-width: 2.1; filter: drop-shadow(0 1px 3px rgba(0,0,0,.9)) drop-shadow(0 0 10px rgba(0,0,0,.55)); } - body.carding .m-panel, body.waying .m-panel { + body.carding .m-panel, body.waying .m-panel, body.rooming .m-panel { top: calc(var(--safe-t) + 4.6rem); left: max(1.1rem, env(safe-area-inset-left, 0px), calc(50% - var(--col) / 2 + .5rem)); } + /* The room keeps the established fixed Back arrow on a phone. Its menu and + panel-local Back control are the desktop pair below. */ + body.rooming #menu { display: none; } /* The way: the map with the room lit on it, one plate naming the room, and the ranking peeked underneath. Enes on the schedule, "the list of other classes @@ -776,7 +779,12 @@ .browse-nav { margin: 0; padding: .7rem 1.1rem 0; } /* --- the room screen, a second pane in the same sheet --- */ - #room h2 { margin: 0; padding: .1rem 1.1rem .45rem; font-size: 1.05rem; letter-spacing: -.01em; } + .room-head { position: relative; padding: 0 1.1rem .45rem; } + #room .room-head h2 { + margin: 0; padding: .1rem 0 0; + font-size: 1.05rem; letter-spacing: -.01em; + } + .room-back { display: none; } .claim { margin: 0; padding: 0 1.1rem .35rem; font-weight: 650; } .claim .sub { display: block; font-weight: 400; color: var(--dim); font-size: .86rem; } .facts { @@ -1015,13 +1023,25 @@ } body:is(.has-bar) #sheet { bottom: calc(var(--bar-h, 0px) + 1rem); } #sheet .handle { display: none; } - #room h2 { padding-top: 1.5rem; } - #back, #menu, body.waying #menu { left: 1.5rem; } - .m-panel, body.waying .m-panel { left: 1.5rem; } + #back, #menu, body.waying #menu, body.rooming #menu { left: 1.5rem; } + /* The menu owns a 60px target while Back owns a 44px target. Offset Back + by half that 16px difference so their glyphs keep the same corner centre + when moving between desktop screens. */ + #back { + left: calc(1.5rem + 8px); + top: calc(var(--safe-t) + 1.1rem + 8px); + } + .m-panel, body.waying .m-panel, body.rooming .m-panel { left: 1.5rem; } #way-plate { left: 1.5rem; transform: none; width: min(30rem, calc(60vw - 3rem)); max-width: none; } + body.rooming #back { display: none; } + body.rooming #menu { display: flex; } + #room .room-head { padding-top: 1.5rem; padding-right: 4.8rem; } + #room .room-back { + display: flex; position: absolute; top: 1.5rem; right: 1.1rem; + } body.carding #sheet { left: 0; right: 0; top: 1.5rem; bottom: 1.5rem; diff --git a/js/app.js b/js/app.js index 4c36a8d..f1354a5 100644 --- a/js/app.js +++ b/js/app.js @@ -2664,7 +2664,12 @@ function roomHtml(id) {

` : ''; - return `

${esc(bname)} ${esc(room.n ?? '')}

+ return `
+ +

${esc(bname)} ${esc(room.n ?? '')}

+

${esc(claim.head)}${claim.sub ? `${esc(claim.sub)}` : ''}

${facts.join('')}

${acts} @@ -2943,16 +2948,18 @@ function showPane(name) { // no rounded top, no border and no grip there. index.html hangs those off the // body rather than the pane, because the sheet is what has to lose them. document.body.classList.toggle('carding', name === 'card'); + document.body.classList.toggle('rooming', name === 'room'); document.body.classList.remove('waying'); for (const id of PANES) $(id).hidden = id !== name; $('find').hidden = name !== 'pick'; $('origin').hidden = !originBarOn(name); $('ask').hidden = true; $('sheet').hidden = false; - // One corner, two controls, never both. The card is a photograph and gets the - // menu; everything else is a panel or a map and keeps the arrow. + // The card and desktop room screen keep the menu over the map. The room's + // back arrow lives in its panel on desktop; CSS keeps this global arrow on + // mobile, where the room still fills the sheet. $('back').hidden = name === 'card'; - $('menu').hidden = name !== 'card'; + $('menu').hidden = name !== 'card' && name !== 'room'; closeMenu(); document.body.classList.remove('asking'); const arrived = state.screen !== name; @@ -2978,7 +2985,7 @@ function showAsk() { $('way').hidden = true; $('ask').hidden = false; document.body.classList.add('asking'); - document.body.classList.remove('carding', 'waying'); + document.body.classList.remove('carding', 'rooming', 'waying'); $('sheet').hidden = true; $('back').hidden = true; $('menu').hidden = true; @@ -3084,6 +3091,8 @@ function showAbout() { // heading and re-snaps the sheet to rest, and a sheet that jumps back to rest // under a reader is worse than a stale number. function wireRoom(id) { + $('room-back').setAttribute('aria-label', roomBackLabel()); + $('room-back').onclick = () => history.back(); attachBearing(id); // A week either way, and no clamp. Past the term's own bounds the grid comes // back empty and the sentence over it says the schedule does not reach there. @@ -3164,16 +3173,7 @@ function showRoom(id, { keepDay = false } = {}) { // Back pops to the entry underneath this one, and openRoom wrote down which // screen that is. Outside scheduled hours the room screen opens over the // buildings screen, and the label promised a room list that is not there. - $('back').setAttribute( - 'aria-label', - history.state?.from === 'near' - ? 'Back to the nearest buildings' - : history.state?.from === 'browse' - ? 'Back to browse buildings' - : history.state?.from === 'way' - ? 'Back to the way' - : 'Back to the room list', - ); + $('back').setAttribute('aria-label', roomBackLabel()); // frame() below runs only when the room is one of the ranked rows. Opened // from a link or out of hours it is not, and the footprint still has to light. @@ -3192,6 +3192,16 @@ function showRoom(id, { keepDay = false } = {}) { } } +function roomBackLabel() { + return history.state?.from === 'near' + ? 'Back to the nearest buildings' + : history.state?.from === 'browse' + ? 'Back to browse buildings' + : history.state?.from === 'way' + ? 'Back to the way' + : 'Back to the room list'; +} + // Where the room is: the map with the footprint lit, an arrow to it, one plate // naming it, and the ranking peeked underneath with that room's row lit. // @@ -3233,7 +3243,7 @@ function showWay(id, view = null) { $('menu').hidden = false; $('way').hidden = false; closeMenu(); - document.body.classList.remove('asking', 'carding'); + document.body.classList.remove('asking', 'carding', 'rooming'); // The menu is this screen's only chrome, and it is the same button it was on // the card a tap ago, so it is drawn the same: no disc, 40px in a 60px target. document.body.classList.add('waying'); diff --git a/scripts/test/screens.test.mjs b/scripts/test/screens.test.mjs index f080df0..a922368 100644 --- a/scripts/test/screens.test.mjs +++ b/scripts/test/screens.test.mjs @@ -1283,9 +1283,11 @@ test('the back button on the room screen names the pane back lands on', () => { // so separate matches all hold with the arms swapped, which is the reported // defect back again. Three arms now: the way opens rooms too, and it is not // the room list. - const show = bodyOf('showRoom'); - assert.match(show, /from === 'near'\s*\?\s*'Back to the nearest buildings'/); - assert.match(show, /from === 'way'\s*\?\s*'Back to the way'\s*:\s*'Back to the room list'/); + const label = bodyOf('roomBackLabel'); + assert.match(label, /from === 'near'\s*\?\s*'Back to the nearest buildings'/); + assert.match(label, /from === 'way'\s*\?\s*'Back to the way'\s*:\s*'Back to the room list'/); + assert.match(bodyOf('roomHtml'), /id="room-back"/); + assert.match(bodyOf('wireRoom'), /\$\('room-back'\)\.onclick = \(\) => history\.back\(\)/); }); // ---- the duration, and where the list says it @@ -1512,7 +1514,7 @@ test('neither screen without a back arrow is a dead end', () => { // the arrow left. const pane = bodyOf('showPane'); assert.match(pane, /\$\('back'\)\.hidden = name === 'card'/); - assert.match(pane, /\$\('menu'\)\.hidden = name !== 'card'/); + assert.match(pane, /\$\('menu'\)\.hidden = name !== 'card' && name !== 'room'/); const way = bodyOf('showWay'); assert.match(way, /\$\('back'\)\.hidden = true/); assert.match(way, /\$\('menu'\)\.hidden = false/); @@ -1526,6 +1528,10 @@ test('neither screen without a back arrow is a dead end', () => { // A disclosure, not a menu: role="menu" promises arrow keys move between the // items, and the app does not implement that. const css = readFileSync(join(ROOT, 'index.html'), 'utf8'); + // The desktop hamburger has a 60px target and Back has a 44px target. Back + // needs half that 16px difference on both axes for the two glyph centres to + // occupy the same corner when one screen replaces the other. + assert.match(css, /#back \{\s*left:\s*calc\(1\.5rem \+ 8px\);\s*top:\s*calc\(var\(--safe-t\) \+ 1\.1rem \+ 8px\);\s*\}/); assert.match(css, /