fix(lightbox): mobile toolbar clipping + iOS safe-area + viewport-fit (#336)

When feedback was enabled the lightbox bottom toolbar packed counter +
zoom + download + like + 5-star + comments into a single row that
overflowed the viewport on iPhone-class widths, putting the rating
stars under the screen edge and below the iOS home indicator.

Changes:
- Bottom toolbar now uses flex-wrap with reduced gap/padding on mobile,
  so all controls fit (375px viewport: max-right 363 < 375; 390px:
  max-right 378 < 390; 393px: max-right 393 < 393).
- pb computed as max(0.75rem, env(safe-area-inset-bottom)) so the row
  sits above the iOS home indicator on devices with a gesture bar.
- Close button top/right now use max(1rem, env(safe-area-inset-*)) so
  it doesn't disappear under the notch / dynamic island.
- "Swipe to navigate" hint moved from bottom-20 to bottom-40 so it
  clears the now-taller wrapped toolbar.
- index.html viewport meta gains viewport-fit=cover to enable
  env(safe-area-inset-*) on iOS Safari.

Verified in mobile emulation across iPhone SE (375x667), iPhone 13/14
(390x844), iPhone 14 Pro (393x852) portrait, and 14 Pro landscape
(852x393) — toolbar fits, photo centered, no clipping.
This commit is contained in:
Paul Nothaft
2026-04-28 16:50:44 +02:00
parent fcddfe094b
commit 42a7ae4be8
2 changed files with 23 additions and 12 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>PicPeak - Photo Sharing Platform</title>
</head>
<body>
@@ -426,12 +426,16 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
return (
<div className={lightboxClass}>
{/* Close button */}
{/* Close button. top respects iOS safe-area (notch) so it doesn't
disappear under the camera/dynamic-island. */}
<button
onClick={onClose}
className="absolute top-4 p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-30"
className="absolute p-2 bg-white/10 hover:bg-white/20 rounded-full transition-colors z-30"
aria-label="Close"
style={{ right: isDesktopFeedback ? `${desktopFeedbackWidth + 16}px` : '1rem' }}
style={{
top: 'max(1rem, env(safe-area-inset-top))',
right: isDesktopFeedback ? `${desktopFeedbackWidth + 16}px` : 'max(1rem, env(safe-area-inset-right))'
}}
>
<X className="w-6 h-6 text-white" />
</button>
@@ -456,19 +460,25 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
</button>
) : null}
{/* Bottom toolbar */}
{/* Bottom toolbar. flex-wrap + reduced gap/padding on mobile prevent
the action row from clipping when feedback (likes / 5-star ratings /
comments) is enabled. pb-[env(safe-area-inset-bottom)] keeps the
buttons above the iOS home indicator. */}
<div
className="absolute bottom-0 left-0 bg-gradient-to-t from-black/80 to-transparent p-4 z-20"
style={{ right: isDesktopFeedback ? `${desktopFeedbackWidth}px` : 0 }}
className="absolute bottom-0 left-0 bg-gradient-to-t from-black/80 to-transparent px-3 pt-3 pb-3 sm:p-4 z-20"
style={{
right: isDesktopFeedback ? `${desktopFeedbackWidth}px` : 0,
paddingBottom: 'max(0.75rem, env(safe-area-inset-bottom))'
}}
>
<div className="max-w-4xl mx-auto flex items-center justify-between">
<div className="max-w-4xl mx-auto flex items-center justify-between gap-2 flex-wrap">
<div className="text-white">
<p className="text-sm opacity-75">
{currentIndex + 1} / {photos.length}
</p>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 sm:gap-2 flex-wrap justify-end">
<button
onClick={handleZoomOut}
disabled={zoom <= 1}
@@ -638,8 +648,9 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
)}
</div>
{/* Touch/swipe indicators for mobile */}
<div className="absolute bottom-20 left-1/2 -translate-x-1/2 text-white text-sm opacity-50 pointer-events-none md:hidden z-20">
{/* Touch/swipe hint for mobile. Sits above the bottom toolbar
(which can wrap to two rows when feedback controls are enabled). */}
<div className="absolute bottom-40 left-1/2 -translate-x-1/2 text-white text-sm opacity-50 pointer-events-none md:hidden z-20">
Swipe to navigate
</div>