6 Commits

Author SHA1 Message Date
b2eb7bcb9e Update src/app/layout.tsx 2026-02-26 13:35:41 +00:00
3a50853045 Merge version_11 into main
Merge version_11 into main
2026-02-26 13:23:15 +00:00
1aec378997 Bob AI: Add a 3D flip animation to product cards on hover. When flip 2026-02-26 13:22:35 +00:00
aa6c9de82e Merge version_10 into main
Merge version_10 into main
2026-02-26 13:21:13 +00:00
290b651435 Bob AI: Add hover effects to the product cards (scale, shadow, lift, 2026-02-26 13:20:32 +00:00
3ee5a3cfba Merge version_8 into main
Merge version_8 into main
2026-02-26 13:16:30 +00:00
2 changed files with 1480 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,9 @@
import { memo, useCallback } from "react";
import { useRouter } from "next/navigation";
import { ArrowUpRight } from "lucide-react";
"use client";
import { useState } from "react";
import CardStack from "@/components/cardStack/CardStack";
import ProductImage from "@/components/shared/ProductImage";
import { cls, shouldUseInvertedText } from "@/lib/utils";
@@ -63,6 +66,95 @@ interface ProductCardItemProps {
cardPriceClassName?: string;
}
const ProductCardItemFlip = memo(({
product,
shouldUseLightText,
cardClassName = "",
imageClassName = "",
cardNameClassName = "",
cardPriceClassName = "",
}: ProductCardItemProps) => {
const [isFlipped, setIsFlipped] = useState(false);
return (
<article
className="h-full perspective cursor-pointer"
onMouseEnter={() => setIsFlipped(true)}
onMouseLeave={() => setIsFlipped(false)}
>
<div
className="relative w-full h-full transition-transform duration-500 ease-out"
style={{
transformStyle: "preserve-3d",
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)",
}}
>
{/* Front side */}
<div
className={cls(
"w-full h-full",
cardClassName
)}
style={{ backfaceVisibility: "hidden" }}
>
<div className={cls("relative overflow-hidden", imageClassName)}>
<ProductImage
src={product.image}
alt={product.name}
className="w-full h-full object-cover"
/>
</div>
<div className="p-4">
<h3 className={cls("font-semibold truncate", cardNameClassName)}>
{product.name}
</h3>
<p className={cls("text-sm font-medium", cardPriceClassName)}>
${product.price}
</p>
</div>
</div>
{/* Back side */}
<div
className={cls(
"w-full h-full p-4 flex flex-col justify-center",
cardClassName
)}
style={{
backfaceVisibility: "hidden",
transform: "rotateY(180deg)",
}}
>
<div className="space-y-3">
<h3 className={cls("font-semibold text-lg", cardNameClassName)}>
{product.name}
</h3>
<p className={cls("text-sm", shouldUseLightText ? "text-gray-300" : "text-gray-600")}>
{product.description || "Premium quality product with exceptional craftsmanship"}
</p>
<div className="space-y-2">
<p className={cls("font-semibold", cardPriceClassName)}>
${product.price}
</p>
{product.specs && (
<ul className="text-xs space-y-1">
{product.specs.map((spec, idx) => (
<li key={idx} className={shouldUseLightText ? "text-gray-400" : "text-gray-500"}>
{spec}
</li>
))}
</ul>
)}
</div>
</div>
</div>
</div>
</article>
);
});
ProductCardItemFlip.displayName = "ProductCardItemFlip";
const ProductCardItem = memo(({
product,
shouldUseLightText,
@@ -72,7 +164,27 @@ const ProductCardItem = memo(({
cardPriceClassName = "",
}: ProductCardItemProps) => {
return (
<article
<ProductCardItemFlip
product={product}
shouldUseLightText={shouldUseLightText}
cardClassName={cardClassName}
imageClassName={imageClassName}
cardNameClassName={cardNameClassName}
cardPriceClassName={cardPriceClassName}
/>
);
});
const ProductCardItemOld = memo(({
product,
shouldUseLightText,
cardClassName = "",
imageClassName = "",
cardNameClassName = "",
cardPriceClassName = "",
}: ProductCardItemProps) => {
return (
<article className="transition-all duration-300 ease-out hover:scale-105 hover:shadow-lg"
className={cls("card group relative h-full flex flex-col gap-4 cursor-pointer p-4 rounded-theme-capped", cardClassName)}
onClick={product.onProductClick}
role="article"