From 2019ce6acf5b4b5be86f6a151f43c8a6c35a5048 Mon Sep 17 00:00:00 2001 From: Mandyx22 <1915537307@qq.com> Date: Fri, 24 Jul 2026 16:19:21 -0400 Subject: [PATCH 1/2] fix(frontend,website): make the wizard and docs site usable on a phone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wizard had no width-based breakpoints, so its fixed 210px step rail left about 165px of content on a 375px screen. Below 700px the rail now becomes a horizontal top bar: the same nodes and connector rotated a quarter turn, each label under its node. The connector reuses the vertical rule's trick — equal width columns and no gap make `left: 50%; width: 100%` land exactly on the next node, the way `height: calc(100% + gap)` did going down. The rest follows from the wider content area: landing cards stack and scroll from the top rather than centering and clipping on a short screen, the preview drawer goes full width now that no side rail constrains it, the Variables description/type pair and the Authors paired fields drop to one column, picker and action rows wrap, and the JSON viewer's per-level indent tightens. Form controls are pinned to 16px on small screens because iOS Safari auto-zooms on any focused control below that size, which left the page zoomed in and scrolled sideways after tapping a field. On the site, the wizard frame and contact page gain dvh units alongside vh so a retractable mobile URL bar doesn't push them past the fold, and the wizard frame stops reserving a single-row footer height that the footer exceeds once it wraps to three rows on a phone. Homepage display type and gutters shrink at 600px, and the footer centers its wrapped rows instead of scattering them under space-between. Co-Authored-By: Claude Opus 5 --- .changeset/frontend-mobile-layout.md | 7 ++ .../src/components/AppShell.module.css | 22 ++++++ .../src/components/JsonViewer.module.css | 13 ++++ .../src/components/PreviewDrawer.module.css | 10 +++ .../src/components/Sidebar.module.css | 77 ++++++++++++++++++- packages/frontend/src/index.css | 14 ++++ .../frontend/src/pages/Authors.module.css | 23 ++++++ .../frontend/src/pages/DataUpload.module.css | 34 ++++++++ .../frontend/src/pages/Landing.module.css | 37 +++++++++ .../frontend/src/pages/ProjectInfo.module.css | 34 ++++++++ packages/frontend/src/pages/Review.module.css | 17 ++++ .../frontend/src/pages/Variables.module.css | 39 ++++++++++ website/src/pages/contact.module.css | 9 +++ website/src/pages/index.module.css | 52 +++++++++++++ website/src/pages/wizard.module.css | 15 ++++ website/src/theme/Footer/styles.module.css | 20 +++++ 16 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 .changeset/frontend-mobile-layout.md diff --git a/.changeset/frontend-mobile-layout.md b/.changeset/frontend-mobile-layout.md new file mode 100644 index 0000000..d2034f7 --- /dev/null +++ b/.changeset/frontend-mobile-layout.md @@ -0,0 +1,7 @@ +--- +"frontend": patch +--- + +The wizard now fits a phone screen. It previously had no width breakpoints at all, so the fixed 210px step rail left roughly 165px of usable content on a 375px device. Below 700px the rail becomes a horizontal top bar — the same nodes and connector rotated a quarter turn, each label under its node — so every step stays visible without hiding navigation behind a menu. The remaining layouts follow: the landing cards stack and scroll from the top instead of centering and clipping on a short screen, the JSON preview drawer takes the full width now that there is no side rail to sit beside, the Variables description/type pair and the Authors paired fields drop to one column, picker and action rows wrap, and the JSON viewer's per-level indent tightens so deep nesting no longer runs off the screen. + +Form controls are also held at 16px on small screens: iOS Safari auto-zooms whenever a focused control is smaller than that, and every field here was around 14px, so tapping one zoomed the page in and left it scrolled sideways. diff --git a/packages/frontend/src/components/AppShell.module.css b/packages/frontend/src/components/AppShell.module.css index 4f8c2db..e164714 100644 --- a/packages/frontend/src/components/AppShell.module.css +++ b/packages/frontend/src/components/AppShell.module.css @@ -11,6 +11,28 @@ background-color: var(--c-bg); } +/* Phone: the sidebar becomes a top bar (see Sidebar.module.css), so the shell + stacks and the content takes the full width. `min-height: 0` lets the content + scroll inside the shell rather than pushing it taller than the viewport — + without it a flex column child refuses to shrink below its content height. */ +@media (max-width: 700px) { + .shell { + flex-direction: column; + } + + .content { + min-height: 0; + /* Bottom padding clears the fixed Preview pill so it can't cover the last + control on the page (usually Continue). */ + padding: 1.5rem 1rem 4.5rem; + } + + .previewPill { + bottom: 1rem; + right: 1rem; + } +} + .previewPill { position: fixed; bottom: 1.5rem; diff --git a/packages/frontend/src/components/JsonViewer.module.css b/packages/frontend/src/components/JsonViewer.module.css index 23cc0ce..505cd6c 100644 --- a/packages/frontend/src/components/JsonViewer.module.css +++ b/packages/frontend/src/components/JsonViewer.module.css @@ -19,6 +19,19 @@ padding-left: 1.5rem; } +/* Phone: 1.5rem per nesting level eats a 375px screen within a few levels, so + tighten the indent (and the gutters) rather than force sideways scrolling. */ +@media (max-width: 700px) { + .viewer { + padding: 0.85rem 0.75rem; + font-size: 0.78rem; + } + + .children { + padding-left: 0.9rem; + } +} + /* Syntax token colors — light mode (jsPsych logo palette, darkened for contrast on white) */ .key { color: #006838; } /* logo dark green — 7:1 on white */ .str { color: #b06210; } /* orange-amber (hue ~31°) — 4.5:1 on white */ diff --git a/packages/frontend/src/components/PreviewDrawer.module.css b/packages/frontend/src/components/PreviewDrawer.module.css index 980638e..42375bb 100644 --- a/packages/frontend/src/components/PreviewDrawer.module.css +++ b/packages/frontend/src/components/PreviewDrawer.module.css @@ -23,6 +23,16 @@ background: rgba(0, 0, 0, 0.35); } +/* Phone: there's no side rail to sit beside, so the drawer takes the screen. + (The --sidebar-w allowance above is meaningless once the nav is a top bar.) */ +@media (max-width: 700px) { + .drawer { + width: 100%; + max-width: 100%; + border-left: none; + } +} + @keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } diff --git a/packages/frontend/src/components/Sidebar.module.css b/packages/frontend/src/components/Sidebar.module.css index c50a3fd..bd9e6ca 100644 --- a/packages/frontend/src/components/Sidebar.module.css +++ b/packages/frontend/src/components/Sidebar.module.css @@ -216,6 +216,79 @@ :global([data-theme="dark"]) .psychdsLink { color: var(--c-ink-2); } :global([data-theme="dark"]) .psychdsLink:hover { color: var(--c-forest-bright); } +/* ─── Phone: the step nav becomes a top bar ────────────────────────────────── + A 210px side rail leaves ~165px of content on a 375px screen, so below + --w-narrow the nav turns horizontal and sits above the content: the same + nodes and connector, rotated a quarter turn, with each label under its node. + Progress stays visible at all times — no hamburger, nothing hidden. */ + +@media (max-width: 700px) { + .sidebar { + width: 100%; + min-width: 0; + padding: 0.75rem 0 0.5rem; + border-right: none; + border-bottom: 1px solid var(--c-border-sub); + } + + .header { + padding: 0 1rem 0.75rem; + } + + /* Steps run left-to-right, each an equal-width column (node above label). */ + .stepList { + flex-direction: row; + flex: 0 0 auto; + gap: 0; + padding: 0 0.5rem; + } + + .stepList li { + flex: 1 1 0; + min-width: 0; + } + + .step { + flex-direction: column; + gap: 0.3rem; + padding: 0.4rem 0.15rem; + font-size: 0.68rem; + line-height: 1.25; + text-align: center; + } + + /* The connector rotates with the flow: it now runs from this node's center + across to the next node's. Equal-width columns make width:100% land exactly + on the next node, the same trick the vertical rule uses with its height. */ + .step::before { + left: 50%; + top: calc(0.4rem + 11px); /* step padding-top + node radius = node center */ + width: 100%; + height: 2px; + transform: none; + } + + /* Footer flattens into a single row under the steps so it costs one line of + height instead of two stacked blocks. */ + .footer { + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.6rem 1rem 0; + margin-top: 0.4rem; + } + + .psychdsLink { + padding: 0; + } + + .startOver { + width: auto; + flex-shrink: 0; + } +} + /* Start over confirmation modal — native for free focus trap + Escape */ .dialog { @@ -223,7 +296,9 @@ border: 1px solid var(--c-border); border-radius: 12px; padding: 1.75rem; - width: 320px; + /* Never wider than the viewport it's centred in (320px overflows a 375px + phone once the UA's dialog margins are counted). */ + width: min(320px, calc(100vw - 2rem)); display: flex; flex-direction: column; gap: 0.75rem; diff --git a/packages/frontend/src/index.css b/packages/frontend/src/index.css index cc60063..90e0a85 100644 --- a/packages/frontend/src/index.css +++ b/packages/frontend/src/index.css @@ -236,6 +236,20 @@ a:hover { outline-offset: 2px; } +/* Phone: iOS Safari auto-zooms the page whenever a focused form control has a + font-size under 16px — every field here is ~14px, so tapping one would zoom + in and leave the layout scrolled sideways with no way back short of a pinch. + Holding the controls at 16px on small screens is the standard prevention. + !important because each page's CSS module sets its own font-size on these + controls by class, which outranks a bare element selector. */ +@media (max-width: 700px) { + input, + select, + textarea { + font-size: 16px !important; + } +} + /* Respect users who ask for less motion: collapse transitions and the preview drawer's slide-in to a near-instant, non-animated state change. */ @media (prefers-reduced-motion: reduce) { diff --git a/packages/frontend/src/pages/Authors.module.css b/packages/frontend/src/pages/Authors.module.css index 9692cd1..21c5569 100644 --- a/packages/frontend/src/pages/Authors.module.css +++ b/packages/frontend/src/pages/Authors.module.css @@ -410,3 +410,26 @@ .continueBtn:hover { background-color: var(--c-cta-hover); } + +/* ─── Phone ─────────────────────────────────────────────────────────────────── */ + +@media (max-width: 700px) { + /* Paired fields (name/email, etc.) stack — two columns on a phone leaves each + input too narrow to read what's typed in it. */ + .fieldRow { + grid-template-columns: 1fr; + gap: 0.7rem; + } + + .card { + padding: 0.85rem 0.85rem 0.75rem; + } + + .bulkActions { + flex-wrap: wrap; + } + + .continueBtn { + width: 100%; + } +} diff --git a/packages/frontend/src/pages/DataUpload.module.css b/packages/frontend/src/pages/DataUpload.module.css index c7daedb..8f6f186 100644 --- a/packages/frontend/src/pages/DataUpload.module.css +++ b/packages/frontend/src/pages/DataUpload.module.css @@ -485,3 +485,37 @@ .replaceConfirmYes:hover { filter: brightness(0.94); } + +/* ─── Phone ─────────────────────────────────────────────────────────────────── */ + +@media (max-width: 700px) { + /* Button + "or" + folder name is too much for one phone line; let it wrap, + and let the (ellipsised) folder name shrink rather than push the row wide. */ + .pickerRow { + flex-wrap: wrap; + gap: 0.6rem 0.85rem; + } + + .folderName { + min-width: 0; + flex: 1 1 100%; + } + + /* Action rows keep their spacing but drop to a second line when needed. */ + .joinKeyActions, + .replaceConfirmBtns { + flex-wrap: wrap; + gap: 0.75rem 1rem; + } + + /* Status/file lists get more of the short viewport before they scroll. */ + .fileList, + .statusList { + max-height: 40vh; + } + + .continueBtn, + .processBtn { + width: 100%; + } +} diff --git a/packages/frontend/src/pages/Landing.module.css b/packages/frontend/src/pages/Landing.module.css index b3d2a28..7c66625 100644 --- a/packages/frontend/src/pages/Landing.module.css +++ b/packages/frontend/src/pages/Landing.module.css @@ -180,3 +180,40 @@ padding: 0.1em 0.3em; border-radius: 3px; } + +/* ─── Phone ─────────────────────────────────────────────────────────────────── + Two 260px cards side by side need 544px, so they stack. Once stacked the + screen can no longer centre everything vertically without clipping the top on + a short phone, so the column scrolls from the top instead. */ + +@media (max-width: 700px) { + .landing { + justify-content: flex-start; + overflow-y: auto; + padding: 2rem 1rem; + } + + .logo { + height: 44px; + } + + .title { + font-size: 1.75rem; + } + + .description { + font-size: 0.95rem; + } + + .cards { + flex-direction: column; + gap: 1rem; + width: 100%; + max-width: 400px; + } + + .card { + width: 100%; + padding: 1.25rem; + } +} diff --git a/packages/frontend/src/pages/ProjectInfo.module.css b/packages/frontend/src/pages/ProjectInfo.module.css index a1b4201..ab24306 100644 --- a/packages/frontend/src/pages/ProjectInfo.module.css +++ b/packages/frontend/src/pages/ProjectInfo.module.css @@ -401,3 +401,37 @@ .continueBtn:hover { background-color: var(--c-cta-hover); } + +/* ─── Phone ─────────────────────────────────────────────────────────────────── */ + +@media (max-width: 700px) { + .form { + gap: 1.25rem; + } + + /* "Upload JSON" + its hint stop sharing one line. */ + .uploadRow { + flex-wrap: wrap; + gap: 0.5rem 0.75rem; + } + + /* The 4.5rem label gutter leaves too little for the value, so the conflict + rows read as label-above-value instead. */ + .conflictDetailRow { + flex-direction: column; + gap: 0.15rem; + } + + .conflictDetailLabel { + width: auto; + } + + .conflictBtns { + flex-wrap: wrap; + } + + .continueBtn { + align-self: stretch; + width: 100%; + } +} diff --git a/packages/frontend/src/pages/Review.module.css b/packages/frontend/src/pages/Review.module.css index b47562b..ea01727 100644 --- a/packages/frontend/src/pages/Review.module.css +++ b/packages/frontend/src/pages/Review.module.css @@ -246,3 +246,20 @@ white-space: pre-wrap; word-break: break-word; } + +/* ─── Phone ─────────────────────────────────────────────────────────────────── */ + +@media (max-width: 700px) { + /* Full-width so the primary action is an easy thumb target. */ + .downloadBtn, + .saveJsonBtn, + .validateBtn { + align-self: stretch; + width: 100%; + text-align: center; + } + + .validatorNote { + padding: 0.85rem 0.9rem; + } +} diff --git a/packages/frontend/src/pages/Variables.module.css b/packages/frontend/src/pages/Variables.module.css index 562cffb..c83b990 100644 --- a/packages/frontend/src/pages/Variables.module.css +++ b/packages/frontend/src/pages/Variables.module.css @@ -374,3 +374,42 @@ .continueBtn:hover { background-color: var(--c-cta-hover); } + +/* ─── Phone ─────────────────────────────────────────────────────────────────── */ + +@media (max-width: 700px) { + /* Subtext and the "show system variables" switch stop competing for one line. */ + .introRow { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + margin-bottom: 1.25rem; + } + + /* A long column name plus its badges no longer forces the row wider than the + screen — the name wraps and the badges keep their own line if they must. */ + .rowHeader { + flex-wrap: wrap; + gap: 0.4rem 0.6rem; + padding: 0.6rem 0.75rem; + } + + .rowName { + min-width: 0; + overflow-wrap: anywhere; + } + + /* Description over type, rather than a 140px column squeezing both. */ + .bodyMain { + grid-template-columns: 1fr; + gap: 0.85rem; + } + + .rowBody { + padding: 0.6rem 0.75rem 0.85rem; + } + + .continueBtn { + width: 100%; + } +} diff --git a/website/src/pages/contact.module.css b/website/src/pages/contact.module.css index 6ae7590..a017e73 100644 --- a/website/src/pages/contact.module.css +++ b/website/src/pages/contact.module.css @@ -3,9 +3,18 @@ align-items: center; justify-content: center; min-height: calc(100vh - var(--ifm-navbar-height) - 6rem); + /* dvh so the retractable URL bar on mobile doesn't push the page taller than + the screen; the vh line above is the fallback. */ + min-height: calc(100dvh - var(--ifm-navbar-height) - 6rem); padding: 3rem 1.5rem; } +@media (max-width: 700px) { + .wrap { + padding: 2rem 1.15rem; + } +} + .inner { max-width: 720px; text-align: left; diff --git a/website/src/pages/index.module.css b/website/src/pages/index.module.css index 6e3becb..f01b8d2 100644 --- a/website/src/pages/index.module.css +++ b/website/src/pages/index.module.css @@ -232,3 +232,55 @@ font-size: 0.95rem; margin: 0; } + +/* --- Phone -------------------------------------------------------------- */ + +/* The 900px step above already stacks the hero; this one is about the phone + itself — trimming the outer gutters and the display type, which still + overhangs a 375px screen at 2.4rem. */ +@media (max-width: 600px) { + .hero { + padding: 2.25rem 1.15rem 1.5rem; + } + + .heroLogo { + height: 60px; + margin-bottom: 1.15rem; + } + + .heroTitle { + font-size: 1.9rem; + letter-spacing: -0.02em; + } + + .heroLede { + font-size: 1.05rem; + } + + .pathCard { + padding: 1.15rem; + } + + /* Side-by-side button + link would leave the button too narrow to read, so + they stack full-width — the link centres to stay aligned with the button + above it rather than hanging off its left edge. */ + .pathCardActions { + align-items: stretch; + flex-direction: column; + gap: 0.85rem; + } + + .pathLink { + text-align: center; + } + + .features, + .showcase { + padding-left: 1.15rem; + padding-right: 1.15rem; + } + + .showcaseCard { + padding: 1.25rem 1.15rem; + } +} diff --git a/website/src/pages/wizard.module.css b/website/src/pages/wizard.module.css index 2f9e791..85b59de 100644 --- a/website/src/pages/wizard.module.css +++ b/website/src/pages/wizard.module.css @@ -6,5 +6,20 @@ 4.5rem is the footer's single-row height; it wraps taller on narrow screens, where a small scroll is fine. */ height: calc(100vh - var(--ifm-navbar-height) - 4.5rem); + /* Mobile browsers count their retractable URL bar in 100vh, so the frame + would hang below the fold until the bar hides. dvh tracks the bar as it + moves; the vh line above stays as the fallback for browsers without it. */ + height: calc(100dvh - var(--ifm-navbar-height) - 4.5rem); border: 0; } + +/* Phone: the footer wraps to three rows here, so the 4.5rem single-row + allowance is far too small — subtracting it would still leave the frame + overhanging. Give the wizard the whole viewport below the navbar and let the + footer live below the fold; the wizard scrolls internally either way. */ +@media (max-width: 700px) { + .wizardFrame { + height: calc(100vh - var(--ifm-navbar-height)); + height: calc(100dvh - var(--ifm-navbar-height)); + } +} diff --git a/website/src/theme/Footer/styles.module.css b/website/src/theme/Footer/styles.module.css index 17da2c3..30686ce 100644 --- a/website/src/theme/Footer/styles.module.css +++ b/website/src/theme/Footer/styles.module.css @@ -64,3 +64,23 @@ color: var(--ifm-color-primary); text-decoration: none; } + +/* Phone: the row already wraps, but a 1.75rem link gap plus space-between + scatters the wrapped items across the width. Centre everything into a tidy + stack and close the gaps up. */ +@media (max-width: 700px) { + .footer { + padding: 1.15rem 1.15rem; + } + + .inner { + justify-content: center; + text-align: center; + gap: 0.9rem 1.25rem; + } + + .links { + justify-content: center; + gap: 0.75rem 1.25rem; + } +} From 4d3a56cb18a87c4f05b9f75f3cd55a6b6e2f615a Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Sat, 25 Jul 2026 11:37:39 -0400 Subject: [PATCH 2/2] fix(frontend): make the phone overrides actually apply, and narrow two of them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .previewPill override sat in a media block placed above the base rule. Same specificity, so source order decided and the base won — the pill never moved on a phone. It now sits after the rule it overrides, with a note on why the position matters. The .shell/.content overrides in that block were unaffected (their base rules precede it) and stay where they are. The 16px rule no longer applies to checkboxes and radios. Neither can be zoomed into, so they were never part of the iOS problem being solved, and forcing a font-size on a control that may size itself from one is a risk taken for nothing. The list cap is now 40dvh (40vh retained as the fallback), matching the reasoning applied to the site's viewport heights in this same branch: on a phone the URL bar counts toward vh. The comment also states what the rule is for, since at portrait heights it lands close to the desktop cap and only does visible work in landscape. Also drops a comment reference to --w-narrow, which does not exist. Co-Authored-By: Claude Opus 5 --- .../frontend/src/components/AppShell.module.css | 14 +++++++++----- .../frontend/src/components/Sidebar.module.css | 6 +++--- packages/frontend/src/index.css | 9 ++++++--- packages/frontend/src/pages/DataUpload.module.css | 7 ++++++- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/components/AppShell.module.css b/packages/frontend/src/components/AppShell.module.css index e164714..fe84c27 100644 --- a/packages/frontend/src/components/AppShell.module.css +++ b/packages/frontend/src/components/AppShell.module.css @@ -26,11 +26,6 @@ control on the page (usually Continue). */ padding: 1.5rem 1rem 4.5rem; } - - .previewPill { - bottom: 1rem; - right: 1rem; - } } .previewPill { @@ -52,6 +47,15 @@ transition: border-color 0.15s, color 0.15s, background-color 0.15s; } +/* Phone: tuck the pill closer into the corner. This has to sit *after* the base + rule above — same specificity, so source order is what decides. */ +@media (max-width: 700px) { + .previewPill { + bottom: 1rem; + right: 1rem; + } +} + .previewPill:hover { color: var(--c-ink); border-color: var(--c-ink-3); diff --git a/packages/frontend/src/components/Sidebar.module.css b/packages/frontend/src/components/Sidebar.module.css index bd9e6ca..3272788 100644 --- a/packages/frontend/src/components/Sidebar.module.css +++ b/packages/frontend/src/components/Sidebar.module.css @@ -217,9 +217,9 @@ :global([data-theme="dark"]) .psychdsLink:hover { color: var(--c-forest-bright); } /* ─── Phone: the step nav becomes a top bar ────────────────────────────────── - A 210px side rail leaves ~165px of content on a 375px screen, so below - --w-narrow the nav turns horizontal and sits above the content: the same - nodes and connector, rotated a quarter turn, with each label under its node. + A 210px side rail leaves ~165px of content on a 375px screen, so below 700px + the nav turns horizontal and sits above the content: the same nodes and + connector, rotated a quarter turn, with each label under its node. Progress stays visible at all times — no hamburger, nothing hidden. */ @media (max-width: 700px) { diff --git a/packages/frontend/src/index.css b/packages/frontend/src/index.css index 90e0a85..352efc4 100644 --- a/packages/frontend/src/index.css +++ b/packages/frontend/src/index.css @@ -240,10 +240,13 @@ a:hover { font-size under 16px — every field here is ~14px, so tapping one would zoom in and leave the layout scrolled sideways with no way back short of a pinch. Holding the controls at 16px on small screens is the standard prevention. - !important because each page's CSS module sets its own font-size on these - controls by class, which outranks a bare element selector. */ + Only the controls you actually type into: a checkbox or radio can't be zoomed + into, and forcing a font-size on one only risks disturbing a styled control + that sizes itself from it. !important because each page's CSS module sets its + own font-size on these controls by class, which outranks a bare element + selector. */ @media (max-width: 700px) { - input, + input:not([type="checkbox"]):not([type="radio"]), select, textarea { font-size: 16px !important; diff --git a/packages/frontend/src/pages/DataUpload.module.css b/packages/frontend/src/pages/DataUpload.module.css index 8f6f186..de2312e 100644 --- a/packages/frontend/src/pages/DataUpload.module.css +++ b/packages/frontend/src/pages/DataUpload.module.css @@ -508,10 +508,15 @@ gap: 0.75rem 1rem; } - /* Status/file lists get more of the short viewport before they scroll. */ + /* Size the lists to the viewport rather than to a fixed 220/260px. In portrait + that lands near the desktop cap; in landscape, where 40% of the height is + well under it, the list gives the rest of the page room instead of filling + the screen. dvh so the retractable URL bar doesn't inflate it; the vh line + is the fallback for browsers without dvh. */ .fileList, .statusList { max-height: 40vh; + max-height: 40dvh; } .continueBtn,