fix(gallery): give masonry tiles their real shape back (#1130, #1131)

Two independent causes of the same symptom — an aspect-ratio layout that does
not lay anything out.

gallery-premium discarded the tile height MasonryPhotoAlbum computed from
photos.width/height and set height:auto on both card and image, so the rendered
shape came from whatever rendition was served. With thumbnail_fit seeded 'cover'
every rendition is square, so the masonry drew identical squares.

The bundled CSS templates pinned images to a fixed pixel height, which beats the
.h-full utility six of the seven layouts rely on. Elegant Dark is seeded
is_default, so that was the out-of-the-box result for any layout other than
grid/timeline.

Migrations 052/053 corrected for fresh installs; 175 repairs the rows already
seeded. The repair is whitespace-tolerant because sanitizeCSS strips newlines
from any template ever saved through the editor, matches the height property
with a lookbehind so line-height/max-height are untouched, handles grouped
selectors and skips nested rules.

Stable twin of #1135.
This commit is contained in:
Paul Nothaft
2026-08-22 21:36:51 +02:00
committed by GitHub
parent da44f1947b
commit d977e3e296
5 changed files with 388 additions and 9 deletions
@@ -54,7 +54,7 @@ interface PhotoCardProps {
const PhotoCard: React.FC<PhotoCardProps> = ({
photo,
width,
height: _height,
height,
onClick,
onLike,
onSelect,
@@ -70,8 +70,13 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
allowLikes = false,
index
}) => {
// Note: height is passed but not used as we maintain aspect ratio via width
void _height;
// The height MasonryPhotoAlbum computed from photos.width/height is used, not
// discarded (#1130). Letting the tile size itself from the image meant the
// rendered shape came from whatever rendition was served — and with
// thumbnail_fit seeded to 'cover' (migration 040) every rendition is square,
// so the masonry laid out identical squares and was indistinguishable from
// the fixed grid. The photo's real aspect ratio is in the DB and is what the
// album already laid out against.
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0.1,
@@ -85,7 +90,7 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
<motion.div
ref={ref}
className={`gallery-premium-photo-card group ${isSelected ? 'selected' : ''}`}
style={{ width: '100%', height: 'auto', display: 'block' }}
style={{ width: '100%', height, display: 'block' }}
initial={{ opacity: 0, y: 20 }}
animate={inView ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 }}
transition={{ duration: 0.4, delay: Math.min(index * 0.05, 0.3) }}
@@ -95,8 +100,11 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
<AuthenticatedImage
src={photo.thumbnail_url || photo.url}
alt={photo.filename}
style={{ width, height: 'auto' }}
className="w-full h-auto object-cover"
// No inline height: the card now has a definite one, so the
// stylesheet's `.gallery-premium-photo-card img { height: 100% }` can
// finally apply and object-fit: cover crops a square rendition INTO the
// correctly-shaped tile, rather than the rendition dictating the shape.
className="w-full h-full object-cover"
loading="lazy"
isGallery={true}
slug={slug}