diff --git a/app/cart/components/RelatedProducts.tsx b/app/cart/components/RelatedProducts.tsx index 0ad199c..906498e 100644 --- a/app/cart/components/RelatedProducts.tsx +++ b/app/cart/components/RelatedProducts.tsx @@ -129,9 +129,13 @@ export function RelatedProducts({ (opacity: 0) — invisible. Not worth chasing a fix for a scroll-reveal nicety on a list that mutates; a static grid renders correctly with no animation risk. */} - {/* No items-start — see ProductGrid.tsx's own comment on why that - silently defeats ProductCard's h-full equal-height design. */} -
+ {/* No items-start, and grid-auto-rows + subgrid on each card — see + ProductGrid.tsx's own comment on why both are needed for + ProductCard's internal rows (price, etc.) to actually line up + across cards, not just overall card height. No RevealItem + wrapper here (see comment above), so the subgrid classes go + straight onto ProductCard's own className prop instead. */} +
{displayProducts.map((product, i) => ( 0 ? product.variants.some((v) => v.lowStock) : product.lowStock; return ( + // 6 direct grid rows (image / header / price / belowPrice / lowstock / + // button) instead of the old flex column — a plain flex-1 spacer only + // pins the button to the bottom of THIS card, it can't make the price + // row start at the same Y as a row sibling whose header block is a + // different height (e.g. a 2-line vs 1-line subline). grid-template- + // rows: subgrid pulls in the row-tracks each caller's grid container + // already declares (see ProductGrid.tsx's own comment) so every row is + // genuinely shared/max'd across the row of cards — dynamic, no + // line-clamp/truncation needed on the subline. gap-3 is used for every + // row gap including after the image (was pt-4/1rem there before — + // 0.25rem less, accepted for one consistent grid gap instead of mixed + // margin/gap spacing).
{/* Link wraps only the image, not the whole header — WishlistButton @@ -98,55 +110,46 @@ export function ProductCard({ )}
-
-
- {product.categories.length > 0 && ( -

{product.categories.join(", ")}

- )} - {/* line-clamp-1 on the name and a reserved+clamped 2-line slot - for the subline — without these, a longer name or a subline - that wraps to 2 lines pushes THAT card's price row down - relative to its row siblings; the flex-1 spacer near the - button only re-aligns the button, not the price above it. */} - {product.href ? ( - - - - ) : ( -

- -

- )} -

{product.subline}

-
-

- {discount !== null && ( - {formatPrice(product.compareAtPrice!)} - )} - {formatPrice(product.price)} - {!kleinunternehmer && inkl. {taxRate}% MwSt.} -

- {belowPrice} - {/* Always rendered, text conditional — reserved height keeps every - card in a row equal height regardless of low-stock status. An - earlier version omitted this for fully-out-of-stock products - (this line can never apply there), on the theory that it was - dead space — but NotifyMeForm's single-row redesign (see its - own comment) now matches AddToCartInlineButton's button height - exactly, which made THIS line the only remaining source of a - mismatch: omitting it made the sold-out card shorter than its - siblings instead of taller. Keeping it unconditional is what - actually gets an exact match now. */} -

{anyLowStock ? "Nur noch wenige verfügbar" : null}

- {/* flex-1 spacer — pins every card's button to the same Y - regardless of whether the title/category line wraps. */} -
+
+ {product.categories.length > 0 && ( +

{product.categories.join(", ")}

+ )} + {product.href ? ( + + + + ) : ( +

+ +

+ )} + {product.subline &&

{product.subline}

} +
+

+ {discount !== null && ( + {formatPrice(product.compareAtPrice!)} + )} + {formatPrice(product.price)} + {!kleinunternehmer && inkl. {taxRate}% MwSt.} +

+ +
{belowPrice}
+ + {/* Always rendered, text conditional — an empty row still occupies + its shared track (0-height unless a row sibling needs it), same + reasoning as the price/header rows above. */} +

{anyLowStock ? "Nur noch wenige verfügbar" : null}

+ + {/* self-end pins the button to the bottom of this row even though + the row itself is minmax(0,1fr) (see grid-auto-rows) and can be + taller than the button — replaces the old flex-1 spacer. */} +
+ // No items-start, and grid-auto-rows + subgrid on each RevealItem — + // see ProductGrid.tsx's own comment on why both are needed for + // ProductCard's internal rows (price, etc.) to actually line up + // across cards, not just overall card height. + {visibleEntries.map(({ item, product }) => ( - + p.id).join(",")} - className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-12 gap-6 sm:gap-[var(--layout-grid-gap)] w-full" + className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-12 gap-6 sm:gap-[var(--layout-grid-gap)] [grid-auto-rows:auto_auto_auto_auto_auto_minmax(0,1fr)] w-full" > {products.map((product) => ( - + } - className="h-full" /> ))}