Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2eb7bcb9e | |||
| 3a50853045 | |||
| 1aec378997 | |||
| aa6c9de82e | |||
| 290b651435 | |||
| 3ee5a3cfba | |||
| 4f96977d28 | |||
| 2e27f3fdf9 | |||
| e3e977c0ce | |||
| 5f0303afed | |||
| 973b38f7a0 | |||
| 9a3a179ad4 | |||
| 2745a435fb | |||
| 174847fd3e | |||
| b1bfb8dacf |
2734
src/app/layout.tsx
2734
src/app/layout.tsx
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import HeroBackgrounds, { type HeroBackgroundVariantProps } from "@/components/b
|
|||||||
import LogoMarquee, { type MarqueeItem } from "@/components/shared/LogoMarquee";
|
import LogoMarquee, { type MarqueeItem } from "@/components/shared/LogoMarquee";
|
||||||
import { cls } from "@/lib/utils";
|
import { cls } from "@/lib/utils";
|
||||||
import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
|
import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
|
||||||
|
import { useState } from "react";
|
||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
import type { ButtonConfig, ButtonAnimationType } from "@/types/button";
|
import type { ButtonConfig, ButtonAnimationType } from "@/types/button";
|
||||||
import type { Avatar } from "@/components/shared/AvatarGroup";
|
import type { Avatar } from "@/components/shared/AvatarGroup";
|
||||||
@@ -61,7 +62,7 @@ interface HeroBillboardProps {
|
|||||||
buttonTextClassName?: string;
|
buttonTextClassName?: string;
|
||||||
mediaWrapperClassName?: string;
|
mediaWrapperClassName?: string;
|
||||||
imageClassName?: string;
|
imageClassName?: string;
|
||||||
marqueeClassName?: string;
|
marqueeClassName?: string;
|
||||||
marqueeItemClassName?: string;
|
marqueeItemClassName?: string;
|
||||||
marqueeCardClassName?: string;
|
marqueeCardClassName?: string;
|
||||||
marqueeImageClassName?: string;
|
marqueeImageClassName?: string;
|
||||||
@@ -70,45 +71,46 @@ interface HeroBillboardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HeroBillboard = ({
|
const HeroBillboard = ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
background,
|
background,
|
||||||
tag,
|
tag,
|
||||||
tagIcon,
|
tagIcon,
|
||||||
tagAnimation,
|
tagAnimation,
|
||||||
buttons,
|
buttons,
|
||||||
buttonAnimation,
|
buttonAnimation,
|
||||||
avatars,
|
avatars,
|
||||||
avatarText,
|
avatarText,
|
||||||
imageSrc,
|
imageSrc,
|
||||||
videoSrc,
|
videoSrc,
|
||||||
imageAlt = "",
|
imageAlt = "",
|
||||||
videoAriaLabel = "Hero video",
|
videoAriaLabel = "Hero video",
|
||||||
mediaAnimation = "none",
|
mediaAnimation = "none",
|
||||||
marqueeItems,
|
marqueeItems,
|
||||||
marqueeSpeed = 30,
|
marqueeSpeed = 30,
|
||||||
showMarqueeCard = true,
|
showMarqueeCard = true,
|
||||||
ariaLabel = "Hero section",
|
ariaLabel = "Hero section",
|
||||||
className = "",
|
className = "",
|
||||||
containerClassName = "",
|
containerClassName = "",
|
||||||
textBoxClassName = "",
|
textBoxClassName = "",
|
||||||
titleClassName = "",
|
titleClassName = "",
|
||||||
descriptionClassName = "",
|
descriptionClassName = "",
|
||||||
tagClassName = "",
|
tagClassName = "",
|
||||||
avatarGroupClassName = "",
|
avatarGroupClassName = "",
|
||||||
buttonContainerClassName = "",
|
buttonContainerClassName = "",
|
||||||
buttonClassName = "",
|
buttonClassName = "",
|
||||||
buttonTextClassName = "",
|
buttonTextClassName = "",
|
||||||
mediaWrapperClassName = "",
|
mediaWrapperClassName = "",
|
||||||
imageClassName = "",
|
imageClassName = "",
|
||||||
marqueeClassName = "",
|
marqueeClassName = "",
|
||||||
marqueeItemClassName = "",
|
marqueeItemClassName = "",
|
||||||
marqueeCardClassName = "",
|
marqueeCardClassName = "",
|
||||||
marqueeImageClassName = "",
|
marqueeImageClassName = "",
|
||||||
marqueeTextClassName = "",
|
marqueeTextClassName = "",
|
||||||
marqueeIconClassName = "",
|
marqueeIconClassName = "",
|
||||||
}: HeroBillboardProps) => {
|
}: HeroBillboardProps) => {
|
||||||
const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation });
|
const { containerRef: mediaContainerRef } = useButtonAnimation({ animationType: mediaAnimation });
|
||||||
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
@@ -139,17 +141,35 @@ const HeroBillboard = ({
|
|||||||
center={true}
|
center={true}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-6" >
|
<div className="flex flex-col gap-6" >
|
||||||
<div ref={mediaContainerRef} className={cls("w-full overflow-hidden rounded-theme-capped card p-4 [perspective:1000px]", mediaWrapperClassName)}>
|
<div
|
||||||
<div className="w-full h-full transition-transform duration-500 [transform-style:preserve-3d] hover:[transform:rotateY(180deg)]">
|
ref={mediaContainerRef}
|
||||||
<MediaContent
|
className={cls("w-full overflow-hidden rounded-theme-capped card p-4 [perspective:1000px]", mediaWrapperClassName)}
|
||||||
imageSrc={imageSrc}
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
videoSrc={videoSrc}
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
imageAlt={imageAlt}
|
>
|
||||||
videoAriaLabel={videoAriaLabel}
|
<style>{`
|
||||||
imageClassName={cls("z-1 aspect-square md:aspect-video", imageClassName)}
|
@keyframes infiniteSpin {
|
||||||
/>
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.spin-on-hover {
|
||||||
|
animation: infiniteSpin 8s linear infinite;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<div className={cls("w-full h-full [transform-style:preserve-3d]", isHovering && "spin-on-hover")}>
|
||||||
|
<MediaContent
|
||||||
|
imageSrc={imageSrc}
|
||||||
|
videoSrc={videoSrc}
|
||||||
|
imageAlt={imageAlt}
|
||||||
|
videoAriaLabel={videoAriaLabel}
|
||||||
|
imageClassName={cls("z-1 aspect-square md:aspect-video", imageClassName)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{marqueeItems && marqueeItems.length > 0 && (
|
{marqueeItems && marqueeItems.length > 0 && (
|
||||||
<LogoMarquee
|
<LogoMarquee
|
||||||
items={marqueeItems}
|
items={marqueeItems}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
import { memo, useCallback } from "react";
|
import { memo, useCallback } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { ArrowUpRight } from "lucide-react";
|
import { ArrowUpRight } from "lucide-react";
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import CardStack from "@/components/cardStack/CardStack";
|
import CardStack from "@/components/cardStack/CardStack";
|
||||||
import ProductImage from "@/components/shared/ProductImage";
|
import ProductImage from "@/components/shared/ProductImage";
|
||||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
||||||
@@ -63,6 +66,95 @@ interface ProductCardItemProps {
|
|||||||
cardPriceClassName?: string;
|
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(({
|
const ProductCardItem = memo(({
|
||||||
product,
|
product,
|
||||||
shouldUseLightText,
|
shouldUseLightText,
|
||||||
@@ -72,7 +164,27 @@ const ProductCardItem = memo(({
|
|||||||
cardPriceClassName = "",
|
cardPriceClassName = "",
|
||||||
}: ProductCardItemProps) => {
|
}: ProductCardItemProps) => {
|
||||||
return (
|
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)}
|
className={cls("card group relative h-full flex flex-col gap-4 cursor-pointer p-4 rounded-theme-capped", cardClassName)}
|
||||||
onClick={product.onProductClick}
|
onClick={product.onProductClick}
|
||||||
role="article"
|
role="article"
|
||||||
|
|||||||
Reference in New Issue
Block a user