This commit is contained in:
2026-09-09 16:03:24 +03:30
parent ce2cf49edc
commit 71cd5c8072
16 changed files with 6191 additions and 190 deletions
+116 -12
View File
@@ -1,26 +1,130 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
:root { @custom-variant dark (&:is(.dark *));
--background: #ffffff;
--foreground: #171717;
}
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans); --font-sans: var(--font-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
--font-heading: var(--font-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
} }
@media (prefers-color-scheme: dark) {
:root { :root {
--background: #0a0a0a; --background: oklch(1 0 0);
--foreground: #ededed; --foreground: oklch(0.145 0 0);
} --card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
} }
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body { body {
background: var(--background); @apply bg-background text-foreground;
color: var(--foreground); }
font-family: Arial, Helvetica, sans-serif; html {
@apply font-sans;
}
} }
+24 -15
View File
@@ -1,29 +1,38 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Vazirmatn } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { cn } from "@/lib/utils";
import Header from "@/components/shared/header";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({ const vazirmatn = Vazirmatn({
variable: "--font-geist-mono", subsets: ["arabic", "latin"],
subsets: ["latin"], variable: "--font-vazirmatn",
display: "swap",
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "شرکت فناوری نوین | راهکارهای هوشمند نرم‌افزاری",
description: "Generated by create next app", description: "ارائه‌دهنده تخصصی خدمات توسعه وب، اپلیکیشن‌های سازمانی و سامانه‌های هوشمند",
}; };
export default function RootLayout({ children }: LayoutProps<"/">) { export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return ( return (
<html <html lang="fa" dir="rtl">
lang="en" <body
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} className={cn(
"min-h-screen bg-background font-sans antialiased flex flex-col",
vazirmatn.className,
vazirmatn.variable
)}
> >
<body className="min-h-full flex flex-col">{children}</body> <Header />
<main className="flex-1">{children}</main>
</body>
</html> </html>
); );
} }
+10 -63
View File
@@ -1,69 +1,16 @@
import Image from "next/image"; import CtaBanner from "@/components/banner/CtaBanner";
import HeroSection from "@/components/features/home/hero-section";
import ProjectsSection from "@/components/features/projects/projects-section";
import { ServicesBentoGrid } from "@/components/features/services/services-bento-grid";
export default function Home() { export default function Home() {
return ( return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black"> <main className="flex min-h-screen flex-col">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start"> <HeroSection />
<Image <ProjectsSection/>
className="dark:invert h-5 w-[100px]" <CtaBanner/>
src="/next.svg" <ServicesBentoGrid />
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the{" "}
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
page.tsx
</code>{" "}
file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert h-[14px] w-4"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={14}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main> </main>
</div>
); );
} }
+25
View File
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
+94
View File
@@ -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>
);
}
+117
View File
@@ -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>
);
}
+174
View File
@@ -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>
</>
);
}
+48
View File
@@ -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 }
+66
View File
@@ -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 }
+147
View File
@@ -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,
}
+1
View File
@@ -0,0 +1 @@
export { cn } from "cn"
+4883 -93
View File
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -9,9 +9,20 @@
"lint": "eslint" "lint": "eslint"
}, },
"dependencies": { "dependencies": {
"@studio-freight/react-lenis": "^0.0.47",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cn": "^0.2.6",
"framer-motion": "^13.2.0",
"lucide-react": "^1.42.0",
"next": "16.3.4", "next": "16.3.4",
"radix-ui": "^1.6.7",
"react": "19.2.8", "react": "19.2.8",
"react-dom": "19.2.8" "react-dom": "19.2.8",
"shadcn": "^4.21.0",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0",
"zustand": "^5.0.15"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",