a
This commit is contained in:
+119
-15
@@ -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: oklch(1 0 0);
|
||||||
--background: #0a0a0a;
|
--foreground: oklch(0.145 0 0);
|
||||||
--foreground: #ededed;
|
--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 {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
@apply font-sans;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|||||||
+24
-15
@@ -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
@@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { cn } from "cn"
|
||||||
Generated
+4883
-93
File diff suppressed because it is too large
Load Diff
+12
-1
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user