Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/frontend-mobile-layout.md
Original file line number Diff line number Diff line change
@@ -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.
26 changes: 26 additions & 0 deletions packages/frontend/src/components/AppShell.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@
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 {
position: fixed;
bottom: 1.5rem;
Expand All @@ -30,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);
Expand Down
13 changes: 13 additions & 0 deletions packages/frontend/src/components/JsonViewer.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
10 changes: 10 additions & 0 deletions packages/frontend/src/components/PreviewDrawer.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
77 changes: 76 additions & 1 deletion packages/frontend/src/components/Sidebar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,89 @@
: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 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) {
.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 <dialog> for free focus trap + Escape */

.dialog {
background: var(--c-bg-raised);
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;
Expand Down
17 changes: 17 additions & 0 deletions packages/frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,23 @@ 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.
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:not([type="checkbox"]):not([type="radio"]),
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) {
Expand Down
23 changes: 23 additions & 0 deletions packages/frontend/src/pages/Authors.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
}
}
39 changes: 39 additions & 0 deletions packages/frontend/src/pages/DataUpload.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -485,3 +485,42 @@
.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;
}

/* 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,
.processBtn {
width: 100%;
}
}
37 changes: 37 additions & 0 deletions packages/frontend/src/pages/Landing.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
34 changes: 34 additions & 0 deletions packages/frontend/src/pages/ProjectInfo.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
}
}
17 changes: 17 additions & 0 deletions packages/frontend/src/pages/Review.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading
Loading