Redesign storefront to match minimal reference design

Product cards, header, footer, PDP, and typography reworked toward a
leaner layout; adds shop policy pages backed by the Storefront API.

- Type: switch to Geist Sans/Mono, regular-weight headings
- Product cards: drop borders, rounded corners, and action buttons
- Header: shorter bar, no bottom border, center-out hover underline,
  bag icon replacing the cart icon (drawer wording updated to match)
- Footer: single line with policy links and social icons on bg-background
- Policies: /policies/[handle] renders shop.privacyPolicy,
  termsOfService, refundPolicy, shippingPolicy, subscriptionPolicy
  (SSG, hourly revalidation)
- PDP: image grid with mobile carousel + dots and click-to-zoom,
  sticky info column, colour swatches from option optionValues with a
  configurable name-to-colour fallback in config/swatches.ts,
  Shop-purple checkout button
- Recommendations: left-aligned heading on bg-background
- Ignore .env*.local and tsconfig.tsbuildinfo

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jWbNNJLksC1QG8z8845FX
This commit is contained in:
Rami Bitar
2026-08-01 11:11:25 -04:00
co-authored by Claude Opus 5
parent e3d5e75299
commit 69f7435d6e
25 changed files with 1055 additions and 593 deletions
+65 -77
View File
@@ -2,11 +2,10 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import { useProduct, type Product } from '@/hooks/use-shopify-products';
import { useShopifyCart } from '@/hooks/use-shopify-cart';
import { useShopifyCart, redirectToCheckout } from '@/hooks/use-shopify-cart';
import ProductDetailGallery from './product-detail-gallery';
import ProductDetailInfo, { type ProductFeature } from './product-detail-info';
import ProductDetailInfo from './product-detail-info';
import { Button } from '@/components/ui/button';
import {
Empty,
@@ -15,14 +14,6 @@ import {
EmptyDescription,
EmptyContent,
} from '@/components/ui/empty';
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
interface ProductVariant {
id: string;
@@ -47,17 +38,15 @@ export type { Product };
interface ProductDetailProps {
handle?: string;
addToCartLabel?: string;
features?: ProductFeature[];
}
const ProductDetail: React.FC<ProductDetailProps> = ({
handle: handleProp,
addToCartLabel = 'Add to Cart',
features,
}) => {
const params = useParams();
const handle = handleProp || (params?.handle as string);
const { addItem, openCart } = useShopifyCart();
const { addItem, openCart, checkoutUrl } = useShopifyCart();
const { product, loading, error } = useProduct(handle);
@@ -68,8 +57,8 @@ const ProductDetail: React.FC<ProductDetailProps> = ({
Record<string, string>
>({});
const [quantity, setQuantity] = useState(1);
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
const [addingToCart, setAddingToCart] = useState(false);
const [buyingNow, setBuyingNow] = useState(false);
// Initialize variant when product loads
useEffect(() => {
@@ -102,17 +91,6 @@ const ProductDetail: React.FC<ProductDetailProps> = ({
if (matchingVariant) {
setSelectedVariant(matchingVariant.node);
// Update image if variant has an associated image
if (matchingVariant.node.image && product) {
const variantImageUrl = matchingVariant.node.image.url;
const imageIndex = product.images.edges.findIndex(
(edge) => edge.node.url === variantImageUrl
);
if (imageIndex !== -1) {
setSelectedImageIndex(imageIndex);
}
}
}
};
@@ -130,30 +108,50 @@ const ProductDetail: React.FC<ProductDetailProps> = ({
}
};
// Adds the item, then sends the shopper straight to the Shopify checkout
// (where Shop Pay is offered) rather than opening the cart drawer.
const handleBuyNow = async () => {
if (!selectedVariant || !product) return;
try {
setBuyingNow(true);
const updatedCart = await addItem(selectedVariant.id, quantity);
const url = updatedCart?.checkoutUrl ?? checkoutUrl;
if (url) {
redirectToCheckout(url);
} else {
openCart();
}
} catch (err) {
console.error('Failed to start checkout:', err);
} finally {
setBuyingNow(false);
}
};
if (loading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
<div className="max-w-screen-2xl mx-auto px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-10">
{/* Image Gallery Skeleton */}
<div>
<div className="aspect-square bg-gray-200 rounded-lg animate-pulse mb-4"></div>
<div className="grid grid-cols-4 gap-2">
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className="aspect-square bg-gray-200 rounded animate-pulse"
></div>
))}
</div>
<div className="lg:col-span-3 grid grid-cols-1 sm:grid-cols-2 gap-3">
{Array.from({ length: 4 }).map((_, i) => (
<div
key={i}
className="aspect-square bg-zinc-100 animate-pulse"
></div>
))}
</div>
{/* Product Info Skeleton */}
<div>
<div className="h-8 bg-gray-200 rounded mb-4 animate-pulse"></div>
<div className="h-6 bg-gray-200 rounded mb-6 w-1/3 animate-pulse"></div>
<div className="h-24 bg-gray-200 rounded mb-6 animate-pulse"></div>
<div className="h-12 bg-gray-200 rounded mb-4 animate-pulse"></div>
<div className="h-12 bg-gray-200 rounded animate-pulse"></div>
<div className="lg:col-span-2 animate-pulse">
<div className="h-8 bg-zinc-100 w-2/3"></div>
<div className="h-5 bg-zinc-100 w-24 mt-2"></div>
<div className="h-8 bg-zinc-100 w-32 mt-8"></div>
<div className="h-10 bg-zinc-100 mt-8"></div>
<div className="h-12 bg-zinc-100 mt-8"></div>
<div className="h-12 bg-zinc-100 mt-3"></div>
</div>
</div>
</div>
@@ -181,39 +179,29 @@ const ProductDetail: React.FC<ProductDetailProps> = ({
}
return (
<div className="bg-white">
<div className="container mx-auto px-4 py-8">
<Breadcrumb className="mb-6">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href="/">Home</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>{product.title}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
<ProductDetailGallery
images={product.images.edges.map((edge) => edge.node)}
selectedImageIndex={selectedImageIndex}
onImageSelect={setSelectedImageIndex}
/>
<ProductDetailInfo
product={product}
selectedVariant={selectedVariant}
selectedOptions={selectedOptions}
quantity={quantity}
setQuantity={setQuantity}
handleAddToCart={handleAddToCart}
onOptionChange={handleOptionChange}
loading={addingToCart}
addToCartLabel={addToCartLabel}
features={features}
/>
<div className="bg-background">
<div className="max-w-screen-2xl mx-auto px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-10 items-start">
<div className="lg:col-span-3">
<ProductDetailGallery
images={product.images.edges.map((edge) => edge.node)}
/>
</div>
<div className="lg:col-span-2 lg:sticky lg:top-20">
<ProductDetailInfo
product={product}
selectedVariant={selectedVariant}
selectedOptions={selectedOptions}
quantity={quantity}
setQuantity={setQuantity}
handleAddToCart={handleAddToCart}
handleBuyNow={handleBuyNow}
onOptionChange={handleOptionChange}
loading={addingToCart}
buyingNow={buyingNow}
addToCartLabel={addToCartLabel}
/>
</div>
</div>
</div>
</div>
@@ -1,4 +1,7 @@
import React from 'react';
'use client';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { RiCloseLine } from '@remixicon/react';
interface ProductImage {
url: string;
@@ -7,57 +10,148 @@ interface ProductImage {
interface ProductDetailGalleryProps {
images: ProductImage[];
selectedImageIndex?: number;
onImageSelect?: (index: number) => void;
}
const ProductDetailGallery: React.FC<ProductDetailGalleryProps> = ({
images,
selectedImageIndex = 0,
onImageSelect,
}) => {
const setSelectedImage = onImageSelect || (() => {});
const [zoomedIndex, setZoomedIndex] = useState<number | null>(null);
const [activeIndex, setActiveIndex] = useState(0);
const scrollerRef = useRef<HTMLDivElement>(null);
const isZoomed = zoomedIndex !== null;
const close = useCallback(() => setZoomedIndex(null), []);
// The slide whose left edge sits closest to the scroller's left edge is the
// one in view. Measuring rects keeps this correct whatever the gap or width.
const handleScroll = useCallback(() => {
const scroller = scrollerRef.current;
if (!scroller) return;
const scrollerLeft = scroller.getBoundingClientRect().left;
let nearest = 0;
let smallestOffset = Infinity;
Array.from(scroller.children).forEach((child, index) => {
const offset = Math.abs(child.getBoundingClientRect().left - scrollerLeft);
if (offset < smallestOffset) {
smallestOffset = offset;
nearest = index;
}
});
setActiveIndex(nearest);
}, []);
const scrollToIndex = (index: number) => {
const scroller = scrollerRef.current;
const slide = scroller?.children[index];
if (!scroller || !slide) return;
const offset =
slide.getBoundingClientRect().left - scroller.getBoundingClientRect().left;
scroller.scrollTo({ left: scroller.scrollLeft + offset, behavior: 'smooth' });
};
// Close on Escape, and keep the page behind the overlay from scrolling.
useEffect(() => {
if (!isZoomed) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') close();
};
document.addEventListener('keydown', onKeyDown);
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKeyDown);
document.body.style.overflow = previousOverflow;
};
}, [isZoomed, close]);
if (images.length === 0) {
return (
<div className="aspect-square bg-zinc-50 flex items-center justify-center text-zinc-300">
<i className="ri-image-line text-[120px]"></i>
</div>
);
}
const isSingle = images.length === 1;
const zoomedImage = zoomedIndex !== null ? images[zoomedIndex] : null;
return (
<div className="space-y-4">
{/* Main Image */}
<div className="product-gallery-main aspect-square bg-zinc-50 border border-border rounded-3xl overflow-hidden shadow-inner relative">
{images.length > 0 ? (
<img
src={images[selectedImageIndex].url}
alt={images[selectedImageIndex].altText || 'Product image'}
className="w-full h-full object-cover transition-all hover:scale-105 duration-700"
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-zinc-300">
<i className="ri-image-line text-[120px]"></i>
</div>
)}
<>
{/* Swipeable carousel on mobile, grid from sm up */}
<div
ref={scrollerRef}
onScroll={handleScroll}
className="flex snap-x snap-mandatory gap-3 overflow-x-auto no-scrollbar sm:grid sm:grid-cols-2 sm:overflow-visible"
>
{images.map((image, index) => (
<button
key={index}
onClick={() => setZoomedIndex(index)}
aria-label={`Zoom ${image.altText || 'product image'}`}
className={`relative aspect-square w-full shrink-0 snap-center overflow-hidden bg-white cursor-zoom-in sm:shrink ${
isSingle ? 'sm:col-span-2' : ''
}`}
>
<img
src={image.url}
alt={image.altText || 'Product image'}
className="w-full h-full object-cover"
/>
</button>
))}
</div>
{/* Image Thumbnails */}
{images.length > 1 && (
<div className="grid grid-cols-4 gap-3">
{images.map((image, index) => (
{/* Carousel pagination — the grid needs no dots, so mobile only */}
{!isSingle && (
<div className="flex justify-center gap-2 pt-4 sm:hidden">
{images.map((_, index) => (
<button
key={index}
onClick={() => setSelectedImage(index)}
className={`aspect-square rounded-2xl overflow-hidden border transition-all duration-200 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-primary/30 ${
selectedImageIndex === index
? 'border-primary shadow-md scale-[1.03]'
: 'border-border hover:border-zinc-300'
onClick={() => scrollToIndex(index)}
aria-label={`Go to image ${index + 1}`}
aria-current={index === activeIndex}
className={`h-1.5 rounded-full transition-all ${
index === activeIndex
? 'w-5 bg-foreground'
: 'w-1.5 bg-border hover:bg-foreground/40'
}`}
>
<img
src={image.url}
alt={image.altText || 'Product thumbnail'}
className="w-full h-full object-cover"
/>
</button>
/>
))}
</div>
)}
</div>
{zoomedImage && (
<div
role="dialog"
aria-modal="true"
aria-label={zoomedImage.altText || 'Product image'}
onClick={close}
className="fixed inset-0 z-100 flex items-center justify-center bg-foreground/20 backdrop-blur-md p-6 md:p-10"
>
<img
src={zoomedImage.url}
alt={zoomedImage.altText || 'Product image'}
onClick={(event) => event.stopPropagation()}
className="max-h-full max-w-full object-contain bg-white"
/>
<button
onClick={close}
aria-label="Close"
className="absolute top-4 right-4 h-10 w-10 rounded-full bg-background text-foreground flex items-center justify-center shadow-sm hover:bg-secondary transition-colors"
>
<RiCloseLine size={20} />
</button>
</div>
)}
</>
);
};
@@ -1,8 +1,9 @@
import React from 'react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Loader } from '@/app/components/ui/loader';
import RemixIcon from '@/components/ui/remix-icon';
import { RiSubtractLine, RiAddLine } from '@remixicon/react';
import ShopPayLogo from '@/components/shopify/shop-pay-logo';
import type { ProductOption, ProductOptionValue } from '@/hooks/use-shopify-products';
import { isSwatchOptionName, swatchColorForName } from '@/config/swatches';
interface ProductPrice {
amount: string;
@@ -20,12 +21,6 @@ interface ProductVariant {
}>;
}
interface ProductOption {
id: string;
name: string;
values: string[];
}
interface Product {
id: string;
title: string;
@@ -53,12 +48,29 @@ interface ProductDetailInfoProps {
quantity: number;
setQuantity: (quantity: number) => void;
handleAddToCart: () => void;
handleBuyNow?: () => void;
onOptionChange: (optionName: string, value: string) => void;
loading?: boolean;
buyingNow?: boolean;
addToCartLabel?: string;
features?: ProductFeature[];
}
// A swatch comes from the option value's own swatch (colour or image) or the
// colour its name implies (see config/swatches) — never a variant photo.
const swatchStyle = (
value: ProductOptionValue
): { background?: string; image?: string } => {
if (value.swatch?.color) return { background: value.swatch.color };
const swatchImage = value.swatch?.image?.previewImage?.url;
if (swatchImage) return { image: swatchImage };
const namedColor = swatchColorForName(value.name);
if (namedColor) return { background: namedColor };
return {};
};
const ProductDetailInfo: React.FC<ProductDetailInfoProps> = ({
product,
selectedVariant,
@@ -66,14 +78,11 @@ const ProductDetailInfo: React.FC<ProductDetailInfoProps> = ({
quantity,
setQuantity,
handleAddToCart,
handleBuyNow,
onOptionChange,
loading = false,
buyingNow = false,
addToCartLabel = 'Add to Cart',
features = [
{ icon: 'RiTruckLine', label: 'Free shipping on orders over $100' },
{ icon: 'RiArrowGoBackLine', label: '30-day return policy' },
{ icon: 'RiSecurePaymentLine', label: 'Secure payment' },
],
}) => {
const formatPrice = (amount: string) => {
return `$${parseFloat(amount).toFixed(2)}`;
@@ -84,48 +93,152 @@ const ProductDetailInfo: React.FC<ProductDetailInfoProps> = ({
const hasDiscount =
compareAtPrice &&
parseFloat(compareAtPrice.amount) > parseFloat(price.amount);
const isAvailable = selectedVariant?.availableForSale ?? false;
const isSwatchOption = (option: ProductOption) =>
isSwatchOptionName(option.name);
// Some products return Size before Color; show the swatches first either way.
const orderedOptions = [...(product.options ?? [])].sort(
(a, b) => Number(isSwatchOption(b)) - Number(isSwatchOption(a))
);
// `optionValues` carries the swatch data; fall back to plain `values`.
const optionValuesFor = (option: ProductOption): ProductOptionValue[] =>
option.optionValues?.length
? option.optionValues
: option.values.map((value) => ({ id: value, name: value }));
return (
<div className="pt-2">
<div className="mb-2">
<div className="uppercase tracking-[3px] text-xs font-mono text-muted-foreground mb-1">
LUMINA COLLECTION
</div>
<h1 className="text-5xl md:text-6xl font-bold tracking-tighter font-heading text-foreground leading-none mb-6">
{product.title}
</h1>
</div>
{/* Price */}
<div className="flex items-center gap-x-4 mb-10">
<span className="text-4xl font-semibold tracking-tighter text-foreground price-display">
<div>
<h1 className="text-2xl md:text-3xl font-normal text-foreground">
{product.title}
</h1>
<div className="flex items-baseline gap-x-3 mt-1">
<span className="font-mono tabular-nums tracking-tight text-base text-foreground">
{formatPrice(price.amount)}
</span>
{hasDiscount && compareAtPrice && (
<>
<span className="text-2xl text-muted-foreground line-through tracking-tight">
{formatPrice(compareAtPrice.amount)}
</span>
<Badge
variant="destructive"
className="text-xs font-mono px-4 py-1"
>
SAVE{' '}
{Math.round(
((parseFloat(compareAtPrice.amount) -
parseFloat(price.amount)) /
parseFloat(compareAtPrice.amount)) *
100
)}
%
</Badge>
</>
<span className="font-mono tabular-nums tracking-tight text-sm line-through text-muted-foreground">
{formatPrice(compareAtPrice.amount)}
</span>
)}
</div>
{/* Product Options — colour swatches lead, whatever order the API returns */}
{orderedOptions.map((option) => {
const isSwatch = isSwatchOption(option);
const selected = selectedOptions[option.name];
return (
<div key={option.id} className="mt-8">
<div className="text-sm text-muted-foreground mb-2">
{option.name}
{isSwatch && selected && (
<span className="text-foreground font-medium">: {selected}</span>
)}
</div>
<div className="flex flex-wrap gap-2">
{optionValuesFor(option).map((value) => {
const isSelected = selected === value.name;
if (isSwatch) {
const { background, image } = swatchStyle(value);
return (
<button
key={value.id}
onClick={() => onOptionChange(option.name, value.name)}
title={value.name}
aria-label={value.name}
aria-pressed={isSelected}
className={`h-8 w-8 rounded-full bg-cover bg-center transition-shadow ${
isSelected
? 'ring-2 ring-foreground ring-offset-2'
: 'ring-1 ring-border hover:ring-foreground/40'
}`}
style={{
backgroundColor: background,
backgroundImage: image ? `url(${image})` : undefined,
}}
>
{!background && !image && (
<span className="text-[10px]">{value.name.at(0)}</span>
)}
</button>
);
}
return (
<button
key={value.id}
onClick={() => onOptionChange(option.name, value.name)}
aria-pressed={isSelected}
className={`min-w-14 px-5 py-2 rounded-md border text-sm text-center transition-colors ${
isSelected
? 'border-foreground text-foreground'
: 'border-border text-muted-foreground hover:border-foreground hover:text-foreground'
}`}
>
{value.name}
</button>
);
})}
</div>
</div>
);
})}
{/* Quantity + Add to Cart */}
<div className="mt-8 flex items-stretch gap-3">
<div className="flex items-center rounded-md border border-border h-11">
<button
onClick={() => setQuantity(Math.max(1, quantity - 1))}
disabled={quantity <= 1}
aria-label="Decrease quantity"
className="h-full px-3 text-foreground disabled:text-muted-foreground/50 transition-colors"
>
<RiSubtractLine size={16} />
</button>
<span className="w-8 text-center text-sm tabular-nums">
{quantity}
</span>
<button
onClick={() => setQuantity(quantity + 1)}
aria-label="Increase quantity"
className="h-full px-3 text-foreground transition-colors"
>
<RiAddLine size={16} />
</button>
</div>
<button
onClick={handleAddToCart}
disabled={!isAvailable || loading}
className="flex-1 h-11 rounded-md bg-foreground text-background text-sm font-medium hover:bg-foreground/90 disabled:opacity-50 transition-colors flex items-center justify-center gap-x-2"
>
{loading && <Loader size={16} />}
{isAvailable ? addToCartLabel : 'Out of Stock'}
</button>
</div>
{/* Sends the shopper to the Shopify checkout, where Shop Pay is offered. */}
{handleBuyNow && (
<button
type="button"
onClick={handleBuyNow}
disabled={!isAvailable || buyingNow}
className="mt-3 flex h-11 w-full cursor-pointer items-center justify-center rounded-md bg-shop px-4 text-white transition-colors hover:bg-shop/85 disabled:cursor-not-allowed disabled:opacity-50"
>
<span className="sr-only">Buy with</span>
{buyingNow ? <Loader size={16} /> : <ShopPayLogo />}
</button>
)}
{/* Description */}
{product.description && (
<div className="prose text-muted-foreground mb-10 text-[15px] leading-relaxed max-w-prose">
{(product.descriptionHtml || product.description) && (
<div className="mt-10 text-sm leading-6 text-foreground product-description">
{product.descriptionHtml ? (
<div
dangerouslySetInnerHTML={{ __html: product.descriptionHtml }}
@@ -135,104 +248,6 @@ const ProductDetailInfo: React.FC<ProductDetailInfoProps> = ({
)}
</div>
)}
{/* Product Options */}
{product.options &&
product.options.map((option) => (
<div key={option.id} className="mb-8">
<div className="text-xs uppercase font-mono tracking-widest text-muted-foreground mb-3">
{option.name}
</div>
<div className="flex flex-wrap gap-2">
{option.values.map((value) => (
<Button
key={value}
onClick={() => onOptionChange(option.name, value)}
variant={
selectedOptions[option.name] === value
? 'default'
: 'outline'
}
className={`rounded-2xl px-6 py-2.5 text-sm font-medium transition-all ${
selectedOptions[option.name] === value ? 'shadow-md' : ''
}`}
>
{value}
</Button>
))}
</div>
</div>
))}
{/* Quantity Selector */}
<div className="mb-8">
<div className="text-xs uppercase font-mono tracking-widest text-muted-foreground mb-3">
QUANTITY
</div>
<div className="inline-flex items-center border border-border rounded-2xl">
<Button
onClick={() => setQuantity(Math.max(1, quantity - 1))}
variant="ghost"
size="icon-sm"
className="rounded-l-2xl h-12 w-12"
disabled={quantity <= 1}
>
</Button>
<div className="px-8 font-mono text-lg font-semibold tabular-nums">
{quantity}
</div>
<Button
onClick={() => setQuantity(quantity + 1)}
variant="ghost"
size="icon-sm"
className="rounded-r-2xl h-12 w-12"
>
+
</Button>
</div>
</div>
{/* Add to Cart Button */}
<Button
onClick={handleAddToCart}
disabled={!selectedVariant?.availableForSale || loading}
size="lg"
className="w-full h-16 text-base font-medium tracking-wider rounded-3xl btn-modern"
>
{loading ? (
<>
<Loader size={18} className="mr-3" />
ADDING TO BAG...
</>
) : selectedVariant?.availableForSale ? (
String(addToCartLabel ?? 'Add to Cart').toUpperCase()
) : (
'OUT OF STOCK'
)}
</Button>
{/* Features */}
{features.length > 0 && (
<div className="mt-12 pt-10 border-t border-border">
<div className="space-y-6">
{features.map((feature, index) => (
<div key={index} className="flex gap-x-4 text-sm">
<div className="mt-0.5 text-primary">
<RemixIcon name={feature.icon} size={18} />
</div>
<div className="text-muted-foreground leading-snug">
{feature.label}
</div>
</div>
))}
</div>
</div>
)}
<div className="mt-12 text-[10px] font-mono text-center text-muted-foreground tracking-widest">
FREE SHIPPING EASY RETURNS SECURE CHECKOUT
</div>
</div>
);
};