fix(media): rework portrait image sizing to fill+object-contain

Chasing this through width/height class combinations (w-fit/h-fit vs
w-auto/h-auto) kept trading one bug for another: centering broke height,
fixing height broke centering. Root cause: max-h-full/max-w-full on the
image are percentages, and every wrapper in the chain only ever had an
intrinsic (auto/fit-content) size — CSS treats percentages against an
intrinsically-sized parent as unresolvable, so which axis "won" depended
on incidental flex-stretch behavior rather than anything deliberate.

Replaced the shrink-wrap-to-image approach with fill-the-definite-parent:
wrapper and image now take w-full h-full of the already definitely-sized
media column (PostDetailDialog) / full-screen overlay (StoryViewer), and
object-contain (default centered) handles aspect-ratio scaling entirely
on its own — no percentage resolution against an ambiguous parent left
anywhere in the chain.

Not visually verified locally (auth-gated route) — needs a real check
against a portrait post/story after deploy.
This commit is contained in:
admin committed 2026-08-14 15:14:32 +02:00
1 parent 9efc207710
commit 23722f8b6d
2 files changed
+26 -15

No files matched your search

+14 -6
View File
@@ -243,6 +243,14 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD
</>
);
// Image wrappers below fill the (definitely-sized) media column and let
// object-contain handle aspect-ratio scaling/centering. An earlier
// "shrink-wrap to the image's own rendered box" approach relied on
// percentage max-height/max-width resolving against an intrinsically-sized
// (auto/fit-content) parent, which CSS treats as unresolvable — portrait
// images ended up either uncentered or rendered at full natural size
// (clipped) depending on which axis lost. Filling a definite-size parent
// avoids that class of bug entirely.
const mediaContent = post.type === "VIDEO" && post.videoPost ? (
<div className="relative h-full w-full">
<VideoCard
@@ -259,13 +267,13 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD
<Carousel setApi={setCarouselApi} className="h-full">
<CarouselContent className="h-full">
{images.map((img) => (
<CarouselItem key={img.id} className="basis-full flex items-center justify-center h-full">
<CarouselItem key={img.id} className="basis-full h-full">
<BlurImage
src={getMediaUrl(img.storageKey) ?? ""}
blurhash={img.blurhash}
alt={currentCaption ?? `${pet.name}'s post`}
className="block max-h-full max-w-full object-contain"
wrapperClassName="relative block h-auto w-fit"
className="block w-full h-full object-contain"
wrapperClassName="relative block w-full h-full"
/>
</CarouselItem>
))}
@@ -288,13 +296,13 @@ export function PostDetailDialog({ post, open, onClose, onDeleted }: PostDetailD
</div>
</div>
) : (
<div className="relative inline-flex w-fit max-h-full max-w-full">
<div className="relative w-full h-full">
<BlurImage
src={getMediaUrl(images[0].storageKey) ?? ""}
blurhash={images[0].blurhash}
alt={currentCaption ?? `${pet.name}'s post`}
className="block max-h-full max-w-full object-contain"
wrapperClassName="relative block h-auto w-fit"
className="block w-full h-full object-contain"
wrapperClassName="relative block w-full h-full"
/>
<AiDisclosureBadge value={post.aiDisclosure} className="h-8" />
</div>
+12 -9
View File
@@ -155,20 +155,23 @@ export function StoryViewer({
))}
</div>
{/* Story image — inline-flex wrapper shrinks to the actual rendered
image box (not the full-screen overlay) so both the centering
and the badge overlay track the real image bounds. BlurImage's
own wrapper defaults to w-full/h-full (fine for fill-container
usages like feed thumbnails), so it's overridden here via
wrapperClassName. */}
{/* Story image — wrapper fills the full-screen overlay and
object-contain does all the aspect-ratio scaling/centering
itself. A previous "shrink-wrap to the image's own rendered
box" approach relied on percentage max-height/max-width
resolving against an intrinsically-sized (auto/fit-content)
parent, which CSS treats as unresolvable — portrait images
would render at full natural size (clipped) or lose centering
depending on which axis' ambiguity won. Filling a definite-size
parent sidesteps that class of bug entirely. */}
{mediaUrl && (
<div className="relative inline-flex w-fit max-h-screen max-w-full">
<div className="relative w-full h-full">
<BlurImage
src={mediaUrl}
blurhash={currentStory.blurhash}
alt={t("storyAriaLabel", { petName })}
className="block max-h-screen max-w-full object-contain"
wrapperClassName="relative block h-auto w-fit"
className="block w-full h-full object-contain"
wrapperClassName="relative block w-full h-full"
/>
<AiDisclosureBadge
value={currentStory.aiDisclosure}