also looking at this
fix(ui): bound the mobile menu to the visible viewport
#5
2 files
+24
-0
| @@ -110,6 +110,7 @@ a { color: inherit; text-decoration: none; } | ||
| 110 | 110 | .nav-menu-panel { |
| 111 | 111 | position: absolute; right: 0; top: calc(100% + 12px); z-index: 50; |
| 112 | 112 | 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; | |
| 113 | 114 | background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r); |
| 114 | 115 | box-shadow: 0 24px 60px rgba(0, 0, 0, 0.22); |
| 115 | 116 | } |
| @@ -304,6 +305,7 @@ a.cell:hover .cell-more { color: var(--accent); gap: 0.55rem; } | ||
| 304 | 305 | |
| 305 | 306 | /* responsive */ |
| 306 | 307 | @media (max-width: 900px) { |
| 308 | body:has(.nav-menu[open]) { overflow: hidden; } | |
| 307 | 309 | .hero { grid-template-columns: 1fr; gap: 2rem; padding-top: 2.5rem; } |
| 308 | 310 | .bento, .related, .cmp { grid-template-columns: 1fr; } |
| 309 | 311 | .metrics { grid-template-columns: 1fr 1fr; } |
| @@ -0,0 +1,22 @@ | ||
| 1 | import { describe, expect, test } from 'bun:test' | |
| 2 | import { readFileSync } from 'node:fs' | |
| 3 | import { join } from 'node:path' | |
| 4 | ||
| 5 | const ROOT = join(import.meta.dir, '../..') | |
| 6 | const 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. | |
| 11 | describe('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 | }) | |