diff --git a/frontend/src/components/gallery/PhotoLightbox.tsx b/frontend/src/components/gallery/PhotoLightbox.tsx index 4276af9c..d079c564 100644 --- a/frontend/src/components/gallery/PhotoLightbox.tsx +++ b/frontend/src/components/gallery/PhotoLightbox.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useLayoutEffect, useRef } from 'react'; import { useDevToolsProtection } from '../../hooks/useDevToolsProtection'; import { X, ChevronLeft, ChevronRight, Download, ZoomIn, ZoomOut, Minimize2, MessageSquare, Heart, Star } from 'lucide-react'; import type { Photo } from '../../types'; @@ -103,6 +103,21 @@ export const PhotoLightbox: React.FC = ({ return () => window.removeEventListener('resize', onResize); }, []); + // The bottom toolbar is opaque and the image area stops above it (#888) + // so the toolbar never masks part of the photo. Its height varies + // (flex-wrap on small screens, optional filename line, safe-area + // padding), so measure it and keep the measurement fresh. + const toolbarRef = useRef(null); + const [toolbarHeight, setToolbarHeight] = useState(0); + useLayoutEffect(() => { + const el = toolbarRef.current; + if (!el) return; + setToolbarHeight(el.offsetHeight); + const observer = new ResizeObserver(() => setToolbarHeight(el.offsetHeight)); + observer.observe(el); + return () => observer.disconnect(); + }, []); + // Save-aware download. On mobile (where Web Share + files is supported) // this opens the OS share sheet so "Save to Photos" actually lands in @@ -656,9 +671,12 @@ export const PhotoLightbox: React.FC = ({ {/* 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. */} + buttons above the iOS home indicator. Opaque, and the image area + above is shortened by toolbarHeight so it never masks the photo + (#888). */}
= ({ return (
= ({ style={{ cursor: isVideoCurrent ? 'default' : (zoom > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default'), right: isDesktopFeedback ? `${desktopFeedbackWidth}px` : 0, + // Stop above the opaque toolbar so it never masks the + // photo (#888). + bottom: `${toolbarHeight}px`, // Tell the browser we handle horizontal gestures ourselves so // it doesn't fight us with edge-swipe back navigation, native // pinch-zoom, etc. Videos keep default touch behaviour.