2 Commits

Author SHA1 Message Date
fb0402b239 Bob AI: Add flip animation on hover to product cards. Implement CSS 2026-02-23 20:03:49 +00:00
f1c667b415 Merge version_1 into main
Merge version_1 into main
2026-02-23 19:53:44 +00:00

View File

@@ -111,53 +111,106 @@ const ProductCardItem = memo(({
}
}, [isFromApi, onBuyClick, product, quantity]);
const [isFlipped, setIsFlipped] = useState(false);
return (
<article
className={cls("card group relative h-full flex flex-col gap-4 cursor-pointer p-4 rounded-theme-capped", cardClassName)}
className={cls("card group relative h-full cursor-pointer", cardClassName)}
onClick={handleClick}
role="article"
aria-label={`${product.name} - ${product.price}`}
onMouseEnter={() => setIsFlipped(true)}
onMouseLeave={() => setIsFlipped(false)}
style={{
perspective: "1000px",
}}
>
<ProductImage
imageSrc={product.imageSrc}
imageAlt={product.imageAlt || product.name}
isFavorited={product.isFavorited}
onFavoriteToggle={product.onFavorite}
imageClassName={imageClassName}
/>
<div className="relative z-1 flex flex-col gap-3">
<h3 className={cls("text-xl font-medium leading-[1.15] truncate", shouldUseLightText ? "text-background" : "text-foreground", cardNameClassName)}>
{product.name}
</h3>
<div className="flex items-center justify-between gap-4">
<div className={cls("flex items-center gap-2", quantityControlsClassName)}>
<QuantityButton
onClick={handleDecrement}
ariaLabel="Decrease quantity"
Icon={Minus}
/>
<span className={cls("text-base font-medium min-w-[2ch] text-center leading-[1]", shouldUseLightText ? "text-background" : "text-foreground")}>
{quantity}
</span>
<QuantityButton
onClick={handleIncrement}
ariaLabel="Increase quantity"
Icon={Plus}
/>
</div>
<Button
{...getButtonProps(
{
text: product.price,
props: product.priceButtonProps,
},
0,
theme.defaultButtonVariant
)}
<div
style={{
position: "relative",
width: "100%",
height: "100%",
transition: "transform 0.6s",
transformStyle: "preserve-3d",
transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)",
}}
>
{/* Front of card */}
<div
style={{
position: "absolute",
width: "100%",
height: "100%",
backfaceVisibility: "hidden",
}}
className={cls("flex flex-col gap-4 p-4 rounded-theme-capped", cardClassName)}
>
<ProductImage
imageSrc={product.imageSrc}
imageAlt={product.imageAlt || product.name}
isFavorited={product.isFavorited}
onFavoriteToggle={product.onFavorite}
imageClassName={imageClassName}
/>
<div className="relative z-1 flex flex-col gap-3">
<h3 className={cls("text-xl font-medium leading-[1.15] truncate", shouldUseLightText ? "text-background" : "text-foreground", cardNameClassName)}>
{product.name}
</h3>
<div className="flex items-center justify-between gap-4">
<div className={cls("flex items-center gap-2", quantityControlsClassName)}>
<QuantityButton
onClick={handleDecrement}
ariaLabel="Decrease quantity"
Icon={Minus}
/>
<span className={cls("text-base font-medium min-w-[2ch] text-center leading-[1]", shouldUseLightText ? "text-background" : "text-foreground")}>
{quantity}
</span>
<QuantityButton
onClick={handleIncrement}
ariaLabel="Increase quantity"
Icon={Plus}
/>
</div>
<Button
{...getButtonProps(
{
text: product.price,
props: product.priceButtonProps,
},
0,
theme.defaultButtonVariant
)}
/>
</div>
</div>
</div>
{/* Back of card */}
<div
style={{
position: "absolute",
width: "100%",
height: "100%",
backfaceVisibility: "hidden",
transform: "rotateY(180deg)",
}}
className={cls("flex flex-col gap-4 p-4 rounded-theme-capped justify-center items-center", cardClassName)}
>
<div className="flex flex-col gap-3 text-center">
<h3 className={cls("text-lg font-medium leading-[1.15]", shouldUseLightText ? "text-background" : "text-foreground")}>
{product.name}
</h3>
<p className={cls("text-sm leading-relaxed", shouldUseLightText ? "text-background/80" : "text-foreground/80")}>
{product.description || "Premium quality product"}
</p>
<div className={cls("text-2xl font-bold mt-2", shouldUseLightText ? "text-background" : "text-foreground")}>
{product.price}
</div>
</div>
</div>
</div>
</article>