4 Commits

Author SHA1 Message Date
e3231fe6c1 Merge version_3_1777316718554 into main
Merge version_3_1777316718554 into main
2026-04-27 19:05:59 +00:00
kudinDmitriyUp
ebf09712ac Bob AI: Update the hero section's headline to 'Sell What They Feel, 2026-04-27 19:05:56 +00:00
91ed153d48 Merge version_2_1777290330734 into main
Merge version_2_1777290330734 into main
2026-04-27 11:47:08 +00:00
kudinDmitriyUp
070a4f65a9 Bob AI: Implement a 3D flip animation for each product card in the p 2026-04-27 11:47:04 +00:00
3 changed files with 101 additions and 39 deletions

View File

@@ -17,6 +17,7 @@ type ProductQuantityCardsProps = {
name: string;
price: string;
imageSrc: string;
description?: string;
onAddToCart?: (quantity: number) => void;
}[];
};
@@ -37,6 +38,7 @@ const ProductQuantityCards = ({
name: p.name,
price: p.price,
imageSrc: p.imageSrc,
description: p.description,
onAddToCart: undefined as ((quantity: number) => void) | undefined,
}))
: productsProp;
@@ -90,7 +92,7 @@ const ProductQuantityCards = ({
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary"/>}
{secondaryButton && <Button text={secondaryButton.text} href={secondaryButton.href} variant="secondary"animationDelay={0.1} />}
{secondaryButton && <Button text={secondaryButton.text} href={secondaryButton.href} variant="secondary" animationDelay={0.1} />}
</div>
)}
</div>
@@ -100,42 +102,87 @@ const ProductQuantityCards = ({
{products.map((product) => (
<div
key={product.name}
className="h-full flex flex-col gap-3 xl:gap-4 2xl:gap-5 p-3 xl:p-4 2xl:p-5 card rounded"
className="group perspective-1000 w-full h-full min-h-[420px]"
>
<div className="aspect-square rounded overflow-hidden">
<ImageOrVideo imageSrc={product.imageSrc} />
</div>
<div className="flex flex-col gap-3">
<h3 className="text-xl font-medium truncate">{product.name}</h3>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<button
onClick={() => handleDecrement(product.name)}
className="flex items-center justify-center size-8 rounded card cursor-pointer"
aria-label="Decrease quantity"
>
<Minus className="size-4" strokeWidth={1.5} />
</button>
<span className="w-fit text-base text-center font-medium">{getQuantity(product.name)}</span>
<button
onClick={() => handleIncrement(product.name)}
className="flex items-center justify-center size-8 rounded card cursor-pointer"
aria-label="Increase quantity"
>
<Plus className="size-4" strokeWidth={1.5} />
</button>
<div className="relative w-full h-full transform-style-preserve-3d transition-transform duration-700 group-hover:rotate-y-180">
{/* Front Face */}
<div className="absolute inset-0 backface-hidden flex flex-col gap-3 xl:gap-4 2xl:gap-5 p-3 xl:p-4 2xl:p-5 card rounded">
<div className="aspect-square rounded overflow-hidden shrink-0">
<ImageOrVideo imageSrc={product.imageSrc} />
</div>
<button
onClick={() => product.onAddToCart?.(getQuantity(product.name))}
className="h-8 px-5 rounded primary-button text-base text-primary-cta-text font-medium cursor-pointer"
>
{product.price}
</button>
<div className="flex flex-col gap-3 flex-1 justify-between">
<h3 className="text-xl font-medium truncate">{product.name}</h3>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<button
onClick={() => handleDecrement(product.name)}
className="flex items-center justify-center size-8 rounded card cursor-pointer"
aria-label="Decrease quantity"
>
<Minus className="size-4" strokeWidth={1.5} />
</button>
<span className="w-fit text-base text-center font-medium">{getQuantity(product.name)}</span>
<button
onClick={() => handleIncrement(product.name)}
className="flex items-center justify-center size-8 rounded card cursor-pointer"
aria-label="Increase quantity"
>
<Plus className="size-4" strokeWidth={1.5} />
</button>
</div>
<button
onClick={() => product.onAddToCart?.(getQuantity(product.name))}
className="h-8 px-5 rounded primary-button text-base text-primary-cta-text font-medium cursor-pointer"
>
{product.price}
</button>
</div>
</div>
</div>
{/* Back Face */}
<div className="absolute inset-0 backface-hidden rotate-y-180 flex flex-col gap-3 xl:gap-4 2xl:gap-5 p-3 xl:p-4 2xl:p-5 card rounded">
<h3 className="text-xl font-medium">{product.name}</h3>
<div className="flex-1 overflow-y-auto pr-2">
<p className="text-base leading-relaxed opacity-80">
{product.description || "Detailed product information will be displayed here. This includes specifications, materials, and care instructions to help you make an informed decision."}
</p>
</div>
<div className="flex items-center justify-between gap-3 pt-3 border-t border-foreground/10 shrink-0">
<div className="flex items-center gap-2">
<button
onClick={() => handleDecrement(product.name)}
className="flex items-center justify-center size-8 rounded card cursor-pointer"
aria-label="Decrease quantity"
>
<Minus className="size-4" strokeWidth={1.5} />
</button>
<span className="w-fit text-base text-center font-medium">{getQuantity(product.name)}</span>
<button
onClick={() => handleIncrement(product.name)}
className="flex items-center justify-center size-8 rounded card cursor-pointer"
aria-label="Increase quantity"
>
<Plus className="size-4" strokeWidth={1.5} />
</button>
</div>
<button
onClick={() => product.onAddToCart?.(getQuantity(product.name))}
className="h-8 px-5 rounded primary-button text-base text-primary-cta-text font-medium cursor-pointer"
>
{product.price}
</button>
</div>
</div>
</div>
</div>
@@ -147,4 +194,4 @@ const ProductQuantityCards = ({
);
};
export default ProductQuantityCards;
export default ProductQuantityCards;

View File

@@ -198,3 +198,18 @@ h6 {
bg, a same-color border is invisible and the button disappears. */
border: 1px solid color-mix(in srgb, var(--color-foreground) 18%, transparent);
}
@layer utilities {
.perspective-1000 {
perspective: 1000px;
}
.transform-style-preserve-3d {
transform-style: preserve-3d;
}
.backface-hidden {
backface-visibility: hidden;
}
.rotate-y-180 {
transform: rotateY(180deg);
}
}

View File

@@ -15,10 +15,10 @@ export default function HomePage() {
<RadialGradientBackground position="absolute" />
<HeroSplitVerticalMarquee
tag="Emotional Well-being"
title="Buy the Feeling You Need Today"
description="Curated products and experiences designed to spark joy, calm, energy, or connection. Shop by emotion, not category."
title="Sell What They Feel, Not What You Sell"
description="Transform your brand story into genuine emotional connection. Stop competing on features. Start competing on meaning."
primaryButton={{
text: "Explore Your Mood",
text: "Start Selling Emotionally",
href: "#products",
}}
secondaryButton={{
@@ -231,4 +231,4 @@ export default function HomePage() {
</div>
</>
);
}
}