208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
import { type ReactNode, type SVGProps, useId } from "react";
|
|
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type FeatureItem = {
|
|
id: string;
|
|
title: ReactNode;
|
|
description: string[];
|
|
image: {
|
|
src: string;
|
|
alt: string;
|
|
className?: string;
|
|
};
|
|
imagePosition: "content" | "header";
|
|
className?: string;
|
|
headerClassName?: string;
|
|
contentClassName?: string;
|
|
};
|
|
|
|
const ITEMS: FeatureItem[] = [
|
|
{
|
|
id: "var-flexibility-hero",
|
|
title: (
|
|
<>
|
|
مدیریت پروژه
|
|
<br />
|
|
همراستایی
|
|
</>
|
|
),
|
|
description: [
|
|
"برنامه تیم را هماهنگ کنید و تداخل وظایف را کاهش دهید.",
|
|
"نسخه درست فایل همیشه در دسترس همه اعضا باشد.",
|
|
],
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
alt: "تقویم تیم",
|
|
},
|
|
imagePosition: "content",
|
|
className:
|
|
"flex flex-col gap-6 overflow-hidden py-6 ps-6 md:col-span-3 md:flex-row md:gap-12",
|
|
headerClassName: "flex-2 p-0 text-start md:flex-1",
|
|
contentClassName:
|
|
"relative w-full self-start overflow-hidden rounded-s-xl border p-0 md:flex-1",
|
|
},
|
|
{
|
|
id: "var-flexibility-flow",
|
|
title: "تقویم تیمی",
|
|
description: [
|
|
"کار گروهی",
|
|
"بحث را کنار هر تسک نگه دارید تا تصمیمها گم نشود.",
|
|
],
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
alt: "فضای همکاری تیمی",
|
|
className: "aspect-[2/1.1] rounded-xl",
|
|
},
|
|
className: "flex flex-col justify-center md:col-span-2",
|
|
contentClassName: "flex items-center justify-center",
|
|
imagePosition: "content",
|
|
},
|
|
{
|
|
id: "var-flexibility-diagram",
|
|
title: "اشتراک فایل",
|
|
description: [
|
|
"وضعیت هر کار را در یک نما ببینید و گلوگاهها را زودتر پیدا کنید.",
|
|
],
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
alt: "برد مدیریت پروژه",
|
|
className: "mb-6 aspect-2/1 flex-1 self-center rounded-xl",
|
|
},
|
|
className: "md:col-span-2",
|
|
headerClassName: "h-full text-start",
|
|
imagePosition: "header",
|
|
},
|
|
{
|
|
id: "var-flexibility-transfer",
|
|
title: "منشن و بحث",
|
|
description: [
|
|
"همکاری تیمی",
|
|
],
|
|
image: {
|
|
src: "/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
alt: "تقویم تیم",
|
|
},
|
|
imagePosition: "content",
|
|
className: "overflow-hidden md:col-span-3",
|
|
headerClassName: "text-start",
|
|
contentClassName:
|
|
"relative mt-4 ms-8 aspect-[2/1.25] w-full overflow-hidden rounded-t-xl p-0 md:mx-auto md:max-w-[400px] lg:max-w-[500px]",
|
|
},
|
|
];
|
|
|
|
export const Feature193 = () => {
|
|
return (
|
|
<section dir="rtl" lang="fa" className="relative overflow-hidden py-32">
|
|
<h3 className="container font-mono text-sm font-semibold text-accent-foreground">
|
|
ردیابی زمان
|
|
</h3>
|
|
|
|
<div className="relative z-10 container mt-8 grid grid-cols-1 gap-6 md:grid-cols-5">
|
|
{ITEMS.map((item) => (
|
|
<Card
|
|
key={item.id}
|
|
className={cn("col-span-1 shadow-xl", item.className)}
|
|
>
|
|
<CardHeader className={item.headerClassName}>
|
|
{item.imagePosition === "header" && (
|
|
<img
|
|
src={item.image.src}
|
|
alt={item.image.alt}
|
|
className={cn("object-cover", item.image.className)}
|
|
/>
|
|
)}
|
|
<CardTitle className="mt-0! text-3xl text-balance">
|
|
{item.title}
|
|
</CardTitle>
|
|
{item.description.map((desc) => (
|
|
<CardDescription
|
|
key={desc}
|
|
className="mt-3 text-base leading-snug font-medium text-pretty"
|
|
>
|
|
{desc}
|
|
</CardDescription>
|
|
))}
|
|
</CardHeader>
|
|
<CardContent className={item.contentClassName}>
|
|
{item.imagePosition === "content" && (
|
|
<img
|
|
src={item.image.src}
|
|
alt={item.image.alt}
|
|
className={cn(
|
|
"self-center object-cover",
|
|
item.image.className,
|
|
)}
|
|
/>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
<>
|
|
<div className="absolute inset-0">
|
|
<div className="absolute top-1/2 size-[700px] -translate-y-1/2 rounded-full bg-gradient-1/30 blur-[300px] will-change-transform" />
|
|
<div className="absolute top-1/2 end-0 size-[700px] -translate-y-1/2 -rotate-12 rounded-full bg-gradient-2/15 blur-[300px] will-change-transform rtl:rotate-12" />
|
|
<div className="absolute end-20 bottom-10 h-[500px] w-[800px] -rotate-12 rounded-full bg-gradient-3/[0.06] blur-[100px] will-change-transform rtl:rotate-12" />
|
|
</div>
|
|
<div className="absolute -inset-x-20 top-0 [mask-image:radial-gradient(circle_at_center,black_0%,black_20%,transparent_85%)]">
|
|
<PlusSigns className="h-full w-full text-foreground/[0.05]" />
|
|
</div>
|
|
</>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
interface PlusSignsProps extends SVGProps<SVGSVGElement> {
|
|
className?: string;
|
|
}
|
|
|
|
const PlusSigns = ({ className, ...props }: PlusSignsProps) => {
|
|
const GAP = 16;
|
|
const STROKE_WIDTH = 1;
|
|
const PLUS_SIZE = 6;
|
|
const id = useId();
|
|
const patternId = `plus-pattern-${id}`;
|
|
|
|
return (
|
|
<svg width={GAP * 2} height={GAP * 2} className={className} {...props}>
|
|
<defs>
|
|
<pattern
|
|
id={patternId}
|
|
x="0"
|
|
y="0"
|
|
width={GAP}
|
|
height={GAP}
|
|
patternUnits="userSpaceOnUse"
|
|
>
|
|
<line
|
|
x1={GAP / 2}
|
|
y1={(GAP - PLUS_SIZE) / 2}
|
|
x2={GAP / 2}
|
|
y2={(GAP + PLUS_SIZE) / 2}
|
|
stroke="currentColor"
|
|
strokeWidth={STROKE_WIDTH}
|
|
/>
|
|
<line
|
|
x1={(GAP - PLUS_SIZE) / 2}
|
|
y1={GAP / 2}
|
|
x2={(GAP + PLUS_SIZE) / 2}
|
|
y2={GAP / 2}
|
|
stroke="currentColor"
|
|
strokeWidth={STROKE_WIDTH}
|
|
/>
|
|
</pattern>
|
|
</defs>
|
|
<rect width="100%" height="100%" fill={`url(#${patternId})`} />
|
|
</svg>
|
|
);
|
|
};
|