What the frame does below 820px
The frame is a desktop idea — two rails 1320px apart, notches docked to them, a page locked to the viewport. None of that survives a phone unchanged, and pretending otherwise is how a site ends up with a 1px horizontal scroll and a footer nobody can reach. This page says what each piece becomes.
Breakpoint ladder
Five breakpoints, and each exists for a structural reason rather than for a device. Nothing in the system keys off "tablet" or "phone".
| Width | What changes | Why |
|---|---|---|
| 1320px | Side margins reach zero | Not a media query — the frame simply fills the viewport once it is narrower than --maxw. The rails become the window edges. |
| 920px | Two-column layouts become one | Card grids drop to two across; the spec dialog's paired blocks stack and swap their vertical rule for a horizontal one. |
| 820px | Desktop nav → drawer | The popovers are hover-driven. On a touch device they either never open or open and trap. Replaced wholesale, not restyled. |
| 760px | Notches go; grids go single-column; the viewport lock is dropped | Below the frame width there is no rail to dock to, and 100vh stops being trustworthy. |
| 560px | Gutter tightens to 1.1rem; stat rows go single-column; dialogs go full-screen | Small-phone. A four-across stat row at 360px gives each figure 70px, which is not a figure. |
| pointer: coarse | Hit areas grow to 44px | A capability query, not a width one — a 400px window on a desktop is still a mouse, and a 1024px tablet is still a finger. |
The drawer
Two panes ride one track. The root pane lists the sections; choosing one slides the track left rather than opening a nested popover, and the breadcrumb at the top names where you are. Back slides it home. It is a second screen, not a menu hanging off the header.
<header>. header.nav carries backdrop-filter, which makes it a containing block for position: fixed descendants. A fixed drawer nested inside it resolves top/bottom against the 64px header box and collapses to zero height. It looks like the drawer "did not open".body.nav-drawer-open { overflow: hidden }. Without it the page behind scrolls under the drawer and the reader loses their place on close.journeymro.js handles this, along with Escape and closing on any real navigation.<header class="nav">
<div class="nav-inner">
<a href="/" class="wordmark">…</a>
<button class="nav-toggle" data-drawer-toggle aria-expanded="false" aria-controls="drawer">
<svg class="nav-toggle-bars icon" …><path d="M4 7h16M4 12h16M4 17h16"/></svg>
<svg class="nav-toggle-x icon" …><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
<div class="nav-right">…desktop nav, display:none below 820…</div>
</div>
</header>
<!-- SIBLING of header -->
<div class="nav-drawer" id="drawer" hidden>
<div class="nav-drawer-crumb">
<button class="nav-drawer-back" data-drawer-back hidden>…</button>
<nav class="nav-drawer-trail">
<span>Menu</span>
<span class="nav-drawer-crumb-sep" data-drawer-sep hidden>/</span>
<span class="nav-drawer-crumb-leaf" data-drawer-leaf></span>
</nav>
</div>
<div class="nav-drawer-track">
<div class="nav-drawer-page">
<button class="nav-drawer-row" data-drawer-section="services">
<span class="nav-drawer-row-label">Services</span>…
</button>
</div>
<div class="nav-drawer-page" data-drawer-sub>
<div data-section-panel="services" hidden>…</div>
</div>
</div>
</div>Touch targets
44px is the floor, not the target. The system applies it under @media (pointer: coarse) — a capability query, so a narrow desktop window keeps its compact controls and a large tablet gets the bigger ones.
- The burger is a 44px square with a 22px icon — the padding is the target, and it keeps the icon optically centred against the wordmark on the other rail.
- Drawer rows are 56px minimum, full bleed. A row is easier to hit than a word.
-webkit-tap-highlight-color: transparenteverywhere, with a real:activestate instead. The default blue flash is off-palette.- Never rely on hover for anything a touch user must reach. If a control only appears on hover, it does not exist on a phone.
Tables on a phone
The common advice — reflow each row into a stack of labelled pairs — is wrong here. A table exists so values can be compared down a column. Turn it into a stack of cards and you have destroyed the only reason it was a table.
Keep the table a table and let it scroll horizontally inside .table-wrap. Below 760px the wrapper bleeds to the gutter edges so the scrollable area is the full screen width.
Reflow rows into cards. Comparison dies, and the reader gets six screens of the same five labels.
Drop columns instead. Decide which two or three actually answer the question on a phone, and hide the rest behind a row tap.
Shrink the type to fit. 0.8rem is already the floor; below that the tabular figures stop being scannable.
| Part | On hand | ROP | Value | Status |
|---|---|---|---|---|
| …118-22 | 1,184 | 860 | $412,900 | OK |
| …206-08 | 42 | 180 | $38,400 | Low |
| …311-14 | 960 | 640 | $74,100 | OK |
| …455-91 | 18 | 24 | $2,140 | Low |
| Part | Cover | Short |
|---|---|---|
| Seal, mech 80 4400-206-08 | −138 | |
| Coupling, flex 4400-502-33 | −12 | |
| Belt, drive 4R 4400-455-91 | −6 |
Locked pages
100vh is a lie on a phone. Mobile browsers size vh against the viewport without the address bar, then shrink the visible area when the bar is showing. A page locked to calc(100vh − 130px) either clips its own footer or grows a second scrollbar as the bar hides and shows.So the lock is dropped below 760px and the page scrolls normally. This is already in journeymro.css — there is nothing to add per page:
@media (max-width: 760px) {
.page-main { min-height: 0; max-height: none; overflow: visible; }
.page-scroll { overflow: visible; }
}If a screen genuinely must fill the viewport on a phone — a map, a full-bleed chart — use 100dvh rather than 100vh, and accept that the layout will resize as the address bar moves.
Forms
- 16px minimum on inputs. iOS Safari zooms the page on focus for anything smaller, and it never zooms back.
.msg-inputis 0.95rem — check any override you add. - One field per row below 560px. The paired name/phone row in the site's contact form stacks; two 140px fields side by side is two fields nobody can type in.
- Set
inputmodeandautocomplete.inputmode="numeric"for part numbers and quantities,type="email"for email — the keyboard is a design decision. - Keep the submit button in the flow, not fixed. A fixed bar at the bottom fights the on-screen keyboard on both platforms.
Viewport & safe areas
<meta name="viewport" content="width=device-width, initial-scale=1.0">No maximum-scale and no user-scalable=no — pinch zoom is an accessibility feature, and disabling it to protect a layout means the layout is wrong.
For anything pinned to the bottom of the screen on a notched device, pad by the inset rather than by a guess:
.bottom-dock {
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}overflow-x: hidden is set on body as a backstop, but it hides the symptom rather than the cause. If a page scrolls sideways on a phone, something is wider than the viewport — usually a table without .table-wrap, a fixed-width SVG, or a white-space: nowrap heading.
Checklist
Run this at 375px before calling a screen done.
- No horizontal scroll anywhere on the page — check with the table filtered to its widest row.
- The drawer opens, slides to a section, comes back, and closes on navigation and on Escape.
- Both notches are gone and the arrow keys do nothing.
- The footer is reachable and the page does not clip at the bottom as the address bar hides.
- Every tap target is at least 44px, including icon-only buttons.
- No control is reachable only by hover.
- Focusing an input does not zoom the page.
- Charts keep their reserved aspect ratio and do not overflow their panel.
- The light theme still reads outdoors — check the hairlines have not disappeared.