Add contact and about pages with shared CTA buttons.
Wire forms to the Meshkee contact API, tighten page-title spacing, and polish outline button hover so it fades cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b0ba7b1de8
commit
e6404eaaec
@@ -0,0 +1,207 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/Button";
|
||||
import { ContentPageShell } from "@/components/ContentPageShell";
|
||||
import { images } from "@/data/assets";
|
||||
import { services, site } from "@/data/home";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "درباره ما",
|
||||
description:
|
||||
"آشنایی با تیم برنامهنویسی و تجارت الکترونیک مشکی — طراحی وبسایت، اپلیکیشن، CMS، سئو و گرافیک.",
|
||||
alternates: { canonical: "/about" },
|
||||
};
|
||||
|
||||
const highlights = [
|
||||
{
|
||||
title: "تیم متخصص",
|
||||
body: "برای هر بخش — از گرافیک و برنامهنویسی تا سرور و سئو — متخصص جداگانهای کنار پروژهتان میماند.",
|
||||
},
|
||||
{
|
||||
title: "یکپارچگی کامل",
|
||||
body: "وبسایت، اپلیکیشن و سیستمهای مدیریتی طوری طراحی میشوند که با هم کار کنند، نه جدا از هم.",
|
||||
},
|
||||
{
|
||||
title: "رشد پایدار",
|
||||
body: "از طراحی اولیه تا سئو و مدیریت محتوا، مسیر رشد برند آنلاین شما را همراهی میکنیم.",
|
||||
},
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: "112+", label: "وبسایت" },
|
||||
{ value: "28+", label: "لوگو و هویت بصری" },
|
||||
{ value: "7+", label: "اپلیکیشن" },
|
||||
];
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<ContentPageShell flushTop>
|
||||
<section>
|
||||
<div className="page-title-band">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-9 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+0.5rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+0.65rem)]"
|
||||
>
|
||||
About
|
||||
</p>
|
||||
<div className="reveal relative z-10 max-w-2xl">
|
||||
<h1 className="whitespace-normal text-[clamp(1.35rem,3.6vw,2.75rem)] font-normal leading-tight tracking-tight text-ink">
|
||||
درباره ما
|
||||
</h1>
|
||||
<p className="mt-1 text-sm leading-6 text-muted md:text-base">
|
||||
تیم برنامهنویسی و تجارت الکترونیک مشکی — راهکارهای مدرن برای
|
||||
تجارت شما.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white">
|
||||
<div className="mx-auto grid max-w-7xl items-center gap-12 px-5 py-16 lg:grid-cols-2 lg:gap-16 lg:px-8 lg:py-24">
|
||||
<div className="reveal min-w-0">
|
||||
<p className="mb-4 text-sm text-muted">ما کی هستیم؟</p>
|
||||
<h2 className="mb-6 max-w-[16em] text-[clamp(1.2rem,3vw,2rem)] font-normal leading-snug tracking-tight text-ink">
|
||||
{site.fullTitle}
|
||||
</h2>
|
||||
<p className="text-body mb-5 text-sm leading-8 text-ink-soft md:text-base md:leading-9">
|
||||
مشکی یک تیم تخصصی در حوزه طراحی و توسعه محصولات دیجیتال است. ما
|
||||
کنار کسبوکارها میایستیم تا وبسایت، اپلیکیشن، سیستم مدیریتی و
|
||||
هویت بصریشان را از ایده تا اجرا پیش ببریم.
|
||||
</p>
|
||||
<p className="text-body mb-8 text-sm leading-8 text-muted md:text-base md:leading-9">
|
||||
دفتر ما در قم است و پروژهها را با تمرکز روی کیفیت فنی، تجربه
|
||||
کاربری و رشد پایدار جلو میبریم — نه فقط تحویل یک محصول، بلکه
|
||||
ساخت مسیری برای فروش و حضور آنلاین قویتر.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button href="/contact">تماس با ما</Button>
|
||||
<Button href="/portfolio" variant="outline">
|
||||
نمونهکارها
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="reveal reveal-delay-1 relative mx-auto w-full max-w-md lg:max-w-none">
|
||||
<div className="pointer-events-none absolute inset-8 rounded-full bg-[radial-gradient(circle,var(--glow),transparent_70%)] blur-2xl" />
|
||||
<div className="relative aspect-square">
|
||||
<img
|
||||
src={images.hero}
|
||||
alt={site.fullTitle}
|
||||
className="h-full w-full object-contain drop-shadow-[0_30px_60px_rgba(14,17,22,0.14)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-y border-line/60 bg-[#FAFAFA]">
|
||||
<div className="mx-auto grid max-w-7xl grid-cols-1 divide-y divide-line/60 sm:grid-cols-3 sm:divide-x sm:divide-y-0">
|
||||
{stats.map((stat) => (
|
||||
<div
|
||||
key={stat.label}
|
||||
className="reveal flex items-center gap-4 px-5 py-8 lg:px-8 lg:py-10"
|
||||
>
|
||||
<p
|
||||
className="latin shrink-0 text-[clamp(2rem,4vw,2.75rem)] font-light leading-none tracking-tight text-ink"
|
||||
dir="ltr"
|
||||
>
|
||||
{stat.value}
|
||||
</p>
|
||||
<span aria-hidden className="h-10 w-px shrink-0 bg-line/80" />
|
||||
<p className="text-sm text-ink-soft">{stat.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-7xl px-5 py-16 lg:px-8 lg:py-24">
|
||||
<div className="reveal mb-12 max-w-2xl">
|
||||
<h2 className="mb-3 text-[clamp(1.2rem,3vw,2rem)] font-normal tracking-tight text-ink">
|
||||
چطور کار میکنیم؟
|
||||
</h2>
|
||||
<p className="text-sm leading-8 text-muted md:text-base">
|
||||
سه اصل ساده که در همه پروژههای مشکی ثابت میماند.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-10 md:grid-cols-3 md:gap-8">
|
||||
{highlights.map((item, index) => (
|
||||
<div
|
||||
key={item.title}
|
||||
className={`reveal min-w-0 ${
|
||||
index === 1
|
||||
? "reveal-delay-1"
|
||||
: index === 2
|
||||
? "reveal-delay-2"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<p className="latin mb-3 text-xs tracking-[0.18em] text-muted">
|
||||
0{index + 1}
|
||||
</p>
|
||||
<h3 className="mb-3 text-lg font-normal text-ink">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-sm leading-8 text-muted">{item.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-line/50 bg-[#FAFAFA]">
|
||||
<div className="mx-auto max-w-7xl px-5 py-16 lg:px-8 lg:py-20">
|
||||
<div className="reveal mb-10 max-w-2xl">
|
||||
<h2 className="mb-3 text-[clamp(1.2rem,3vw,2rem)] font-normal tracking-tight text-ink">
|
||||
خدمات ما
|
||||
</h2>
|
||||
<p className="text-sm leading-8 text-muted md:text-base">
|
||||
از ایده تا اجرا، هر بخشی که برای حضور دیجیتال نیاز دارید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{services.map((service, index) => (
|
||||
<li key={service.href}>
|
||||
<Link
|
||||
href={service.href}
|
||||
className={`reveal group block rounded-2xl border border-line/60 bg-white px-5 py-5 transition hover:border-ink/20 ${
|
||||
index % 3 === 1
|
||||
? "reveal-delay-1"
|
||||
: index % 3 === 2
|
||||
? "reveal-delay-2"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<p className="mb-2 text-base text-ink group-hover:text-ink">
|
||||
{service.title}
|
||||
</p>
|
||||
<p className="latin title-en-sm text-muted">
|
||||
{service.titleEn}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="reveal mt-14 flex flex-wrap items-center justify-between gap-6 border-t border-line/60 pt-10">
|
||||
<div>
|
||||
<p className="mb-2 text-lg text-ink">
|
||||
آمادهاید پروژه بعدی را شروع کنید؟
|
||||
</p>
|
||||
<p className="text-sm text-muted">
|
||||
از طریق فرم تماس یا شماره{" "}
|
||||
<span className="latin" dir="ltr">
|
||||
{site.phone}
|
||||
</span>{" "}
|
||||
با ما در ارتباط باشید.
|
||||
</p>
|
||||
</div>
|
||||
<Button href="/contact">برو به تماس با ما</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ContentPageShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { SITE_DOMAIN } from "@/lib/api";
|
||||
|
||||
const API_BASE_URL = "https://api.meshkee.com/api/v1";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const body = (await request.json()) as {
|
||||
title?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
cellNumber?: string;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
if (!body.title?.trim() || !body.name?.trim() || !body.text?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ message: "نام، موضوع و متن پیام الزامی است." },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/tenants/${SITE_DOMAIN}/contact-submissions`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: body.title.trim(),
|
||||
name: body.name.trim(),
|
||||
email: body.email?.trim() || undefined,
|
||||
cellNumber: body.cellNumber?.trim() || undefined,
|
||||
text: body.text.trim(),
|
||||
}),
|
||||
cache: "no-store",
|
||||
},
|
||||
);
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
return NextResponse.json(data, { status: response.status });
|
||||
}
|
||||
@@ -84,10 +84,10 @@ export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
|
||||
<ContentPageShell flushTop>
|
||||
<article className="pb-20 lg:pb-28">
|
||||
<div className="page-title-band mb-8 md:mb-10">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-4 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-5 lg:pt-[calc(var(--header-height)+1rem)]">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-10 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+2.4rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+2.6rem)]"
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+1.75rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+1.95rem)]"
|
||||
>
|
||||
{titleEn}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ContentPageShell } from "@/components/ContentPageShell";
|
||||
import { ContactForm } from "@/components/ContactForm";
|
||||
import { site, socials } from "@/data/home";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "تماس با ما",
|
||||
description:
|
||||
"با تیم مشکی در تماس باشید — فرم تماس، تلفن، ایمیل و آدرس دفتر قم.",
|
||||
alternates: { canonical: "/contact" },
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<ContentPageShell flushTop>
|
||||
<section>
|
||||
<div className="page-title-band">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-9 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+0.5rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+0.65rem)]"
|
||||
>
|
||||
Contact
|
||||
</p>
|
||||
<div className="reveal relative z-10 max-w-2xl">
|
||||
<h1 className="whitespace-normal text-[clamp(1.35rem,3.6vw,2.75rem)] font-normal leading-tight tracking-tight text-ink">
|
||||
تماس با ما
|
||||
</h1>
|
||||
<p className="mt-1 text-sm leading-6 text-muted md:text-base">
|
||||
سوالی دارید یا میخواهید پروژهتان را شروع کنید؟ با تیم مشکی در
|
||||
تماس باشید.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex min-h-[calc(100svh-var(--header-height)-7.5rem)] items-center overflow-hidden bg-[linear-gradient(180deg,#fafafa_0%,#eef1f5_45%,#f3f5f7_100%)] py-16 lg:py-20">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_80%_20%,rgba(196,77,184,0.1),transparent_50%)]" />
|
||||
|
||||
<div className="relative mx-auto grid w-full max-w-7xl items-center gap-14 px-5 lg:grid-cols-[1fr_1.1fr] lg:gap-16 lg:px-8">
|
||||
<div className="reveal flex min-w-0 flex-col gap-8">
|
||||
<div className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-4 gap-y-6">
|
||||
<span className="text-sm text-muted">شماره تماس</span>
|
||||
<a
|
||||
href={site.phoneHref}
|
||||
className="latin link-hover justify-self-start text-lg font-medium tracking-tight text-ink md:text-xl"
|
||||
dir="ltr"
|
||||
>
|
||||
{site.phone}
|
||||
</a>
|
||||
|
||||
<span className="text-sm text-muted">ایمیل</span>
|
||||
<a
|
||||
href={`mailto:${site.email}`}
|
||||
className="latin link-hover justify-self-start text-sm text-ink-soft md:text-base"
|
||||
dir="ltr"
|
||||
>
|
||||
{site.email}
|
||||
</a>
|
||||
|
||||
<span className="text-sm text-muted">آدرس</span>
|
||||
<a
|
||||
href={site.mapUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="link-hover justify-self-start text-start text-sm leading-7 text-ink-soft md:text-base md:leading-8"
|
||||
>
|
||||
{site.address}
|
||||
</a>
|
||||
|
||||
<span className="pt-1 text-sm text-muted">شبکههای اجتماعی</span>
|
||||
<div className="flex flex-wrap items-center justify-start gap-3 pt-1">
|
||||
{socials.map((social) => (
|
||||
<a
|
||||
key={social.label}
|
||||
href={social.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={social.label}
|
||||
title={social.label}
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full border border-line/70 bg-white/60 text-ink-soft shadow-[0_8px_20px_rgba(14,17,22,0.04)] backdrop-blur-sm transition hover:border-ink/20 hover:text-ink"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="h-4 w-4"
|
||||
aria-hidden
|
||||
>
|
||||
<path d={social.icon} />
|
||||
</svg>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative overflow-hidden rounded-[1.5rem] border border-line/70 bg-white/50 shadow-[0_16px_40px_rgba(14,17,22,0.05)] backdrop-blur-sm">
|
||||
<div className="relative aspect-[16/10] w-full overflow-hidden sm:aspect-[2/1]">
|
||||
<iframe
|
||||
title="موقعیت دفتر مشکی روی نقشه"
|
||||
src={`https://maps.google.com/maps?q=${site.mapLat},${site.mapLng}&z=16&hl=fa&output=embed`}
|
||||
className="absolute inset-0 h-full w-full border-0"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
<a
|
||||
href={site.mapUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="absolute bottom-3 inset-inline-3 inline-flex max-w-max items-center gap-2 rounded-full border border-white/70 bg-white/90 px-3.5 py-2 text-sm text-ink shadow-sm backdrop-blur-md transition hover:bg-white"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className="h-4 w-4 shrink-0 text-accent"
|
||||
aria-hidden
|
||||
>
|
||||
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 110-5 2.5 2.5 0 010 5z" />
|
||||
</svg>
|
||||
مشاهده روی نقشه
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="reveal reveal-delay-1 relative min-w-0 overflow-hidden rounded-[1.75rem] border border-white/70 bg-white/45 p-5 shadow-[0_20px_50px_rgba(14,17,22,0.06)] backdrop-blur-xl sm:p-6 md:p-8">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 bg-[linear-gradient(145deg,rgba(255,255,255,0.75)_0%,rgba(255,255,255,0.2)_40%,transparent_70%)]"
|
||||
/>
|
||||
<div className="relative">
|
||||
<h2 className="mb-2 text-xl font-normal text-ink md:text-2xl">
|
||||
فرم تماس
|
||||
</h2>
|
||||
<p className="mb-8 text-sm text-muted">
|
||||
ما همیشه دنبال دوستای جدید هستیم.
|
||||
</p>
|
||||
|
||||
<ContactForm variant="light" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ContentPageShell>
|
||||
);
|
||||
}
|
||||
+112
-5
@@ -117,20 +117,127 @@ h4 {
|
||||
}
|
||||
|
||||
/* Gradient hover utilities */
|
||||
/* Simple fade for primary CTAs */
|
||||
.btn-primary {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
background: var(--ink);
|
||||
color: var(--surface);
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
color 0.25s ease,
|
||||
box-shadow 0.25s ease;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
.btn-primary::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
border-radius: inherit;
|
||||
background: var(--brand-gradient);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled)::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Outline CTA: opacity-only fades (no blink on mouse-out) */
|
||||
.btn-outline {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
isolation: isolate;
|
||||
border: 1px solid transparent;
|
||||
background-color: #fff;
|
||||
color: var(--ink-soft);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-outline::before,
|
||||
.btn-outline::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
border-radius: inherit;
|
||||
padding: 1px;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
.btn-outline::after {
|
||||
background: var(--line);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-outline::before {
|
||||
background: var(--brand-gradient);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.btn-outline:hover::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.btn-outline:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-outline-label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.btn-outline-label-base {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-outline-label-grad {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: inherit;
|
||||
gap: 0.375rem;
|
||||
background-image: var(--brand-gradient);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
-webkit-text-fill-color: transparent;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-outline:hover .btn-outline-label-base {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.btn-outline:hover .btn-outline-label-grad {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.link-hover {
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
|
||||
@@ -127,11 +127,11 @@ export default async function InstructionDetailPage({
|
||||
<ContentPageShell flushTop>
|
||||
<article className="pb-20 lg:pb-28">
|
||||
<div className="page-title-band mb-8 md:mb-10">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-4 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-5 lg:pt-[calc(var(--header-height)+1rem)]">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-10 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
{titleEn ? (
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+2.4rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+2.6rem)]"
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+1.75rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+1.95rem)]"
|
||||
>
|
||||
{titleEn}
|
||||
</p>
|
||||
|
||||
@@ -99,10 +99,10 @@ export default async function InstructionListPage({
|
||||
<ContentPageShell flushTop>
|
||||
<section className="pb-20 lg:pb-28">
|
||||
<div className="page-title-band mb-8 md:mb-10">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-3 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-4 lg:pt-[calc(var(--header-height)+1rem)]">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-9 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+1.15rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+1.35rem)]"
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+0.5rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+0.65rem)]"
|
||||
>
|
||||
Instructions
|
||||
</p>
|
||||
|
||||
@@ -123,11 +123,11 @@ export default async function PortfolioDetailPage({
|
||||
<ContentPageShell flushTop>
|
||||
<article className="pb-20 lg:pb-28">
|
||||
<div className="page-title-band mb-8 md:mb-10">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-4 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-5 lg:pt-[calc(var(--header-height)+1rem)]">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-10 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
{titleEn ? (
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+2.4rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+2.6rem)]"
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+1.75rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+1.95rem)]"
|
||||
>
|
||||
{titleEn}
|
||||
</p>
|
||||
|
||||
@@ -95,10 +95,10 @@ export default async function PortfolioListPage({
|
||||
<ContentPageShell flushTop>
|
||||
<section className="pb-20 lg:pb-28">
|
||||
<div className="page-title-band mb-8 md:mb-10">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-3 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-4 lg:pt-[calc(var(--header-height)+1rem)]">
|
||||
<div className="relative mx-auto max-w-7xl px-5 pb-9 pt-[calc(var(--header-height)+0.15rem)] lg:px-8 lg:pb-12 lg:pt-[calc(var(--header-height)+0.3rem)]">
|
||||
<p
|
||||
aria-hidden
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+1.15rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+1.35rem)]"
|
||||
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+0.5rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+0.65rem)]"
|
||||
>
|
||||
Portfolio
|
||||
</p>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function AppSection() {
|
||||
بیشتر کاربران را به همراه دارد.
|
||||
</p>
|
||||
<a
|
||||
href="#contact"
|
||||
href="/contact"
|
||||
className="btn-primary inline-flex rounded-full px-6 py-3.5 text-sm"
|
||||
>
|
||||
با ما تماس بگیرید
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import Link from "next/link";
|
||||
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||
|
||||
type ButtonVariant = "primary" | "outline";
|
||||
type ButtonSize = "sm" | "md";
|
||||
|
||||
type CommonProps = {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type ButtonAsLink = CommonProps &
|
||||
Omit<ComponentPropsWithoutRef<typeof Link>, "className" | "children"> & {
|
||||
href: string;
|
||||
};
|
||||
|
||||
type ButtonAsAnchor = CommonProps &
|
||||
Omit<ComponentPropsWithoutRef<"a">, "className" | "children" | "href"> & {
|
||||
href: string;
|
||||
external?: true;
|
||||
};
|
||||
|
||||
type ButtonAsButton = CommonProps &
|
||||
Omit<ComponentPropsWithoutRef<"button">, "className" | "children"> & {
|
||||
href?: undefined;
|
||||
};
|
||||
|
||||
export type ButtonProps = ButtonAsLink | ButtonAsAnchor | ButtonAsButton;
|
||||
|
||||
const sizeClass: Record<ButtonSize, string> = {
|
||||
sm: "px-3.5 py-2 text-xs",
|
||||
md: "px-6 py-3.5 text-sm",
|
||||
};
|
||||
|
||||
function cx(...parts: Array<string | false | null | undefined>) {
|
||||
return parts.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function renderLabel(variant: ButtonVariant, children: ReactNode) {
|
||||
if (variant !== "outline") return children;
|
||||
return (
|
||||
<span className="btn-outline-label">
|
||||
<span className="btn-outline-label-base inline-flex items-center gap-1.5">
|
||||
{children}
|
||||
</span>
|
||||
<span className="btn-outline-label-grad" aria-hidden>
|
||||
{children}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function Button(props: ButtonProps) {
|
||||
const {
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const classes = cx(
|
||||
variant === "primary" ? "btn-primary" : "btn-outline",
|
||||
"inline-flex items-center justify-center gap-1.5 rounded-full font-medium",
|
||||
sizeClass[size],
|
||||
className,
|
||||
);
|
||||
|
||||
const content = renderLabel(variant, children);
|
||||
|
||||
if ("href" in props && props.href) {
|
||||
const { href, external, ...linkRest } = rest as Omit<
|
||||
ButtonAsLink & ButtonAsAnchor,
|
||||
keyof CommonProps
|
||||
> & { external?: true };
|
||||
|
||||
if (external || href.startsWith("http") || href.startsWith("tel:") || href.startsWith("mailto:")) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={classes}
|
||||
{...(external
|
||||
? { target: "_blank", rel: "noopener noreferrer" }
|
||||
: {})}
|
||||
{...(linkRest as ComponentPropsWithoutRef<"a">)}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={href} className={classes} {...(linkRest as Omit<ComponentPropsWithoutRef<typeof Link>, "href" | "className" | "children">)}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const buttonRest = rest as Omit<ButtonAsButton, keyof CommonProps>;
|
||||
|
||||
return (
|
||||
<button type={buttonRest.type ?? "button"} className={classes} {...buttonRest}>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -35,13 +35,13 @@ export function CmsSection() {
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<a
|
||||
href="#contact"
|
||||
href="/contact"
|
||||
className="btn-primary rounded-full px-6 py-3.5 text-sm"
|
||||
>
|
||||
با ما تماس بگیرید
|
||||
</a>
|
||||
<a
|
||||
href="#contact"
|
||||
href="/contact"
|
||||
className="link-hover rounded-full px-5 py-3.5 text-sm text-ink-soft"
|
||||
>
|
||||
دوست دارم بیشتر بدونم
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
|
||||
type ContactFormProps = {
|
||||
variant?: "light" | "dark";
|
||||
};
|
||||
|
||||
export function ContactForm({ variant = "dark" }: ContactFormProps) {
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
|
||||
const isLight = variant === "light";
|
||||
|
||||
const labelClass = isLight ? "text-ink-soft" : "text-white/70";
|
||||
const hintClass = isLight ? "text-muted" : "text-white/40";
|
||||
const inputClass = isLight
|
||||
? "rounded-xl border border-white/80 bg-white/55 px-4 py-3 text-ink outline-none shadow-[inset_0_1px_0_rgba(255,255,255,0.7)] backdrop-blur-sm transition placeholder:text-muted focus:border-ink/25 focus:bg-white/80"
|
||||
: "rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-surface outline-none transition placeholder:text-white/30 focus:border-accent";
|
||||
const errorClass = isLight ? "text-sm text-red-600" : "text-sm text-red-300";
|
||||
const successClass = isLight
|
||||
? "text-sm text-emerald-700"
|
||||
: "text-sm text-emerald-300";
|
||||
|
||||
async function submit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
const formElement = event.currentTarget;
|
||||
const form = new FormData(formElement);
|
||||
|
||||
const name = String(form.get("name") ?? "").trim();
|
||||
const phone = String(form.get("phone") ?? "").trim();
|
||||
const email = String(form.get("email") ?? "").trim();
|
||||
const subject = String(form.get("subject") ?? "").trim();
|
||||
const message = String(form.get("message") ?? "").trim();
|
||||
|
||||
if (!name || !phone || !subject || !message) {
|
||||
setError("لطفا نام، شماره تماس، موضوع و متن پیام را کامل کنید.");
|
||||
setSuccess("");
|
||||
return;
|
||||
}
|
||||
|
||||
setError("");
|
||||
setSuccess("");
|
||||
setSending(true);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
title: subject,
|
||||
name,
|
||||
cellNumber: phone,
|
||||
email: email || undefined,
|
||||
text: message,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Contact submission failed");
|
||||
}
|
||||
|
||||
formElement.reset();
|
||||
setSuccess(
|
||||
"پیام شما با موفقیت ثبت شد. همکاران ما بهزودی با شما تماس میگیرند.",
|
||||
);
|
||||
} catch {
|
||||
setError("ارسال پیام انجام نشد. لطفا دوباره تلاش کنید.");
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="grid gap-4" onSubmit={submit} noValidate>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className={labelClass}>نام</span>
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="نام شما"
|
||||
autoComplete="name"
|
||||
required
|
||||
className={inputClass}
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className={labelClass}>شماره تماس</span>
|
||||
<input
|
||||
name="phone"
|
||||
type="tel"
|
||||
placeholder="۰۹۱۲…"
|
||||
inputMode="tel"
|
||||
autoComplete="tel"
|
||||
required
|
||||
className={inputClass}
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className={labelClass}>
|
||||
ایمیل <span className={hintClass}>(اختیاری)</span>
|
||||
</span>
|
||||
<input
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
autoComplete="email"
|
||||
className={inputClass}
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className={labelClass}>موضوع</span>
|
||||
<input
|
||||
name="subject"
|
||||
type="text"
|
||||
placeholder="موضوع پیام"
|
||||
required
|
||||
className={inputClass}
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className={labelClass}>پیام</span>
|
||||
<textarea
|
||||
name="message"
|
||||
rows={4}
|
||||
placeholder="چطور میتونیم کمکتون کنیم؟"
|
||||
required
|
||||
className={`resize-none ${inputClass}`}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{error ? (
|
||||
<p className={errorClass} role="alert">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
{success ? (
|
||||
<p className={successClass} role="status">
|
||||
{success}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={sending}
|
||||
className="btn-primary mt-2 w-full cursor-pointer rounded-full px-6 py-3.5 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{sending ? "در حال ارسال…" : "منتظر تماس هستم"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { site } from "@/data/home";
|
||||
import { ContactForm } from "@/components/ContactForm";
|
||||
|
||||
export function ContactSection() {
|
||||
return (
|
||||
@@ -17,6 +16,14 @@ export function ContactSection() {
|
||||
>
|
||||
{site.phone}
|
||||
</a>
|
||||
<p className="mb-2 text-sm text-white/55">ایمیل</p>
|
||||
<a
|
||||
href={`mailto:${site.email}`}
|
||||
className="latin link-hover mb-8 block text-sm text-white/80 md:text-base"
|
||||
dir="ltr"
|
||||
>
|
||||
{site.email}
|
||||
</a>
|
||||
<p className="mb-2 text-sm text-white/55">آدرس</p>
|
||||
<p className="max-w-sm text-sm leading-7 text-white/80 md:text-base md:leading-8">
|
||||
{site.address}
|
||||
@@ -28,45 +35,10 @@ export function ContactSection() {
|
||||
تماس با ما
|
||||
</h2>
|
||||
<p className="mb-8 text-sm text-white/60">
|
||||
ما همیشه دنبال دوستای جدید هستیم — یا ما خدمتتون تماس بگیریم.
|
||||
ما همیشه دنبال دوستای جدید هستیم.
|
||||
</p>
|
||||
|
||||
<form className="grid gap-4" onSubmit={(e) => e.preventDefault()}>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="text-white/70">نام</span>
|
||||
<input
|
||||
name="name"
|
||||
type="text"
|
||||
placeholder="نام شما"
|
||||
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-surface outline-none transition placeholder:text-white/30 focus:border-accent"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="text-white/70">شماره تماس</span>
|
||||
<input
|
||||
name="phone"
|
||||
type="tel"
|
||||
placeholder="۰۹۱۲…"
|
||||
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-surface outline-none transition placeholder:text-white/30 focus:border-accent"
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm">
|
||||
<span className="text-white/70">پیام</span>
|
||||
<textarea
|
||||
name="message"
|
||||
rows={4}
|
||||
placeholder="چطور میتونیم کمکتون کنیم؟"
|
||||
className="resize-none rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-surface outline-none transition placeholder:text-white/30 focus:border-accent"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary mt-2 rounded-full px-6 py-3.5 text-sm font-medium"
|
||||
>
|
||||
منتظر تماس هستم
|
||||
</button>
|
||||
</form>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -101,12 +101,20 @@ export function Footer() {
|
||||
<p className="mt-3 max-w-[16rem] text-sm leading-7 text-muted">
|
||||
{site.address}
|
||||
</p>
|
||||
<Link
|
||||
href="#contact"
|
||||
className="mt-5 inline-flex rounded-full border border-line px-4 py-2 text-xs text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||||
>
|
||||
تماس با ما
|
||||
</Link>
|
||||
<div className="mt-5 flex flex-wrap gap-2">
|
||||
<Link
|
||||
href="/about"
|
||||
className="inline-flex rounded-full border border-line px-4 py-2 text-xs text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||||
>
|
||||
درباره ما
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="inline-flex rounded-full border border-line px-4 py-2 text-xs text-ink-soft transition hover:border-ink/30 hover:text-ink"
|
||||
>
|
||||
تماس با ما
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+39
-34
@@ -2,17 +2,10 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { nav, site, type NavItem } from "@/data/home";
|
||||
import { Button } from "@/components/Button";
|
||||
import { nav, site, socials, type NavItem } from "@/data/home";
|
||||
import { images } from "@/data/assets";
|
||||
|
||||
const socials = [
|
||||
{ label: "اینستاگرام", href: "https://instagram.com", icon: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z" },
|
||||
{ label: "یوتیوب", href: "https://youtube.com", icon: "M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" },
|
||||
{ label: "لینکدین", href: "https://linkedin.com", icon: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" },
|
||||
{ label: "فیسبوک", href: "https://facebook.com", icon: "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" },
|
||||
{ label: "آپارات", href: "https://aparat.com", icon: "M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 4a8 8 0 110 16A8 8 0 0112 4zm0 2a6 6 0 100 12A6 6 0 0012 6zm-1 3l5 3-5 3V9z" },
|
||||
];
|
||||
|
||||
function DesktopNavItem({ item }: { item: NavItem }) {
|
||||
if (!item.children?.length) {
|
||||
return (
|
||||
@@ -101,7 +94,7 @@ export function Header() {
|
||||
<Link href="/about" className="link-hover text-xs text-muted">
|
||||
درباره ما
|
||||
</Link>
|
||||
<Link href="#contact" className="link-hover text-xs text-muted">
|
||||
<Link href="/contact" className="link-hover text-xs text-muted">
|
||||
تماس با ما
|
||||
</Link>
|
||||
<a
|
||||
@@ -139,22 +132,23 @@ export function Header() {
|
||||
|
||||
{/* Login + CTA on the left */}
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<a
|
||||
href="/login"
|
||||
className="flex items-center gap-1.5 rounded-full border border-line px-3.5 py-2 text-xs text-ink-soft transition hover:border-ink hover:text-ink"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className="h-3.5 w-3.5" aria-hidden>
|
||||
<circle cx="12" cy="8" r="4"/>
|
||||
<path d="M4 20c0-4 3.582-7 8-7s8 3 8 7"/>
|
||||
<Button href="/login" variant="outline" size="sm">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.8"
|
||||
className="h-3.5 w-3.5"
|
||||
aria-hidden
|
||||
>
|
||||
<circle cx="12" cy="8" r="4" />
|
||||
<path d="M4 20c0-4 3.582-7 8-7s8 3 8 7" />
|
||||
</svg>
|
||||
ورود
|
||||
</a>
|
||||
<a
|
||||
href="#contact"
|
||||
className="btn-primary rounded-full px-4 py-2 text-sm"
|
||||
>
|
||||
</Button>
|
||||
<Button href="/contact" size="sm" className="px-4">
|
||||
تماس با ما
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -238,21 +232,32 @@ export function Header() {
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-3 flex gap-2">
|
||||
<a
|
||||
<div className="mt-3 flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
href="/about"
|
||||
variant="outline"
|
||||
className="flex-1 py-3"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
درباره ما
|
||||
</Button>
|
||||
<Button
|
||||
href="/contact"
|
||||
className="flex-1 py-3"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
تماس با ما
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
href="/login"
|
||||
className="flex flex-1 items-center justify-center gap-1.5 rounded-full border border-line py-3 text-sm text-ink-soft"
|
||||
variant="outline"
|
||||
className="w-full py-3"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
ورود
|
||||
</a>
|
||||
<a
|
||||
href="#contact"
|
||||
className="btn-primary flex-1 rounded-full py-3 text-center text-sm"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
تماس با ما
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,39 @@ export const site = {
|
||||
phoneHref: "tel:+989024200240",
|
||||
email: "info@meshkee.com",
|
||||
address: "قم، نبش کریمی ۳۴، ساختمان ظفر، طبقهٔ چهارم",
|
||||
mapUrl: "https://maps.app.goo.gl/GdDdMfMFzjobcggv5",
|
||||
mapLat: 34.598146,
|
||||
mapLng: 50.87917,
|
||||
};
|
||||
|
||||
export const socials = [
|
||||
{
|
||||
label: "اینستاگرام",
|
||||
href: "https://instagram.com",
|
||||
icon: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z",
|
||||
},
|
||||
{
|
||||
label: "یوتیوب",
|
||||
href: "https://youtube.com",
|
||||
icon: "M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
|
||||
},
|
||||
{
|
||||
label: "لینکدین",
|
||||
href: "https://linkedin.com",
|
||||
icon: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
|
||||
},
|
||||
{
|
||||
label: "فیسبوک",
|
||||
href: "https://facebook.com",
|
||||
icon: "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z",
|
||||
},
|
||||
{
|
||||
label: "آپارات",
|
||||
href: "https://aparat.com",
|
||||
icon: "M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 4a8 8 0 110 16A8 8 0 0112 4zm0 2a6 6 0 100 12A6 6 0 0012 6zm-1 3l5 3-5 3V9z",
|
||||
},
|
||||
];
|
||||
|
||||
export type NavItem = {
|
||||
label: string;
|
||||
href: string;
|
||||
|
||||
Reference in New Issue
Block a user