diff --git a/src/components/gallery/JustifiedGallery.tsx b/src/components/gallery/JustifiedGallery.tsx index c7956d7..e9981ed 100644 --- a/src/components/gallery/JustifiedGallery.tsx +++ b/src/components/gallery/JustifiedGallery.tsx @@ -44,6 +44,7 @@ type Props = { maxRowItems?: number; // desktop maxRowItemsMobile?: number; // <640px gap?: number; // px + debug?: boolean; className?: string; }; @@ -84,6 +85,7 @@ export default function JustifiedGallery({ maxRowItems = 5, maxRowItemsMobile = 3, gap = 12, + debug = false, className, }: Props) { const containerRef = useRef(null); @@ -125,7 +127,6 @@ export default function JustifiedGallery({ const isMobile = containerWidth < 640; const targetH = isMobile ? targetRowHeightMobile : targetRowHeight; - const minRowHeight = Math.round(targetH * 0.8); const maxItems = (() => { if (containerWidth < 480) return Math.min(2, maxRowItemsMobile); if (containerWidth < 720) return Math.min(3, maxRowItems); @@ -161,7 +162,10 @@ export default function JustifiedGallery({ aspectSum = 0; }; - for (const it of items) { + const workingItems = items.slice(); + + for (let i = 0; i < workingItems.length; i += 1) { + const it = workingItems[i]; const a = aspectOf(it); current.push({ item: it, aspect: a }); @@ -170,29 +174,51 @@ export default function JustifiedGallery({ // Estimate the row width if we were to keep targetH const estimatedWidth = aspectSum * targetH + gap * (current.length - 1); - // If the computed height would be too small, split the row earlier. - if (current.length > 1) { - const gaps = gap * (current.length - 1); - const widthWithoutGaps = Math.max(0, available - gaps); - const computedH = widthWithoutGaps / aspectSum; - if (computedH < minRowHeight) { - const last = current.pop(); - if (last) { - aspectSum -= last.aspect; - flush(); - current = [last]; - aspectSum = last.aspect; - } - continue; - } - } - // If we've filled the row (or reached max items) and have at least 2 tiles, flush. if ( (estimatedWidth >= available || current.length >= maxItems) && current.length > 1 ) { - flush(); + const gaps = gap * (current.length - 1); + const widthWithoutGaps = Math.max(0, available - gaps); + const computedH = widthWithoutGaps / aspectSum; + + // If the row would be shorter than maxRowHeight, reduce items and flush. + if (computedH < maxRowHeight && current.length > 1) { + const last = current.pop(); + if (last) { + aspectSum -= last.aspect; + } + + const limit = widthWithoutGaps / maxRowHeight; + let swapped = false; + + for (let look = 1; look <= 2; look += 1) { + const idx = i + look; + if (idx >= workingItems.length) break; + const candidate = workingItems[idx]; + const candidateAspect = aspectOf(candidate); + + if (aspectSum + candidateAspect <= limit) { + workingItems[idx] = last?.item ?? candidate; + current.push({ item: candidate, aspect: candidateAspect }); + aspectSum += candidateAspect; + swapped = true; + break; + } + } + + flush(); + if (!swapped && last) { + current = [last]; + aspectSum = last.aspect; + } else { + current = []; + aspectSum = 0; + } + } else { + flush(); + } } } @@ -215,27 +241,50 @@ export default function JustifiedGallery({ return `${first}-${last}-${row.length}`; }, []); + const isSmallScreen = containerWidth < 640; + return (
- {rows.map((row) => ( -
- {row.map((t) => ( - - ))} + {rows.map((row, idx) => ( +
+
+ {row.map((t) => ( + + ))} +
+ {debug ? ( +
+ {`row ${idx + 1} | h=${Math.round(row[0]?.h ?? 0)} | w=${Math.round( + row.reduce((sum, t) => sum + t.w, 0) + gap * (row.length - 1), + )} | items=${row.length} | `} + {row + .map( + (t) => + `${t.item.id}:${Math.round(t.w)}x${Math.round(t.h)} (src ${t.item.width}x${t.item.height})`, + ) + .join(" | ")} +
+ ) : null}
))}
diff --git a/src/components/portfolio/PortfolioGallery.tsx b/src/components/portfolio/PortfolioGallery.tsx index 08657e5..f2a0722 100644 --- a/src/components/portfolio/PortfolioGallery.tsx +++ b/src/components/portfolio/PortfolioGallery.tsx @@ -90,17 +90,17 @@ export default function PortfolioGallery({ dominantHex: it.dominantHex, })); - useEffect(() => { - if (items.length === 0) return; - // Debug: inspect dominantHex values coming from the server. - console.log( - "[PortfolioGallery] dominantHex sample", - items.slice(0, 5).map((it) => ({ - id: it.id, - dominantHex: it.dominantHex, - })) - ); - }, [items]); + // useEffect(() => { + // if (items.length === 0) return; + // // Debug: inspect dominantHex values coming from the server. + // console.log( + // "[PortfolioGallery] dominantHex sample", + // items.slice(0, 5).map((it) => ({ + // id: it.id, + // dominantHex: it.dominantHex, + // })) + // ); + // }, [items]); if (!loading && done && galleryItems.length === 0) { return ( @@ -116,6 +116,7 @@ export default function PortfolioGallery({ items={galleryItems} hrefFrom="portfolio" showCaption={false} + debug={false} targetRowHeight={160} targetRowHeightMobile={160} maxRowHeight={300}