a
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
const customEase: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
||||
|
||||
export default function CtaBanner() {
|
||||
return (
|
||||
<section
|
||||
className="relative py-32 px-4 md:px-8 bg-gray-950 overflow-hidden"
|
||||
onMouseMove={(e) => {
|
||||
const { currentTarget, clientX, clientY } = e;
|
||||
const { left, top } = currentTarget.getBoundingClientRect();
|
||||
currentTarget.style.setProperty("--mouse-x", `${clientX - left}px`);
|
||||
currentTarget.style.setProperty("--mouse-y", `${clientY - top}px`);
|
||||
}}
|
||||
>
|
||||
|
||||
{/* پسزمینه: گرید و افکت Spotlight */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
{/* گرید با رنگ خاکستری ملایم مطابق با بخش پروژهها */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]" />
|
||||
|
||||
{/* لایه نورافکن (Spotlight) وابسته به حرکت موس */}
|
||||
<div className="pointer-events-none absolute -inset-px opacity-50 before:absolute before:inset-0 before:bg-[radial-gradient(800px_circle_at_var(--mouse-x)_var(--mouse-y),rgba(255,255,255,0.06),transparent_40%)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 max-w-7xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, y: 0, scale: 1 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 1.2, ease: customEase }}
|
||||
className="relative overflow-hidden rounded-[2.5rem] bg-white shadow-2xl shadow-gray-900/50 border border-gray-200"
|
||||
>
|
||||
{/* هالههای نوری پاستلی کارت */}
|
||||
<div className="absolute -top-32 -right-32 w-[30rem] h-[30rem] bg-indigo-50 blur-[100px] rounded-full pointer-events-none" />
|
||||
<div className="absolute -bottom-32 -left-32 w-[30rem] h-[30rem] bg-pink-50 blur-[100px] rounded-full pointer-events-none" />
|
||||
|
||||
<div className="relative z-10 py-20 px-6 md:px-16 flex flex-col items-center text-center">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: 0.2, ease: customEase }}
|
||||
className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight text-gray-900 mb-6"
|
||||
>
|
||||
آماده خلق یک{" "}
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-indigo-500 to-purple-500">
|
||||
شاهکار
|
||||
</span>{" "}
|
||||
هستید؟
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: 0.3, ease: customEase }}
|
||||
className="text-lg md:text-xl text-gray-500 max-w-2xl mb-10 font-medium leading-relaxed"
|
||||
>
|
||||
ایدههای خود را با تکنولوژیهای روز دنیا و طراحی خیرهکننده به واقعیت تبدیل کنید. تیم ما برای شروع پروژه بعدی شما آماده است.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: 0.4, ease: customEase }}
|
||||
>
|
||||
<button className="group relative inline-flex items-center justify-center gap-3 px-8 py-4 bg-gray-900 text-white rounded-full font-bold text-lg overflow-hidden transition-all duration-300 hover:bg-gray-800 hover:scale-105 hover:shadow-xl hover:shadow-gray-900/20 active:scale-95">
|
||||
<span>شروع همکاری</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5 transition-transform duration-300 group-hover:-translate-x-1"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { motion, Variants, useScroll, useTransform } from "framer-motion";
|
||||
|
||||
// تعریف Variants با انیمیشنهای نرمتر و روانتر
|
||||
const containerVariants: Variants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
filter: "blur(8px)", // بلور اولیه کمی بیشتر برای حس نرمتر شدن
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
filter: "blur(0px)",
|
||||
transition: {
|
||||
duration: 1.2, // زمان طولانیتر برای محو شدن بلور
|
||||
when: "beforeChildren",
|
||||
staggerChildren: 0.15, // فاصله زمانی کمی بیشتر بین ورود هر خط
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { y: 30, opacity: 0 }, // شروع از فاصله کمی پایینتر
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 1.2, // مدت زمان لغزش به بالا
|
||||
// این منحنی (easeOutExpo) راز انیمیشنهای نرم سبک اپل است
|
||||
ease: [0.16, 1, 0.3, 1] as [number, number, number, number],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function Hero() {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
|
||||
// هوکها برای افکت پارالاکس و محو شدن هنگام اسکرول
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: ref,
|
||||
offset: ["start start", "end start"],
|
||||
});
|
||||
const y = useTransform(scrollYProgress, [0, 1], ["0%", "50%"]);
|
||||
const opacity = useTransform(scrollYProgress, [0, 0.8], [1, 0]);
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={ref}
|
||||
className="relative h-screen flex flex-col items-center justify-center overflow-hidden bg-gray-950"
|
||||
onMouseMove={(e) => {
|
||||
// آپدیت متغیرهای CSS برای موقعیت موس جهت افکت نورافکن
|
||||
const { currentTarget, clientX, clientY } = e;
|
||||
const { left, top } = currentTarget.getBoundingClientRect();
|
||||
currentTarget.style.setProperty("--mouse-x", `${clientX - left}px`);
|
||||
currentTarget.style.setProperty("--mouse-y", `${clientY - top}px`);
|
||||
}}
|
||||
>
|
||||
{/* لایه پسزمینه: نورافکن (Spotlight) */}
|
||||
<div
|
||||
className="
|
||||
pointer-events-none absolute -inset-px rounded-xl opacity-50
|
||||
transition-opacity duration-300
|
||||
before:absolute before:inset-0 before:rounded-xl
|
||||
before:bg-[radial-gradient(600px_circle_at_var(--mouse-x)_var(--mouse-y),rgba(30,129,243,0.15),transparent_40%)]
|
||||
"
|
||||
/>
|
||||
|
||||
{/* لایه پسزمینه: خطوط گرید */}
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]" />
|
||||
|
||||
{/* کانتینر اصلی محتوا */}
|
||||
<motion.div
|
||||
style={{ y, opacity }}
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="z-10 text-center w-full max-w-5xl mx-auto px-4"
|
||||
>
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="mb-6 inline-block rounded-full border border-blue-500/30 bg-blue-500/10 px-4 py-1.5 text-sm text-blue-300"
|
||||
>
|
||||
توسعه نرمافزار با بالاترین کیفیت
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
variants={itemVariants}
|
||||
className="text-5xl md:text-7xl font-bold tracking-tighter bg-clip-text text-transparent bg-gradient-to-b from-neutral-50 to-neutral-400 pb-4"
|
||||
>
|
||||
راهکارهای دیجیتال برای فردای شما
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
variants={itemVariants}
|
||||
className="mt-4 text-lg text-neutral-400 max-w-2xl mx-auto"
|
||||
>
|
||||
ما با ترکیب خلاقیت و تکنولوژیهای پیشرفته، نرمافزارهایی خلق میکنیم
|
||||
که نه تنها نیازهای امروز شما را برآورده میکنند، بلکه مسیر رشد آینده
|
||||
کسبوکار شما را هموار میسازند.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="mt-8 flex justify-center gap-4"
|
||||
>
|
||||
<button className="px-6 py-2 rounded-md bg-blue-600 text-white font-medium hover:bg-blue-700 transition-colors">
|
||||
شروع همکاری
|
||||
</button>
|
||||
<button className="px-6 py-2 rounded-md border border-neutral-700 text-neutral-300 font-medium hover:bg-neutral-800 transition-colors">
|
||||
مشاهده نمونه کارها
|
||||
</button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
|
||||
// دیتای تستی پروژهها (میتوانید با دیتای واقعی جایگزین کنید)
|
||||
const projects = [
|
||||
{
|
||||
id: 1,
|
||||
title: "سیستم مدیریت مالی فراز",
|
||||
category: "نرمافزار سازمانی",
|
||||
description: "یک پلتفرم یکپارچه برای مدیریت منابع مالی سازمانها با داشبوردهای تحلیلی در لحظه.",
|
||||
image: "/project-1.jpg", // این عکسها را در پوشه public قرار دهید
|
||||
color: "from-blue-500/20 to-purple-500/20",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "اپلیکیشن سلامتجو",
|
||||
category: "اپلیکیشن موبایل",
|
||||
description: "سیستم نوبتدهی و پایش سلامت بیماران با قابلیت یکپارچهسازی با گجتهای پوشیدنی.",
|
||||
image: "/project-2.jpg",
|
||||
color: "from-emerald-500/20 to-teal-500/20",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "پلتفرم فروشگاهی نوینشاپ",
|
||||
category: "تجارت الکترونیک",
|
||||
description: "فروشگاهساز ابری با معماری میکروسرویس و قابلیت مقیاسپذیری بالا برای ترافیکهای سنگین.",
|
||||
image: "/project-3.jpg",
|
||||
color: "from-orange-500/20 to-red-500/20",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "اتوماسیون اداری هوشمند",
|
||||
category: "هوش مصنوعی",
|
||||
description: "سامانه اتوماسیون مبتنی بر هوش مصنوعی برای دستهبندی خودکار نامهها و ارجاعات.",
|
||||
image: "/project-4.jpg",
|
||||
color: "from-indigo-500/20 to-blue-500/20",
|
||||
},
|
||||
];
|
||||
|
||||
// منحنی انیمیشن لوکس (همان که در Hero استفاده کردیم)
|
||||
const customEase: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
||||
|
||||
export default function Projects() {
|
||||
return (
|
||||
<section
|
||||
className="relative min-h-screen py-32 flex flex-col items-center justify-center overflow-hidden bg-gray-950"
|
||||
onMouseMove={(e) => {
|
||||
const { currentTarget, clientX, clientY } = e;
|
||||
const { left, top } = currentTarget.getBoundingClientRect();
|
||||
currentTarget.style.setProperty("--mouse-x", `${clientX - left}px`);
|
||||
currentTarget.style.setProperty("--mouse-y", `${clientY - top}px`);
|
||||
}}
|
||||
>
|
||||
{/* افکت Spotlight و Grid (دقیقا مشابه Hero) */}
|
||||
<div className="pointer-events-none absolute -inset-px opacity-50 before:absolute before:inset-0 before:bg-[radial-gradient(800px_circle_at_var(--mouse-x)_var(--mouse-y),rgba(255,255,255,0.06),transparent_40%)]" />
|
||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]" />
|
||||
|
||||
<div className="z-10 w-full max-w-7xl mx-auto px-4 md:px-8">
|
||||
{/* هدر بخش پروژهها */}
|
||||
<div className="mb-20 md:mb-32">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 1, ease: customEase }}
|
||||
className="text-4xl md:text-6xl font-bold tracking-tight text-white mb-6"
|
||||
>
|
||||
خلق تجربههای <span className="text-neutral-500">ماندگار.</span>
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 1, delay: 0.1, ease: customEase }}
|
||||
className="text-lg md:text-xl text-neutral-400 max-w-2xl"
|
||||
>
|
||||
برخی از پروژههای منتخب ما که با تمرکز بر کارایی، زیبایی و معماری قدرتمند توسعه یافتهاند.
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* گرید پروژهها */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8">
|
||||
{projects.map((project, index) => (
|
||||
<motion.div
|
||||
key={project.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{
|
||||
duration: 1.2,
|
||||
delay: index * 0.15, // انیمیشن آبشاری برای کارتها
|
||||
ease: customEase,
|
||||
}}
|
||||
className="group relative rounded-3xl bg-neutral-900/50 border border-white/5 overflow-hidden backdrop-blur-sm transition-colors hover:bg-neutral-900/80"
|
||||
>
|
||||
{/* بخش تصویر (بالای کارت) */}
|
||||
<div className={`relative h-64 md:h-80 w-full overflow-hidden bg-gradient-to-br ${project.color}`}>
|
||||
{/*
|
||||
نکته: برای استفاده از Image در Next.js، کامنت زیر را بردارید و عکسها را در فولدر public بگذارید.
|
||||
در اینجا موقتاً از یک گرادیانت به عنوان جایگزین عکس استفاده شده است تا خطا نگیرید.
|
||||
*/}
|
||||
{/* <Image
|
||||
src={project.image}
|
||||
alt={project.title}
|
||||
fill
|
||||
className="object-cover object-center transition-transform duration-700 group-hover:scale-105"
|
||||
/> */}
|
||||
|
||||
{/* یک المان تزئینی به جای عکس واقعی (موقتی) */}
|
||||
<div className="absolute inset-0 flex items-center justify-center text-white/20 font-bold text-3xl transition-transform duration-700 group-hover:scale-105">
|
||||
تصویر پروژه
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* بخش محتوا (پایین کارت) */}
|
||||
<div className="p-8">
|
||||
<div className="text-xs font-semibold tracking-wider text-neutral-500 mb-3 uppercase">
|
||||
{project.category}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-neutral-100 mb-3 group-hover:text-white transition-colors">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="text-neutral-400 leading-relaxed text-sm md:text-base">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { motion, useMotionTemplate, useMotionValue } from "framer-motion";
|
||||
import {
|
||||
Server,
|
||||
Terminal,
|
||||
Database,
|
||||
ShieldCheck,
|
||||
Cpu,
|
||||
Layers,
|
||||
ArrowUpLeft,
|
||||
Activity,
|
||||
Zap,
|
||||
CheckCircle2,
|
||||
Lock
|
||||
} from "lucide-react";
|
||||
|
||||
// ==========================================
|
||||
// 1. کامپوننت کارت با افکت ردیابی نور موس
|
||||
// ==========================================
|
||||
interface SpotlightCardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function SpotlightCard({ children, className = "" }: SpotlightCardProps) {
|
||||
const mouseX = useMotionValue(0);
|
||||
const mouseY = useMotionValue(0);
|
||||
|
||||
function handleMouseMove({ currentTarget, clientX, clientY }: React.MouseEvent) {
|
||||
const { left, top } = currentTarget.getBoundingClientRect();
|
||||
mouseX.set(clientX - left);
|
||||
mouseY.set(clientY - top);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseMove={handleMouseMove}
|
||||
className={`group relative overflow-hidden rounded-2xl border border-border/50 bg-gradient-to-b from-card/80 to-card/30 p-6 md:p-8 backdrop-blur-xl transition-all duration-300 hover:border-primary/50 hover:shadow-2xl hover:shadow-primary/5 ${className}`}
|
||||
>
|
||||
{/* Dynamic Cursor Light */}
|
||||
<motion.div
|
||||
className="pointer-events-none absolute -inset-px rounded-2xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
style={{
|
||||
background: useMotionTemplate`
|
||||
radial-gradient(
|
||||
450px circle at ${mouseX}px ${mouseY}px,
|
||||
hsl(var(--primary) / 0.12),
|
||||
transparent 80%
|
||||
)
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative z-10 flex h-full flex-col justify-between">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. بخش اصلی Bento Grid خدمات
|
||||
// ==========================================
|
||||
export function ServicesBentoGrid() {
|
||||
return (
|
||||
<section className="relative overflow-hidden py-24 md:py-32 bg-background">
|
||||
{/* Ambient Background Grid & Glows */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:28px_28px]" />
|
||||
<div className="absolute -top-40 right-1/4 h-96 w-96 rounded-full bg-primary/10 blur-[120px] pointer-events-none" />
|
||||
<div className="absolute -bottom-40 left-1/4 h-96 w-96 rounded-full bg-blue-500/10 blur-[120px] pointer-events-none" />
|
||||
|
||||
<div className="container relative mx-auto max-w-7xl px-4 md:px-6">
|
||||
|
||||
{/* Section Header */}
|
||||
<div className="flex flex-col items-start md:items-end justify-between gap-6 pb-16 md:flex-row border-b border-border/40">
|
||||
<div className="max-w-2xl space-y-3">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/5 px-3 py-1 text-xs font-mono text-primary">
|
||||
<Zap className="h-3.5 w-3.5" />
|
||||
<span>CAPABILITIES & ARCHITECTURE</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-extrabold tracking-tight md:text-4xl lg:text-5xl text-foreground">
|
||||
توسعه زیرساخت و معماری مقیاسپذیر
|
||||
</h2>
|
||||
<p className="text-sm md:text-base text-muted-foreground leading-relaxed">
|
||||
طراحی، استقرار و بهینهسازی سرویسهای توزیعشده سازمانی با حداکثر پایداری و امنیت تراکنشها.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-xs font-mono text-muted-foreground bg-muted/40 px-4 py-2 rounded-lg border border-border/40">
|
||||
<span className="flex h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span>SYSTEM STATUS: OPERATIONAL</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bento Grid (6 Cards) */}
|
||||
<div className="mt-12 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
{/* Card 1: Distributed Core Systems */}
|
||||
<SpotlightCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary border border-primary/20 shadow-inner">
|
||||
<Server className="h-5 w-5" />
|
||||
</div>
|
||||
<ArrowUpLeft className="h-5 w-5 text-muted-foreground/40 transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1 group-hover:text-primary" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-bold text-foreground">سامانههای توزیعشده و Core</h3>
|
||||
<p className="mt-2 text-xs md:text-sm text-muted-foreground leading-relaxed">
|
||||
معماری میکروسرویس با پرفورمنس بالا، مدیریت همزمانی (Concurrency) و پردازش بیدرنگ دادهها.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-xl border border-border/40 bg-background/60 p-3.5 font-mono text-xs">
|
||||
<div className="flex items-center justify-between text-muted-foreground pb-2 border-b border-border/30">
|
||||
<span>TPS / Throughput</span>
|
||||
<span className="text-emerald-400 font-semibold">12,450 req/s</span>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between text-[11px] text-muted-foreground">
|
||||
<span>Avg Latency:</span>
|
||||
<span className="text-foreground">~8.2ms</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-1.5">
|
||||
{["Go", "Laravel 11", "FastAPI", "gRPC"].map((tag) => (
|
||||
<span key={tag} className="rounded-md bg-secondary/80 px-2 py-0.5 font-mono text-[11px] text-secondary-foreground">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
{/* Card 2: DevOps & Cloud Infrastructure */}
|
||||
<SpotlightCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-blue-500/10 text-blue-400 border border-blue-500/20 shadow-inner">
|
||||
<Terminal className="h-5 w-5" />
|
||||
</div>
|
||||
<ArrowUpLeft className="h-5 w-5 text-muted-foreground/40 transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1 group-hover:text-blue-400" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-bold text-foreground">اتوماسیون زیرساخت و CI/CD</h3>
|
||||
<p className="mt-2 text-xs md:text-sm text-muted-foreground leading-relaxed">
|
||||
استقرار پیوسته، کانتینرسازی و مانیتورینگ سلامت سرویسها بدون وقفه عملیاتی (Zero-Downtime).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-xl border border-border/40 bg-background/80 p-3 font-mono text-[11px] space-y-1">
|
||||
<div className="text-muted-foreground flex items-center gap-1.5">
|
||||
<CheckCircle2 className="h-3 w-3 text-emerald-400" />
|
||||
<span>Cluster Node Health: 100%</span>
|
||||
</div>
|
||||
<div className="text-primary flex items-center gap-1.5">
|
||||
<Activity className="h-3 w-3" />
|
||||
<span>Rolling deployment synced</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-1.5">
|
||||
{["Docker", "Kubernetes", "CI/CD", "Prometheus"].map((tag) => (
|
||||
<span key={tag} className="rounded-md bg-secondary/80 px-2 py-0.5 font-mono text-[11px] text-secondary-foreground">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
{/* Card 3: Data Engineering & Queues */}
|
||||
<SpotlightCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-purple-500/10 text-purple-400 border border-purple-500/20 shadow-inner">
|
||||
<Database className="h-5 w-5" />
|
||||
</div>
|
||||
<ArrowUpLeft className="h-5 w-5 text-muted-foreground/40 transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1 group-hover:text-purple-400" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-bold text-foreground">مهندسی داده و Event-Driven</h3>
|
||||
<p className="mt-2 text-xs md:text-sm text-muted-foreground leading-relaxed">
|
||||
پیادهسازی پایگاههای داده رابطهای و NoSQL، کشینگ هوشمند و معماری پیاممحور نامتقارن.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-xl border border-border/40 bg-background/60 p-3 font-mono text-xs flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Queue Ingestion:</span>
|
||||
<span className="text-purple-400 font-semibold">Kafka / RabbitMQ</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-1.5">
|
||||
{["PostgreSQL", "Redis", "Kafka", "RabbitMQ"].map((tag) => (
|
||||
<span key={tag} className="rounded-md bg-secondary/80 px-2 py-0.5 font-mono text-[11px] text-secondary-foreground">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
{/* Card 4: AI & Vector Databases */}
|
||||
<SpotlightCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-amber-500/10 text-amber-400 border border-amber-500/20 shadow-inner">
|
||||
<Cpu className="h-5 w-5" />
|
||||
</div>
|
||||
<ArrowUpLeft className="h-5 w-5 text-muted-foreground/40 transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1 group-hover:text-amber-400" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-bold text-foreground">جستجوی معنایی و هوش مصنوعی</h3>
|
||||
<p className="mt-2 text-xs md:text-sm text-muted-foreground leading-relaxed">
|
||||
استخراج اطلاعات، تعبیهسازی وکتورها و بازیابی فوقسریع در پایگاههای داده معنایی.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-xl border border-border/40 bg-background/60 p-3 font-mono text-xs flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Vector Dimension:</span>
|
||||
<span className="text-amber-400 font-semibold">1536d (HNSW)</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-1.5">
|
||||
{["Qdrant", "RAG", "Embeddings", "FastAPI"].map((tag) => (
|
||||
<span key={tag} className="rounded-md bg-secondary/80 px-2 py-0.5 font-mono text-[11px] text-secondary-foreground">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
{/* Card 5: Enterprise Portals & Workflows */}
|
||||
<SpotlightCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 shadow-inner">
|
||||
<Layers className="h-5 w-5" />
|
||||
</div>
|
||||
<ArrowUpLeft className="h-5 w-5 text-muted-foreground/40 transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1 group-hover:text-emerald-400" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-bold text-foreground">پرتالهای سازمانی و کارتابل</h3>
|
||||
<p className="mt-2 text-xs md:text-sm text-muted-foreground leading-relaxed">
|
||||
طراحی پنلهای مدیریت فرآیندها (BPMS)، فرمسازهای پویا و ماژولهای سازمانی یکپارچه.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-xl border border-border/40 bg-background/60 p-3 font-mono text-xs flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Workflow Engine:</span>
|
||||
<span className="text-emerald-400 font-semibold">Active State</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-1.5">
|
||||
{["Filament 3", "Next.js", "TypeScript", "Tailwind"].map((tag) => (
|
||||
<span key={tag} className="rounded-md bg-secondary/80 px-2 py-0.5 font-mono text-[11px] text-secondary-foreground">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
{/* Card 6: Enterprise Security & Auth */}
|
||||
<SpotlightCard>
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-rose-500/10 text-rose-400 border border-rose-500/20 shadow-inner">
|
||||
<ShieldCheck className="h-5 w-5" />
|
||||
</div>
|
||||
<ArrowUpLeft className="h-5 w-5 text-muted-foreground/40 transition-transform group-hover:-translate-x-1 group-hover:-translate-y-1 group-hover:text-rose-400" />
|
||||
</div>
|
||||
<h3 className="mt-5 text-lg font-bold text-foreground">امنیت، هویت و دسترسی سازمانی</h3>
|
||||
<p className="mt-2 text-xs md:text-sm text-muted-foreground leading-relaxed">
|
||||
پیادهسازی احراز هویت متمرکز (SSO)، پروتکلهای OAuth2/OpenID و سیستمهای RBAC پیشرفته.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 rounded-xl border border-border/40 bg-background/60 p-3 font-mono text-xs flex justify-between items-center">
|
||||
<span className="text-muted-foreground flex items-center gap-1.5">
|
||||
<Lock className="h-3 w-3 text-rose-400" />
|
||||
Audit Logging:
|
||||
</span>
|
||||
<span className="text-rose-400 font-semibold">Encrypted (AES-256)</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-1.5">
|
||||
{["OAuth2", "SSO", "RBAC", "JWT"].map((tag) => (
|
||||
<span key={tag} className="rounded-md bg-secondary/80 px-2 py-0.5 font-mono text-[11px] text-secondary-foreground">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</SpotlightCard>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServicesBentoGrid;
|
||||
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export function InteractiveAuroraBackground({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="relative h-screen w-full bg-gray-950">
|
||||
{/*
|
||||
اینجا اصل جادو اتفاق میافتد.
|
||||
یک شبهالمان (pseudo-element) روی کل صفحه کشیده میشود.
|
||||
پسزمینه آن یک گرادیان شعاعی (radial-gradient) است که در موقعیت متغیرهای --x و --y قرار گرفته.
|
||||
این متغیرها توسط حرکت موس آپدیت میشوند.
|
||||
*/}
|
||||
<div
|
||||
className="
|
||||
absolute inset-0 z-0
|
||||
bg-[radial-gradient(40%_40%_at_var(--x,30%)_var(--y,30%),#214374_0%,transparent_100%)]
|
||||
[background-size:150%_150%]
|
||||
[background-repeat:no-repeat]
|
||||
"
|
||||
onMouseMove={(e) => {
|
||||
const { clientX, clientY, currentTarget } = e;
|
||||
const { left, top, width, height } = currentTarget.getBoundingClientRect();
|
||||
const xPercent = ((clientX - left) / width) * 100;
|
||||
const yPercent = ((clientY - top) / height) * 100;
|
||||
|
||||
currentTarget.style.setProperty("--x", `${xPercent.toFixed(2)}%`);
|
||||
currentTarget.style.setProperty("--y", `${yPercent.toFixed(2)}%`);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* خطوط گرید برای حفظ حس مهندسی */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] z-10 pointer-events-none" />
|
||||
|
||||
{/* محتوای اصلی صفحه در لایه بالاتر قرار میگیرد */}
|
||||
<div className="relative z-20">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
const navLinks = [
|
||||
{ name: "صفحه اصلی", href: "/" },
|
||||
{ name: "خدمات", href: "/services" },
|
||||
{ name: "نمونه کارها", href: "/portfolio" },
|
||||
{ name: "درباره ما", href: "/about" },
|
||||
];
|
||||
|
||||
export default function Header() {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
// تغییر ظاهر هدر هنگام اسکرول به پایین
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
// جلوگیری از اسکرول شدن صفحه وقتی منوی موبایل باز است
|
||||
useEffect(() => {
|
||||
if (isMobileMenuOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "unset";
|
||||
}
|
||||
}, [isMobileMenuOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 border-b ${
|
||||
isScrolled
|
||||
? "bg-gray-950/60 backdrop-blur-md border-white/10 py-3" // حالت شیشهای هنگام اسکرول
|
||||
: "bg-transparent border-transparent py-5" // حالت شفاف در بالای صفحه
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto px-6 flex items-center justify-between">
|
||||
{/* لوگو */}
|
||||
<Link href="/" className="relative z-50 flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-gradient-to-tr from-blue-600 to-blue-400 flex items-center justify-center">
|
||||
<span className="text-white font-bold text-lg">N</span>
|
||||
</div>
|
||||
<span className="text-neutral-50 font-medium tracking-wide">
|
||||
نوینتک
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* منوی دسکتاپ */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="text-sm text-neutral-400 hover:text-neutral-50 transition-colors duration-300"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* دکمه تماس / اکشن دسکتاپ */}
|
||||
<div className="hidden md:block">
|
||||
<button className="text-sm font-medium text-white px-5 py-2 rounded-full border border-white/10 bg-white/5 hover:bg-white/10 transition-all duration-300 backdrop-blur-sm">
|
||||
شروع پروژه
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* دکمه همبرگری موبایل */}
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(true)}
|
||||
className="md:hidden relative z-50 p-2 text-neutral-300 hover:text-white transition-colors"
|
||||
aria-label="Toggle Menu"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
>
|
||||
<line x1="4" y1="8" x2="20" y2="8" />
|
||||
<line x1="4" y1="16" x2="20" y2="16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* منوی موبایل (Drawer) */}
|
||||
<AnimatePresence>
|
||||
{isMobileMenuOpen && (
|
||||
<>
|
||||
{/* لایه تاریک پسزمینه (Overlay) */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="fixed inset-0 z-[60] bg-gray-950/60 backdrop-blur-sm"
|
||||
/>
|
||||
|
||||
{/* پنل کشویی از سمت راست */}
|
||||
<motion.div
|
||||
initial={{ x: "100%" }} // شروع از بیرون صفحه (سمت راست)
|
||||
animate={{ x: 0 }} // ورود به صفحه
|
||||
exit={{ x: "100%" }} // خروج به سمت راست
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
ease: [0.16, 1, 0.3, 1], // همان منحنی نرم اپل-استایل
|
||||
}}
|
||||
className="fixed top-0 right-0 bottom-0 w-3/4 max-w-sm z-[70] bg-gray-950/90 backdrop-blur-xl border-l border-white/10 shadow-2xl flex flex-col"
|
||||
>
|
||||
<div className="flex items-center justify-between p-6 border-b border-white/10">
|
||||
<span className="text-neutral-50 font-medium">منو</span>
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="p-2 text-neutral-400 hover:text-white transition-colors bg-white/5 rounded-full"
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col p-6 gap-6 flex-1 overflow-y-auto">
|
||||
{navLinks.map((link, i) => (
|
||||
<motion.div
|
||||
key={link.name}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.1 + i * 0.1, duration: 0.4 }}
|
||||
>
|
||||
<Link
|
||||
href={link.href}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="text-lg text-neutral-300 hover:text-white transition-colors"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-white/10">
|
||||
<button className="w-full py-3 rounded-xl bg-blue-600 text-white font-medium hover:bg-blue-700 transition-colors">
|
||||
شروع پروژه
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "cn"
|
||||
import { Slot } from "radix-ui"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||
destructive:
|
||||
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
||||
outline:
|
||||
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||
ghost:
|
||||
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
data-variant={variant}
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "cn"
|
||||
import { Slot } from "radix-ui"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
outline:
|
||||
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||
ghost:
|
||||
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
||||
destructive:
|
||||
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default:
|
||||
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||
icon: "size-8",
|
||||
"icon-xs":
|
||||
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
||||
"icon-sm":
|
||||
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
||||
"icon-lg": "size-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant = "default",
|
||||
size = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot.Root : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,147 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { cn } from "cn"
|
||||
import { Dialog as SheetPrimitive } from "radix-ui"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||
}
|
||||
|
||||
function SheetTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||
}
|
||||
|
||||
function SheetClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||
}
|
||||
|
||||
function SheetPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||
}
|
||||
|
||||
function SheetOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
|
||||
return (
|
||||
<SheetPrimitive.Overlay
|
||||
data-slot="sheet-overlay"
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetContent({
|
||||
className,
|
||||
children,
|
||||
side = "right",
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
showCloseButton?: boolean
|
||||
}) {
|
||||
return (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
data-slot="sheet-content"
|
||||
data-side={side}
|
||||
className={cn(
|
||||
"fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-sm text-popover-foreground shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<SheetPrimitive.Close data-slot="sheet-close" asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="absolute top-3 right-3"
|
||||
size="icon-sm"
|
||||
>
|
||||
<XIcon
|
||||
/>
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
</SheetPrimitive.Close>
|
||||
)}
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="sheet-header"
|
||||
className={cn("flex flex-col gap-0.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="sheet-footer"
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||
return (
|
||||
<SheetPrimitive.Title
|
||||
data-slot="sheet-title"
|
||||
className={cn(
|
||||
"font-heading text-base font-medium text-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SheetDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||
return (
|
||||
<SheetPrimitive.Description
|
||||
data-slot="sheet-description"
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
Reference in New Issue
Block a user