From 88b009edadc89bd669e3296c8837be49c90e591b Mon Sep 17 00:00:00 2001 From: Reed Date: Tue, 4 Aug 2026 18:54:42 +0000 Subject: [PATCH 1/4] fix(BUY-64266): drop search card image hover scale to stop rightmost-card overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA (Hermes SOUL 2026-07-25, VidMee vidmee_ss_aeeb690895e31d361737ec6c) saw product card thumbnails extend beyond the rightmost grid column on desktop multi-column search results. BUY-64736 (just merged to main, commit 294633d91) already capped the image at max-h-[220px] for the parent-reflow symptom, but kept group-hover:scale-[1.03] on the . The hover scale was the residual BUY-64266 cause: on hover the image grew to 103% of its 4:3 frame and, with the parent reflow now bounded, still visibly clipped at the column edge on the rightmost card. Fix: drop transition-transform duration-300 group-hover:scale-[1.03] from the SearchCard and add max-w-full so the natural width can't push past the card column either. BUY-64736's max-h-[220px] + aspect-ratio:4/3 + object-contain are preserved — image still letterboxes inside its frame. Files touched: src/app/search/SearchResultsClient.tsx (SearchCard) --- src/app/search/SearchResultsClient.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/search/SearchResultsClient.tsx b/src/app/search/SearchResultsClient.tsx index 356c3b2bf..521c74a57 100644 --- a/src/app/search/SearchResultsClient.tsx +++ b/src/app/search/SearchResultsClient.tsx @@ -445,7 +445,11 @@ function SearchCard({ product }: { product: SearchCardProduct }) { onError={() => { setImageError(true); }} - className="relative z-10 block h-full w-full max-h-[220px] object-contain p-2 transition-transform duration-300 group-hover:scale-[1.03]" + // BUY-64266: drop group-hover:scale-[1.03] which pushed the rightmost + // card image beyond the grid column on desktop. Keep BUY-64736's + // max-h-[220px] / max-w-full / object-contain bounds so the image + // can never exceed its 220px-tall card frame. + className="relative z-10 block h-full w-full max-h-[220px] max-w-full object-contain p-2" style={{ maxHeight: '220px', width: '100%', objectFit: 'contain' }} /> ) : imageError || !product.imageUrl ? ( From d3f80310bf756801b4dfb132c250f5545d790cd4 Mon Sep 17 00:00:00 2001 From: Rex Date: Tue, 4 Aug 2026 20:00:05 +0000 Subject: [PATCH 2/4] fix(BUY-64266): object-contain on product card images prevents overflow Before: fill + object-cover cropped tall/wide images, causing overflow on multi-column grid (rightmost card bled past viewport). After: object-contain ensures images display fully within their cell; aspect-square wrapper with overflow-hidden clips any remaining overflow. Co-Authored-By: Claude --- src/components/ProductCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ProductCard.tsx b/src/components/ProductCard.tsx index b831ec07c..cfad3ee71 100644 --- a/src/components/ProductCard.tsx +++ b/src/components/ProductCard.tsx @@ -148,8 +148,8 @@ export const ProductCard = React.memo(function ProductCard({ deal, comparisonEna alt={deal.name} fill sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw" - className="object-cover" - style={{ width: '100%', height: '100%', objectFit: 'cover' }} + className="object-contain" + style={{ width: '100%', height: '100%', objectFit: 'contain' }} loading={deal.id <= 4 ? 'eager' : 'lazy'} priority={deal.id <= 2} /> From 008b48878c82d5a01fe28bc79a0617b3b3f5a92c Mon Sep 17 00:00:00 2001 From: Rex Date: Tue, 4 Aug 2026 20:10:32 +0000 Subject: [PATCH 3/4] fix(BUY-64266): remove redundant flex wrapper and inline styles on product card images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit d3f80310b fixed object-cover → object-contain. This follow-up removes: - flex centering wrapper (fill + object-contain handles positioning) - redundant inline style (className object-contain already sets it) - gradient bg from image div (only needed for icon fallback) Co-Authored-By: Claude --- src/components/ProductCard.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/ProductCard.tsx b/src/components/ProductCard.tsx index cfad3ee71..22aa1eae9 100644 --- a/src/components/ProductCard.tsx +++ b/src/components/ProductCard.tsx @@ -140,8 +140,8 @@ export const ProductCard = React.memo(function ProductCard({ deal, comparisonEna rel="noopener noreferrer" className="group block bg-white rounded-xl border border-gray-100 overflow-hidden hover:shadow-lg hover:border-indigo-100 transition-all duration-200" > -
-
+
+
{deal.image_url ? ( ) : ( - {config.icon} +
+ {config.icon} +
)}
{deal.discount_pct && deal.discount_pct > 0 && ( From 59c9c4836a344f69e596314a73666f312205eab6 Mon Sep 17 00:00:00 2001 From: Rex Date: Wed, 5 Aug 2026 07:55:08 +0000 Subject: [PATCH 4/4] fix(BUY-66322): single-column hero checkmark list on SEO landing pages Convert the page-metadata
    in SeoLandingPage from flex flex-wrap to a vertical space-y-2 stack. The previous wrap layout could flow a single check item onto a second row, presenting an empty 'bottom-right cell' that VidMee flags on /air-purifier-singapore. --- src/components/seo/SeoLandingPage.tsx | 36 ++++++++++++++++----------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/seo/SeoLandingPage.tsx b/src/components/seo/SeoLandingPage.tsx index 37e0d5bdd..96efdba57 100644 --- a/src/components/seo/SeoLandingPage.tsx +++ b/src/components/seo/SeoLandingPage.tsx @@ -145,7 +145,7 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig
    -
    +
    {config.heroEyebrow}

    @@ -154,17 +154,25 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig

    {config.heroBody}

    -
      -
    • - + {/* + BUY-66322: single-column layout for the hero checkmark list. + The previous `flex flex-wrap gap-x-6 gap-y-2` layout could wrap + to two rows on narrow/mid viewports, leaving a perceived empty + bottom-right cell (VidMee layout flag). A vertical stack is + unambiguous and matches the editorial intent (page metadata, + not a feature grid). + */} +
        +
      • + {buildRefreshedLabel(config, products)}
      • -
      • - +
      • + {config.country} market coverage
      • -
      • - +
      • + Live BuyWhere search results
      @@ -177,7 +185,7 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig
      {shopperCta.label} @@ -196,10 +204,10 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig
      -

      Live catalog snapshot

      +

      Live catalog snapshot

      {config.productSectionTitle}

      - + Open full search
      @@ -275,7 +283,7 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig
      -

      {config.categoryComparisonEyebrow || "Featured models"}

      +

      {config.categoryComparisonEyebrow || "Featured models"}

      {config.categoryComparisonTitle || "Models compared"}

      @@ -313,7 +321,7 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig
      -

      Buying signals

      +

      Buying signals

      {config.highlightSectionTitle}

      {config.highlights.map((highlight) => ( @@ -332,7 +340,7 @@ export async function SeoLandingPage({ config }: { config: SeoLandingPageConfig
        {config.advicePoints.map((point) => (
      • - + {point}