This commit is contained in:
mr.sabri
2026-07-22 08:52:46 +03:30
parent 55ba2df7b6
commit 89fbd6d158
5 changed files with 124 additions and 94 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ const Hero108 = ({ className }: Hero108Props) => {
<section
className={cn("bg-background py-12 font-sans md:py-20", className)}
>
<div className="container flex flex-col items-center gap-8 lg:flex-row">
<div className="flex flex-col gap-10">
<div className="container flex w-full flex-col items-center gap-8 lg:flex-row">
<div className="flex w-full flex-col gap-10 lg:w-1/2">
<h1 className="text-2xll font-semibold text-foreground md:text-2xll">
درآمد غیرفعال{" "}
<span className="relative inline-block">
+7 -4
View File
@@ -97,7 +97,7 @@ const Hero187 = ({
<div className="relative container grid gap-12 lg:grid-cols-[1fr_1.2fr]">
<div className="absolute inset-x-0 bottom-0 z-10 h-px bg-linear-to-r from-transparent via-border to-border" />
{/* Left side - Content */}
<div className="space-y-8 lg:space-y-12">
<div className="relative z-10 space-y-8 lg:space-y-12">
<div>
<h1 className="text-3xl font-semibold tracking-tight sm:text-xl md:text-2xll lg:text-2xll">
{heading}
@@ -152,19 +152,22 @@ const Hero187 = ({
/>
</div>
{/* Right side - Carousel */}
<div className="relative -mr-[max(2rem,calc((100vw-80rem)/2+2rem))] max-lg:translate-x-10">
{/* Outer-edge carousel: bleed with -me so RTL/LTR both extend away from copy */}
<div className="relative min-w-0 -me-[max(2rem,calc((100vw-80rem)/2+2rem))] max-lg:translate-x-10 rtl:max-lg:-translate-x-10">
<Carousel
dir="rtl"
className="size-full [&>div]:size-full"
setApi={setApi}
opts={{
loop: true,
direction: "rtl",
align: "start",
}}
plugins={[Autoplay({ delay: 4000, stopOnInteraction: true })]}
>
<CarouselContent className="size-full">
{activeImages.map((image, index) => (
<CarouselItem key={index}>
<CarouselItem key={index} className="overflow-hidden">
<img
src={image.src}
alt={image.alt}
+11 -9
View File
@@ -95,12 +95,12 @@ const Hero211 = ({ className }: Hero211Props) => {
<style>{css}</style>
<div className="container">
<div className="relative z-10 mx-auto flex max-w-2xl flex-col items-center justify-center gap-4">
<h1 className="font-calSans text-center text-2xll lg:text-2xll">
<h1 className="font-calSans text-center text-5xl leading-tight lg:text-6xl">
کمتر زمان صرف کنید، بیشتر بسازید
</h1>
</h1>
<p className="px-10 text-center text-muted-foreground lg:text-lg">
صدها بلاک و کامپوننت آماده مبتنی بر رابط کاربری، استایل‌دهی مدرن و ری یو آی که مستقیماً در پروژه‌های شما قابل استفاده هستند.
</p>
</p>
</div>
<div className="relative mt-16 flex h-full items-center justify-center">
@@ -145,30 +145,32 @@ const Hero211 = ({ className }: Hero211Props) => {
<div className="relative z-10 mx-auto mt-10 flex w-fit justify-center">
<Button className="rounded-full px-4 py-2 active:scale-105">
شروع رایگان
</Button>
</Button>
{/* RTL: sit to the left of the button so handwriting doesn't cover it */}
<motion.div
initial={{ opacity: 0, translateY: 20 }}
animate={{ opacity: 1, translateY: 0 }}
transition={{ duration: 0.3, delay: 1 }}
className="absolute top-3 -right-30 h-1"
className="pointer-events-none absolute top-0 -left-36 flex w-40 flex-col items-center sm:-left-44 sm:w-44"
>
<p className="font-handwriting text-xl tracking-tight">
<p className="font-handwriting text-center text-lg leading-snug tracking-tight sm:text-xl">
همین امروز نسخه رایگان را امتحان کنید
</p>
</p>
<svg
width="82"
className="-translate-x-1/2 pe-4"
height="45"
viewBox="0 0 82 45"
className="-mt-1 -scale-x-100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<motion.path
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1, delay: 1.2 }}
d="M80.2759 1.95576C67.8687 20.6075 49.1102 55.0246 21.9767 39.1283C15.3299 35.2342 12.7124 29.0489 9.38472 22.4634C9.24096 22.1789 6.96955 15.0574 7.91833 15.4904C10.4589 16.65 25.535 23.253 15.8013 18.8782C14.7716 18.4154 8.31018 14.0924 7.25323 14.6265C4.37354 16.0816 2.6512 30.2469 1.58546 33.4898"
stroke="#1C1C1C"
stroke="currentColor"
strokeWidth="2.40332"
strokeLinecap="round"
/>
+11 -9
View File
@@ -67,10 +67,10 @@ const Hero212 = ({ className }: Hero212Props) => {
<div className="mx-auto flex max-w-2xl flex-col items-center justify-center gap-4">
<p className="z-10 px-10 text-center tracking-tight text-muted-foreground/40 lg:text-lg">
صدها بلاک و کامپوننت آماده مبتنی بر رابط کاربری، استایل‌دهی مدرن و ری یو آی
</p>
<h1 className="font-playfair text-center text-2xll tracking-tighter italic lg:text-2xll">
</p>
<h1 className="font-playfair text-center text-5xl leading-tight tracking-tighter italic lg:text-6xl">
تجربه‌ای متفاوت خلق کنید
</h1>
</h1>
</div>
<div className="relative mt-16 flex h-full flex-col items-center justify-center">
@@ -97,30 +97,32 @@ const Hero212 = ({ className }: Hero212Props) => {
<div className="relative z-10 mx-auto mt-10 mb-12 flex w-fit justify-center">
<Button className="rounded-full px-4 py-2 active:scale-105">
شروع رایگان
</Button>
</Button>
{/* RTL: sit to the left of the button so handwriting doesn't cover it */}
<motion.div
initial={{ opacity: 0, translateY: 20 }}
animate={{ opacity: 1, translateY: 0 }}
transition={{ duration: 0.3, delay: 1 }}
className="absolute top-3 -right-30 h-1"
className="pointer-events-none absolute top-0 -left-36 flex w-40 flex-col items-center sm:-left-44 sm:w-44"
>
<p className="font-handwriting text-xl tracking-tight">
<p className="font-handwriting text-center text-lg leading-snug tracking-tight sm:text-xl">
همین امروز نسخه رایگان را امتحان کنید
</p>
</p>
<svg
width="82"
className="-translate-x-1/2 pe-4"
height="45"
viewBox="0 0 82 45"
className="-mt-1 -scale-x-100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden
>
<motion.path
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1, delay: 1.2 }}
d="M80.2759 1.95576C67.8687 20.6075 49.1102 55.0246 21.9767 39.1283C15.3299 35.2342 12.7124 29.0489 9.38472 22.4634C9.24096 22.1789 6.96955 15.0574 7.91833 15.4904C10.4589 16.65 25.535 23.253 15.8013 18.8782C14.7716 18.4154 8.31018 14.0924 7.25323 14.6265C4.37354 16.0816 2.6512 30.2469 1.58546 33.4898"
stroke="#1C1C1C"
stroke="currentColor"
strokeWidth="2.40332"
strokeLinecap="round"
/>
+93 -70
View File
@@ -12,81 +12,99 @@ interface Hero213Props {
className?: string;
}
const Hero213 = ({ className }: Hero213Props) => {
const images = [
{
src: "/block-media/shadcnblocks/block/lummi/bw12.jpeg",
alt: "تصویر نمونه",
name: "Joanna Doe",
},
{
src: "/block-media/shadcnblocks/block/lummi/bw13.jpeg",
alt: "تصویر نمونه",
name: "مریم چناری",
},
{
src: "/block-media/shadcnblocks/block/lummi/bw14.jpeg",
alt: "تصویر نمونه",
name: "Joanna Doe",
},
];
const IMAGES = [
{
src: "/block-media/shadcnblocks/block/lummi/bw12.jpeg",
alt: "تصویر نمونه",
name: "Joanna Doe",
},
{
src: "/block-media/shadcnblocks/block/lummi/bw13.jpeg",
alt: "تصویر نمونه",
name: "مریم چناری",
},
{
src: "/block-media/shadcnblocks/block/lummi/bw14.jpeg",
alt: "تصویر نمونه",
name: "Joanna Doe",
},
] as const;
const Hero213 = ({ className }: Hero213Props) => {
const [currentIndex, setCurrentIndex] = useState(0);
const nextIndex = (currentIndex + 1) % IMAGES.length;
useEffect(() => {
const timer = setTimeout(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length);
setCurrentIndex((prev) => (prev + 1) % IMAGES.length);
}, 2500);
return () => clearTimeout(timer);
}, [currentIndex, images.length]);
}, [currentIndex]);
return (
<section className={cn("py-32", className)}>
<section className={cn("overflow-x-hidden py-32", className)}>
<div className="container">
<div className="mx-auto flex max-w-2xl flex-col items-center justify-center gap-4">
<h1 className="font-calSans text-center text-2xll lg:text-2xll">
<h1 className="font-calSans text-center text-5xl leading-tight lg:text-6xl">
مجموعه بلاک‌های آماده <br />
برای رابط کاربری و استایل‌دهی مدرن
</h1>
<p className="px-10 text-center text-muted-foreground lg:text-lg">
صدها کامپوننت استاندارد و قابل سفارشی‌سازی که مستقیماً در پروژه‌های شما قابل استفاده هستند.
</p>
</h1>
<p className="px-10 text-center leading-relaxed text-muted-foreground lg:text-lg">
صدها کامپوننت استاندارد و قابل سفارشی‌سازی که مستقیماً در پروژه‌های
شما قابل استفاده هستند.
</p>
</div>
<div className="relative mt-4">
<div className="relative z-10 mx-auto my-7 flex w-fit cursor-pointer items-center justify-center gap-8 rounded-full bg-muted px-4 py-2 md:my-0 lg:translate-y-12 lg:py-3 xl:px-8 xl:py-4">
<GalleryVerticalEnd className="size-4 opacity-40 hover:opacity-100 lg:size-6" />
<Signature className="size-4 opacity-40 hover:opacity-100 lg:size-6" />
<Figma className="size-4 opacity-40 hover:opacity-100 lg:size-6" />
</div>
<MaskedDiv maskType="type-3">
<AnimatePresence mode="popLayout">
<motion.img
key={currentIndex}
className="h-full w-full object-cover"
src={images[currentIndex].src}
alt={images[currentIndex].alt}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.5 }}
/>
</AnimatePresence>
</MaskedDiv>
<div className="absolute bottom-22 -left-8 hidden h-36 w-32 items-center justify-center overflow-hidden rounded-4xl border lg:flex xl:bottom-28 xl:h-42 2xl:-left-28 2xl:h-52 2xl:w-62 2xl:rounded-[4rem]">
<AnimatePresence mode="popLayout">
<motion.img
key={currentIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.5 }}
src={images[currentIndex + 1]?.src || images[currentIndex].src}
alt={images[currentIndex + 1]?.alt || images[currentIndex].alt}
className="h-full w-full object-cover"
/>
</AnimatePresence>
{/* Shared box = mask bounds, so the tile can sit in the cutout precisely */}
<div className="relative w-full">
<MaskedDiv maskType="type-3">
<AnimatePresence mode="wait" initial={false}>
<motion.img
key={currentIndex}
className="absolute inset-0 size-full object-cover"
src={IMAGES[currentIndex].src}
alt={IMAGES[currentIndex].alt}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.8 }}
/>
</AnimatePresence>
</MaskedDiv>
{/*
type-3 cutout ≈ left 0–12.5%, bottom pocket ≈ 29% of height.
Tile sized under that so a white gutter remains vs the concave edge.
left/bottom 0 align with the mask’s outer edges (like the reference).
*/}
<div
className={cn(
"absolute bottom-0 left-0 z-20 hidden overflow-hidden border border-border bg-background",
"aspect-[4/5] w-[11.2%] min-w-[6.5rem] max-w-[15.5rem]",
"rounded-[1.75rem] lg:block xl:rounded-[2.25rem] 2xl:rounded-[2.75rem]",
)}
>
<AnimatePresence mode="wait" initial={false}>
<motion.img
key={nextIndex}
src={IMAGES[nextIndex].src}
alt={IMAGES[nextIndex].alt}
className="absolute inset-0 size-full object-cover"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.8 }}
/>
</AnimatePresence>
</div>
</div>
</div>
</div>
@@ -103,7 +121,7 @@ interface SvgPath {
}
interface MaskedDivProps {
children: React.ReactElement<HTMLImageElement | HTMLVideoElement>;
children: React.ReactNode;
maskType?: MaskType;
className?: string;
backgroundColor?: string;
@@ -121,9 +139,10 @@ const svgPaths: Record<MaskType, SvgPath> = {
height: 381,
width: 850,
},
// Height matches path bounds (max Y ≈ 765) so the cutout sits on the true bottom edge
"type-3": {
path: "M0.680664 112.659C0.680664 50.805 50.823 0.662672 112.677 0.662672H413.07C456.315 0.662672 497.495 19.1588 526.221 51.4846V51.4846C554.948 83.8104 596.128 102.307 639.373 102.307H711.793C752.427 102.307 790.787 83.5522 815.744 51.4846V51.4846C840.7 19.417 879.06 0.662644 919.695 0.662597L1225.01 0.66224C1286.86 0.662168 1337.01 50.8046 1337.01 112.658V652.815C1337.01 714.668 1286.86 764.811 1225.01 764.811L670 764.811H335.34H278.376C217.423 764.811 168.01 715.399 168.01 654.446V626.747C168.01 580.208 130.283 542.48 83.7437 542.48V542.48C37.8692 542.48 0.680664 505.292 0.680664 459.417V382.737V112.659Z",
height: 889,
height: 765,
width: 1340,
},
"type-4": {
@@ -133,6 +152,10 @@ const svgPaths: Record<MaskType, SvgPath> = {
},
};
function buildMaskDataUrl(mask: SvgPath): string {
return `data:image/svg+xml,%3Csvg width='${mask.width}' height='${mask.height}' viewBox='0 0 ${mask.width} ${mask.height}' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='${mask.path}' fill='%23D9D9D9'/%3E%3C/svg%3E`;
}
const MaskedDiv: React.FC<MaskedDivProps> = ({
children,
maskType = "type-1",
@@ -141,30 +164,30 @@ const MaskedDiv: React.FC<MaskedDivProps> = ({
size = 1,
}) => {
const selectedMask = svgPaths[maskType];
const svgString = `${selectedMask.width}${selectedMask.height}${selectedMask.width} ${selectedMask.height}${selectedMask.path}`;
const maskUrl = buildMaskDataUrl(selectedMask);
const containerStyle: React.CSSProperties = {
aspectRatio: `${selectedMask.width}/${selectedMask.height}`,
aspectRatio: `${selectedMask.width} / ${selectedMask.height}`,
backgroundColor,
maskImage: `url("${svgString}")`,
WebkitMaskImage: `url("${svgString}")`,
maskImage: `url("${maskUrl}")`,
WebkitMaskImage: `url("${maskUrl}")`,
maskRepeat: "no-repeat",
WebkitMaskRepeat: "no-repeat",
maskSize: "contain",
WebkitMaskSize: "contain",
maskSize: "100% 100%",
WebkitMaskSize: "100% 100%",
maskPosition: "center",
WebkitMaskPosition: "center",
width: `${size * 100}%`,
maxWidth: "100%",
margin: "0 auto",
marginInline: "auto",
};
return (
<section className={`relative ${className}`} style={containerStyle}>
{React.cloneElement(children, {
className: `w-full h-full object-cover transition-all duration-300 ${
children.props.className || ""
}`,
})}
</section>
<div
className={cn("relative overflow-hidden", className)}
style={containerStyle}
>
{children}
</div>
);
};