Skip to content
6 changes: 4 additions & 2 deletions src/components/Header/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,13 @@
justify-content: center;
z-index: 113;
/* Desktop sets gap: 74px which pushes children past the viewport
on phones. Reset for mobile and clip any residual overflow. */
on phones. Reset for mobile. Don't add overflow-x: hidden here — it
forces overflow-y to auto, turning the fixed header into a scroll
container that clips the user-profile dropdown (which opens below it).
The root <html> already clips residual horizontal overflow. */
gap: 0;
max-width: 100vw;
max-width: 100dvw;
overflow-x: hidden;

.actions {
display: block;
Expand Down
6 changes: 5 additions & 1 deletion src/components/UserProfile/UserProfile.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
.dropdown {
position: absolute;
top: calc(100% + 8px);
right: 24px;
right: 0;
min-width: 180px;
background-image: url('/keepsimple_/assets/user-dropdown/white-bg.png');
background-size: cover;
Expand All @@ -36,6 +36,10 @@
z-index: 200;
display: flex;
flex-direction: column;
// The home hero <video> is GPU-composited while playing and can paint over
// this menu even though z-index puts it on top; promoting the dropdown to
// its own layer forces the compositor to honour that order.
transform: translateZ(0);
}

.userNameDropdown {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ $cover-aspect-ultrawide: calc(4000 / 852);
}
}

// Mobile (<768px): a fixed 448px portrait band. The hotspot layer isn't
// rendered below 768px (see InteractiveCover.tsx `isWide`), so the cover is a
// static image — the 3:2 art is covered into the box, cropping left/right.
@media (max-width: 767px) {
.frame {
aspect-ratio: auto;
height: 448px;
max-height: none;
}

.image {
left: 0;
transform: none;
width: 100%;
height: 100%;
max-width: none;
object-fit: cover;
}
}

.trigger {
position: absolute;
margin: 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
height: 1px;
background: var(--black-100);
margin: 16px 0;

// Mobile: collapse the top margin so the divider connects to the fixed
// header (paired with the layout wrapper dropping its top padding).
@media (max-width: 768px) {
margin-top: 0;
}
}

.controls {
Expand Down Expand Up @@ -229,7 +235,7 @@

@media (max-width: 768px) {
width: 100%;
justify-content: flex-end;
justify-content: flex-start;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@
grid-template-columns: 1fr;
gap: 24px;
padding: 20px 16px;
// Stacked single-column grid in a flex:1 (full-height) body: grid's default
// align-content stretches the auto rows to fill leftover vertical space,
// ballooning `.actions` and `.left` far past their content. Pack rows to the
// top so each sizes to its content; extra space stays at the bottom.
align-content: start;
}

// Keep the cover from swallowing the screen on tall portrait art.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,38 @@
background: transparent !important;
}

// Mobile: the two action buttons crammed beside the heading squeezed the
// "Share selection via link" label into a 3-line wrap. Stack the buttons under
// one another, and pin each label to one 14px line.
@media (max-width: 768px) {
.header {
gap: 16px;
padding: 16px;
}

.actions {
flex-direction: column;
align-items: stretch;
gap: 12px;
}

.actions button {
width: 100%;
justify-content: center;

span {
font-size: 14px;
white-space: nowrap;
}
}

// Two stacked buttons make the header taller than the desktop 87px bar, so
// grow the collapsed cap to fit it — otherwise the lower button is clipped.
.collapsed {
max-height: 120px;
}
}

// Fills the space under the header and clips its content as the panel collapses
// (the panel's max-height drives the accordion). `flex: 1` + `min-height: 0`
// let .body scroll when the panel hits its 440px cap with many objects.
Expand Down
55 changes: 55 additions & 0 deletions src/components/library/organisms/Shelf/Shelf.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,45 @@
.button {
color: var(--brown);
}

// Mobile: split the cramped single row into two. Promote `.right`'s children
// into the header's flex flow (display: contents) so a zero-height full-width
// break can drop the two action buttons onto their own row:
// Row 1: settings + type icon + name (left) · count (right)
// Row 2: Select shelf (left) · Add object (right)
@media (max-width: 768px) {
flex-wrap: wrap;

.right {
display: contents;
}

.left {
order: 1;
min-width: 0;
}

.count {
order: 2;
}

&::after {
content: '';
flex-basis: 100%;
height: 0;
order: 3;
}

.selectShelfWrap {
order: 4;
margin-top: 12px;
}

.addWrap {
order: 5;
margin-top: 12px;
}
}
}

.content {
Expand Down Expand Up @@ -158,6 +197,22 @@
z-index: 0;
pointer-events: none;

// Mobile: pull the board wider past both edges so it spans the narrow
// viewport. left/right set the box, so width is implied — drop the
// desktop fill-available width and its negative margin.
@media (max-width: 768px) {
left: -148px;
right: -35px;
width: auto;
margin-right: 0;

// The box is now sized by left/right (width: auto), so the inherited
// `auto` would shrink the board to its intrinsic width — fill instead.
img {
width: 100%;
}
}

img {
width: inherit;
}
Expand Down
15 changes: 13 additions & 2 deletions src/components/library/organisms/Shelf/Shelf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,22 @@ export function Shelf(props: ShelfProps): JSX.Element {

const isOverflowing = canScrollLeft || canScrollRight;

// Page the row by most of a viewport, matching the LibraryToolbar jump arrows.
// Advance one card per click. The stride is the distance between two adjacent
// slots (card width + gap); with a single card fall back to its own width, and
// with none to most of a viewport.
const scrollJump = (direction: -1 | 1) => {
const el = itemsRef.current;
if (!el) return;
el.scrollBy({ left: direction * el.clientWidth * 0.8, behavior: 'smooth' });
const slots = cardsRef.current?.children;
let step = el.clientWidth * 0.8;
if (slots && slots.length > 0) {
const first = slots[0] as HTMLElement;
step =
slots.length > 1
? (slots[1] as HTMLElement).offsetLeft - first.offsetLeft
: first.offsetWidth;
}
el.scrollBy({ left: direction * step, behavior: 'smooth' });
};

const closeRename = useCallback(() => {
Expand Down
9 changes: 6 additions & 3 deletions src/layouts/library/Home/Home.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
// context, so the button needs to sit above them to stay clickable.
z-index: 4;

@media (max-width: 590px) {
top: 20px;
left: 16px;
// Mobile: the cover is a full-bleed 448px band under the fixed 55px header.
// Center the button and sit it 22px below the header (55 + 22).
@media (max-width: 767px) {
top: 77px;
left: 50%;
transform: translateX(-50%);
max-width: calc(100% - 32px);
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/layouts/library/Library/Library.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
min-height: calc(100vh - 168px);
}

// Mobile: drop the top padding so the sticky toolbar's divider sits flush
// against the fixed header instead of floating below a 16px gap.
@media (max-width: 768px) {
padding-top: 0;
}

// Fill the viewport below the 48px sticky header so the content centers in the
// real available space rather than the top 60vh (which sat it too high).
.empty {
Expand Down
Loading