ReviewOS

also looking at this

loghqorg/loghq

fix(ui): bound the mobile menu to the visible viewport

#5
Merged glennmichael123 wants to merge fix/mobile-nav into main
2 files +24 -0
public/marketing.cssmodified+2-0
Changes to public/marketing.css
@@ -110,6 +110,7 @@ a { color: inherit; text-decoration: none; }
110110.nav-menu-panel {
111111 position: absolute; right: 0; top: calc(100% + 12px); z-index: 50;
112112 display: flex; flex-direction: column; gap: 0.25rem; min-width: 232px; padding: 0.6rem;
113 max-height: calc(100dvh - 92px); overflow-y: auto; overscroll-behavior: contain;
113114 background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r);
114115 box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22);
115116}
@@ -304,6 +305,7 @@ a.cell:hover .cell-more { color: var(--accent); gap: 0.55rem; }
304305
305306/* responsive */
306307@media (max-width: 900px) {
308 body:has(.nav-menu[open]) { overflow: hidden; }
307309 .hero { grid-template-columns: 1fr; gap: 2rem; padding-top: 2.5rem; }
308310 .bento, .related, .cmp { grid-template-columns: 1fr; }
309311 .metrics { grid-template-columns: 1fr 1fr; }
tests/unit/mobile-layout.test.tsadded+22-0
Changes to tests/unit/mobile-layout.test.ts
@@ -0,0 +1,22 @@
1import { describe, expect, test } from 'bun:test'
2import { readFileSync } from 'node:fs'
3import { join } from 'node:path'
4
5const ROOT = join(import.meta.dir, '../..')
6const marketing = readFileSync(join(ROOT, 'public/marketing.css'), 'utf8')
7
8// The phone menu lists every marketing destination in one column, which is
9// taller than an 812px viewport. The panel has to scroll inside the viewport
10// and the page behind it has to stay put, or the last links are unreachable.
11describe('mobile layout contracts', () => {
12 test('marketing menu is bounded by the visible mobile viewport', () => {
13 const panel = marketing.match(/\.nav-menu-panel\s*\{[^}]*\}/)?.[0] ?? ''
14 expect(panel).toContain('max-height: calc(100dvh - 92px)')
15 expect(panel).toContain('overflow-y: auto')
16 expect(panel).toContain('overscroll-behavior: contain')
17 })
18
19 test('opening the marketing menu locks the page behind it', () => {
20 expect(marketing).toContain('body:has(.nav-menu[open]) { overflow: hidden; }')
21 })
22})