پروژه کامل: فرانت متصل به API + داکرایز
- اتصال کامل فرانت به API: همه محتوای سایت (پروژهها، مقالات، خدمات، برند و...) از بکاند خوانده میشود (SiteProvider + useSite) - فرم تماس و خبرنامه متصل به بکاند و تستشده در مرورگر - ساختار مونوریپو: frontend/ + backend/ - Dockerfile برای هر دو بخش + docker-compose (PostgreSQL + FastAPI + nginx) - nginx با پروکسی /api — بدون نیاز به CORS در حالت داکر - README کامل فارسی
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import React from 'react'
|
||||
import { PageHero } from '../components/PageHero'
|
||||
import { Story } from '../components/home/Story'
|
||||
import { Stats } from '../components/home/Stats'
|
||||
import { Timeline } from '../components/home/Timeline'
|
||||
import { WhyUs } from '../components/home/WhyUs'
|
||||
import { Team } from '../components/home/Team'
|
||||
import { ContactSection } from '../components/ContactSection'
|
||||
import { IMAGES } from '../data/site'
|
||||
|
||||
export function About() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="درباره ما"
|
||||
lead="آرامش پلازا مجموعهای از معماران، مهندسان سازه و مدیران پروژه است که از سال ۱۳۸۳ در حوزه ساختوساز لوکس فعالیت میکند."
|
||||
image={IMAGES[3]}
|
||||
/>
|
||||
|
||||
<Story showLink={false} showLabel={false} alignTop={true}>
|
||||
<div className="mt-12 border-r-2 border-copper bg-char/40 p-6">
|
||||
<p className="text-sm font-light leading-8 text-ivory/80">
|
||||
«ساختمان برای ما فقط آجر و بتن نیست؛ بلکه تعهدی است به کیفیت زندگی ساکنان. ما فضاهایی میسازیم که در گذر زمان ارزش و هویت خود را حفظ کنند.»
|
||||
</p>
|
||||
<p className="mt-5 text-sm font-bold text-copper">کامران آرامش — مدیرعامل شرکت ساختمانی آرامش</p>
|
||||
</div>
|
||||
</Story>
|
||||
|
||||
<Stats />
|
||||
<Timeline />
|
||||
<WhyUs />
|
||||
<Team />
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { ArrowLeftIcon, ChevronLeftIcon, ClockIcon } from 'lucide-react';
|
||||
import { Reveal } from '../components/Reveal';
|
||||
import { ButtonLink } from '../components/Button';
|
||||
import { ArticleCard } from '../components/ArticleCard';
|
||||
import { Newsletter } from '../components/home/Newsletter';
|
||||
import { useSite } from '../api';
|
||||
|
||||
export function ArticleDetail() {
|
||||
const { slug } = useParams<{slug: string;}>();
|
||||
const { articles } = useSite();
|
||||
const article = articles.find((a) => a.slug === slug);
|
||||
|
||||
if (!article) {
|
||||
return (
|
||||
<section className="flex min-h-[70vh] flex-col items-center justify-center px-6 text-center">
|
||||
<h1 className="text-section font-bold text-ivory">این مقاله پیدا نشد</h1>
|
||||
<p className="mt-4 text-sm font-light text-ivory/60">فهرست کامل مقالات وبلاگ را ببینید.</p>
|
||||
<ButtonLink to="/blog" variant="copper" className="mt-8">
|
||||
بازگشت به وبلاگ
|
||||
<ArrowLeftIcon className="h-4 w-4" />
|
||||
</ButtonLink>
|
||||
</section>);
|
||||
|
||||
}
|
||||
|
||||
const related = articles.filter((a) => a.slug !== article.slug).slice(0, 3);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="relative flex h-[56vh] min-h-[400px] items-end overflow-hidden">
|
||||
<img src={article.image} alt="" aria-hidden="true" className="absolute inset-0 h-full w-full object-cover" />
|
||||
<div className="absolute inset-0 bg-ink/75" aria-hidden="true" />
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ background: 'linear-gradient(to top, #27221c 6%, rgba(39,34,28,0.15) 70%)' }}
|
||||
aria-hidden="true" />
|
||||
|
||||
<div className="relative mx-auto w-full max-w-site px-6 pb-16 lg:px-10">
|
||||
<nav aria-label="مسیر صفحه" className="flex items-center gap-2 text-xs font-light text-ivory/50">
|
||||
<Link to="/" className="transition-colors hover:text-copper">
|
||||
صفحه اصلی
|
||||
</Link>
|
||||
<ChevronLeftIcon className="h-3.5 w-3.5" />
|
||||
<Link to="/blog" className="transition-colors hover:text-copper">
|
||||
وبلاگ
|
||||
</Link>
|
||||
<ChevronLeftIcon className="h-3.5 w-3.5" />
|
||||
<span className="text-copper">{article.category}</span>
|
||||
</nav>
|
||||
<h1 className="mt-5 max-w-[700px] text-hero font-bold text-ivory">{article.title}</h1>
|
||||
<div className="mt-6 flex items-center gap-5 text-xs font-light text-ivory/60">
|
||||
<span>{article.date}</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<ClockIcon className="h-3.5 w-3.5" />
|
||||
{article.read}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<article className="bg-ink py-20 lg:py-28">
|
||||
<div className="mx-auto max-w-3xl px-6">
|
||||
<Reveal>
|
||||
<p className="border-r-2 border-copper pr-6 text-lg font-light leading-10 text-ivory/80">
|
||||
{article.excerpt}
|
||||
</p>
|
||||
</Reveal>
|
||||
{article.body.map((paragraph, i) =>
|
||||
<Reveal key={paragraph} delay={0.03 * i}>
|
||||
<p className="mt-8 text-base font-light leading-10 text-ivory/70">{paragraph}</p>
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<section className="bg-char py-20 lg:py-28" aria-label="مقالات مرتبط">
|
||||
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||
<h2 className="text-section font-bold text-ivory">مقالات مرتبط</h2>
|
||||
<div className="mt-6 h-px w-24 bg-copper/70" />
|
||||
<div className="mt-12 grid gap-6 lg:grid-cols-3">
|
||||
{related.map((item, i) =>
|
||||
<Reveal key={item.slug} delay={0.06 * i} className="h-full">
|
||||
<ArticleCard article={item} />
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Newsletter />
|
||||
</>);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ArrowLeftIcon, ClockIcon } from 'lucide-react'
|
||||
import { PageHero } from '../components/PageHero'
|
||||
import { Reveal } from '../components/Reveal'
|
||||
import { ArticleCard } from '../components/ArticleCard'
|
||||
import { Newsletter } from '../components/home/Newsletter'
|
||||
import { IMAGES } from '../data/site'
|
||||
import { useSite } from '../api'
|
||||
|
||||
export function Blog() {
|
||||
const { articles } = useSite()
|
||||
const [featured, ...rest] = articles
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="وبلاگ"
|
||||
lead="یادداشتهای تیم معماری، اجرا و سرمایهگذاری آرامش پلازا درباره آنچه در کارگاه و بازار میگذرد."
|
||||
image={IMAGES[2]}
|
||||
/>
|
||||
|
||||
<section className="bg-ink py-24 lg:py-32" aria-label="مقالات">
|
||||
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||
<Reveal>
|
||||
<Link
|
||||
to={`/blog/${featured.slug}`}
|
||||
className="group grid items-stretch border border-ivory/10 bg-char transition-[transform,box-shadow,border-color] duration-300 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-lift lg:grid-cols-2"
|
||||
aria-label={`خواندن مقاله ${featured.title}`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<img
|
||||
src={featured.image}
|
||||
alt={`تصویر مقاله ${featured.title}`}
|
||||
className="h-full min-h-[20rem] w-full object-cover transition-transform duration-500 ease-lux group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center p-8 lg:p-12">
|
||||
<p className="text-xs font-light text-copper">مقاله ویژه — {featured.category}</p>
|
||||
<h2 className="mt-6 text-2xl font-bold leading-[2.2] md:leading-[2.2] text-ivory transition-colors duration-200 ease-lux group-hover:text-copper md:text-3xl">
|
||||
{featured.title}
|
||||
</h2>
|
||||
<p className="mt-6 text-sm font-light leading-10 text-ivory/65">{featured.excerpt}</p>
|
||||
<div className="mt-10 flex items-center justify-between border-t border-ivory/10 pt-8 text-xs font-light text-ivory/50">
|
||||
<span className="flex items-center gap-4">
|
||||
<span>{featured.date}</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<ClockIcon className="h-3.5 w-3.5" />
|
||||
{featured.read}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-2 text-copper">
|
||||
ادامه مطلب
|
||||
<ArrowLeftIcon className="h-3.5 w-3.5 transition-transform duration-200 ease-lux group-hover:-translate-x-1" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Reveal>
|
||||
|
||||
<div className="mt-12 grid gap-6 lg:grid-cols-3">
|
||||
{rest.map((article, i) => (
|
||||
<Reveal key={article.slug} delay={0.05 * i} className="h-full">
|
||||
<ArticleCard article={article} />
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Newsletter />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import { PageHero } from '../components/PageHero'
|
||||
import { ContactSection } from '../components/ContactSection'
|
||||
import { Reveal } from '../components/Reveal'
|
||||
import { IMAGES } from '../data/site'
|
||||
|
||||
const OFFICES = [
|
||||
{ city: 'تهران', address: 'خیابان ولیعصر، برج آرامش، طبقه هجدهم', hours: 'شنبه تا چهارشنبه، ۹ تا ۱۸' },
|
||||
{ city: 'اصفهان', address: 'خیابان چهارباغ بالا، مجتمع پارسه، طبقه ششم', hours: 'شنبه تا چهارشنبه، ۹ تا ۱۷' },
|
||||
{ city: 'نوشهر', address: 'بلوار دریا، دفتر پروژه دریاکنار', hours: 'شنبه تا پنجشنبه، ۱۰ تا ۱۶' },
|
||||
]
|
||||
|
||||
export function ContactPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="تماس با ما"
|
||||
lead="برای مشاوره ساخت، سرمایهگذاری یا بازدید از پروژهها، تیم ما در سه شهر آماده پاسخگویی است."
|
||||
image={IMAGES[1]}
|
||||
/>
|
||||
|
||||
<section className="bg-ink py-24" aria-label="دفاتر ما">
|
||||
<div className="mx-auto grid max-w-site gap-6 px-6 md:grid-cols-3 lg:px-10">
|
||||
{OFFICES.map((office, i) => (
|
||||
<Reveal key={office.city} delay={0.06 * i} className="h-full">
|
||||
{/* اعمال فرمول برای نرم شدن بالا آمدن کارت، بدون هیچ تغییری روی متنها */}
|
||||
<article className="flex h-full flex-col border border-ivory/10 bg-char px-8 py-12 text-center transition-all duration-300 ease-out hover:-translate-y-2 hover:border-copper/40 hover:shadow-lift transform-gpu will-change-transform">
|
||||
<h2 className="text-2xl font-bold text-copper">{office.city}</h2>
|
||||
<p className="mt-6 text-sm font-light leading-9 text-ivory/70">{office.address}</p>
|
||||
<div className="mt-auto pt-10">
|
||||
<p className="border-t border-ivory/10 pt-6 text-xs font-light text-ivory/50">
|
||||
{office.hours}
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import { PageHero } from '../components/PageHero'
|
||||
import { Faq } from '../components/home/Faq'
|
||||
import { ContactSection } from '../components/ContactSection'
|
||||
import { IMAGES } from '../data/site'
|
||||
|
||||
export function FaqPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="سوالات متداول"
|
||||
lead="پیش از تماس، شاید پاسخ پرسش شما همینجا باشد. اگر نبود، خوشحال میشویم مستقیم صحبت کنیم."
|
||||
image={IMAGES[1]}
|
||||
/>
|
||||
|
||||
<Faq hideHeading={true} />
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Hero } from '../components/home/Hero';
|
||||
import { Story } from '../components/home/Story';
|
||||
import { Stats } from '../components/home/Stats';
|
||||
import { Services } from '../components/home/Services';
|
||||
import { BeforeAfter } from '../components/home/BeforeAfter';
|
||||
import { FeaturedProjects } from '../components/home/FeaturedProjects';
|
||||
import { Gallery } from '../components/home/Gallery';
|
||||
import { Timeline } from '../components/home/Timeline';
|
||||
import { WhyUs } from '../components/home/WhyUs';
|
||||
import { Testimonials } from '../components/home/Testimonials';
|
||||
import { Team } from '../components/home/Team';
|
||||
import { Articles } from '../components/home/Articles';
|
||||
import { Faq } from '../components/home/Faq';
|
||||
import { Newsletter } from '../components/home/Newsletter';
|
||||
import { ContactSection } from '../components/ContactSection';
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<Story />
|
||||
<Stats />
|
||||
<Services />
|
||||
<BeforeAfter />
|
||||
<FeaturedProjects />
|
||||
<Gallery />
|
||||
<Timeline />
|
||||
<WhyUs />
|
||||
<Testimonials />
|
||||
<Team />
|
||||
<Articles />
|
||||
<Faq />
|
||||
<Newsletter />
|
||||
<ContactSection />
|
||||
</>);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import React from 'react'
|
||||
import { PageHero } from '../components/PageHero'
|
||||
import { Reveal } from '../components/Reveal'
|
||||
import { SectionHeading } from '../components/SectionHeading'
|
||||
import { Stats } from '../components/home/Stats'
|
||||
import { ContactSection } from '../components/ContactSection'
|
||||
import { IMAGES } from '../data/site'
|
||||
|
||||
const MODELS = [
|
||||
{
|
||||
title: 'مشارکت در ساخت',
|
||||
desc: 'مالک زمین و آرامش پلازا در سود پروژه شریک میشوند؛ اجرا، مجوزها و فروش بر عهده ماست.',
|
||||
points: ['ارزیابی رایگان زمین', 'قرارداد شفاف با ضمانت اجرا', 'گزارش ماهانه پیشرفت'],
|
||||
},
|
||||
{
|
||||
title: 'صندوق پروژهمحور',
|
||||
desc: 'سرمایهگذاری روی یک پروژه مشخص با واحدهای قابل خرید و بازدهی محاسبهشده در پایان دوره.',
|
||||
points: ['حداقل دوره ۲۴ ماه', 'گزارش ارزشگذاری فصلی', 'امکان انتقال واحد سرمایه'],
|
||||
},
|
||||
{
|
||||
title: 'پیشخرید واحد',
|
||||
desc: 'خرید واحد مسکونی یا تجاری در مراحل ابتدایی ساخت با قیمت پیش از تکمیل و پرداخت مرحلهای.',
|
||||
points: ['پرداخت مرحلهای متناسب با پیشرفت', 'امکان شخصیسازی پلان', 'تضمین زمان تحویل'],
|
||||
},
|
||||
]
|
||||
|
||||
export function Investment() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="سرمایهگذاری"
|
||||
lead="سه مسیر روشن برای ورود به بازار ساختوساز لوکس، با ساختار مالی شفاف و گزارشدهی منظم."
|
||||
image={IMAGES[4]}
|
||||
/>
|
||||
|
||||
<section className="bg-ink py-24 lg:py-32" aria-labelledby="models-title">
|
||||
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||
<SectionHeading
|
||||
title="مدلهای همکاری"
|
||||
lead="بسته به میزان سرمایه، افق زمانی و سطح ریسکی که میپذیرید، یکی از این سه مسیر مناسب شماست."
|
||||
/>
|
||||
<div className="mt-14 grid gap-6 lg:grid-cols-3">
|
||||
{MODELS.map((model, i) => (
|
||||
<Reveal key={model.title} delay={0.06 * i} className="h-full">
|
||||
<article className="group flex h-full flex-col border border-ivory/10 bg-char p-10 transition-all duration-300 ease-out hover:-translate-y-2 hover:border-copper/40 hover:shadow-lift transform-gpu will-change-transform">
|
||||
<p className="text-sm font-bold text-copper/80">۰{(i + 1).toLocaleString('fa-IR')}</p>
|
||||
|
||||
<h3 className="mt-6 text-2xl font-bold text-ivory transition-colors duration-300 ease-out group-hover:text-copper">
|
||||
{model.title}
|
||||
</h3>
|
||||
|
||||
<p className="mt-5 text-sm font-light leading-9 text-ivory/65">{model.desc}</p>
|
||||
|
||||
<div className="mt-auto pt-10">
|
||||
<ul className="space-y-4 border-t border-ivory/10 pt-8">
|
||||
{model.points.map((point) => (
|
||||
<li key={point} className="flex items-start gap-3 text-sm font-light leading-8 text-ivory/65">
|
||||
<span className="mt-3.5 h-1 w-1 shrink-0 rounded-full bg-copper transition-transform duration-300 ease-out group-hover:scale-[1.5]" aria-hidden="true" />
|
||||
{point}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Stats />
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { ArrowLeftIcon, ChevronLeftIcon } from 'lucide-react'
|
||||
import { Reveal } from '../components/Reveal'
|
||||
import { ButtonLink } from '../components/Button'
|
||||
import { ProjectCard } from '../components/ProjectCard'
|
||||
import { ContactSection } from '../components/ContactSection'
|
||||
import { Lightbox } from '../components/Lightbox'
|
||||
import { STATUS_LABEL } from '../data/site'
|
||||
import { useSite } from '../api'
|
||||
|
||||
export function ProjectDetail() {
|
||||
const { slug } = useParams<{ slug: string }>()
|
||||
const { projects } = useSite()
|
||||
const project = projects.find((p) => p.slug === slug)
|
||||
const [lightbox, setLightbox] = useState<number | null>(null)
|
||||
|
||||
if (!project) {
|
||||
return (
|
||||
<section className="flex min-h-[70vh] flex-col items-center justify-center px-6 text-center">
|
||||
<h1 className="text-section font-bold text-ivory">این پروژه پیدا نشد</h1>
|
||||
<p className="mt-4 text-sm font-light text-ivory/60">
|
||||
ممکن است نشانی تغییر کرده باشد. فهرست کامل پروژهها را ببینید.
|
||||
</p>
|
||||
<ButtonLink to="/projects" variant="copper" className="mt-8">
|
||||
بازگشت به پروژهها
|
||||
<ArrowLeftIcon className="h-4 w-4" />
|
||||
</ButtonLink>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const related = projects.filter((p) => p.slug !== project.slug).slice(0, 3)
|
||||
|
||||
const facts = [
|
||||
{ label: 'موقعیت', value: project.location },
|
||||
{ label: 'سال', value: project.year },
|
||||
{ label: 'زیربنا', value: project.area },
|
||||
{ label: 'تعداد طبقات', value: project.floors },
|
||||
{ label: 'واحدها', value: project.units },
|
||||
{ label: 'کارفرما', value: project.client },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="relative flex h-[62vh] min-h-[440px] items-end overflow-hidden">
|
||||
<img src={project.image} alt="" aria-hidden="true" className="absolute inset-0 h-full w-full object-cover" />
|
||||
<div className="absolute inset-0 bg-ink/72" aria-hidden="true" />
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ background: 'linear-gradient(to top, #27221c 6%, rgba(39,34,28,0.15) 70%)' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="relative mx-auto w-full max-w-site px-6 pb-16 lg:px-10">
|
||||
<nav aria-label="مسیر صفحه" className="flex items-center gap-2 text-xs font-light text-ivory/50">
|
||||
<Link to="/" className="transition-colors hover:text-copper">
|
||||
صفحه اصلی
|
||||
</Link>
|
||||
<ChevronLeftIcon className="h-3.5 w-3.5" />
|
||||
<Link to="/projects" className="transition-colors hover:text-copper">
|
||||
پروژهها
|
||||
</Link>
|
||||
<ChevronLeftIcon className="h-3.5 w-3.5" />
|
||||
<span className="text-copper">{project.title}</span>
|
||||
</nav>
|
||||
<h1 className="mt-5 max-w-[700px] text-hero font-bold text-ivory">{project.title}</h1>
|
||||
<p className="mt-5 max-w-2xl text-base font-light leading-9 text-ivory/70">{project.summary}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-char py-20 lg:py-28" aria-label="جزئیات پروژه">
|
||||
<div className="mx-auto grid max-w-site items-start gap-12 px-6 lg:grid-cols-2 lg:gap-16 lg:px-10">
|
||||
|
||||
<Reveal className="order-1">
|
||||
<span className="flex items-center gap-3 text-sm font-light text-copper">
|
||||
<span aria-hidden="true" className="h-px w-8 bg-copper" />
|
||||
{STATUS_LABEL[project.status]}
|
||||
</span>
|
||||
<h2 className="mt-4 text-3xl font-bold text-ivory">درباره پروژه</h2>
|
||||
|
||||
{project.body.map((para) => (
|
||||
<p key={para} className="mt-5 text-base font-light leading-9 text-ivory/70">
|
||||
{para}
|
||||
</p>
|
||||
))}
|
||||
|
||||
<dl className="mt-10 grid grid-cols-2 gap-px border border-ivory/10 bg-ivory/10">
|
||||
{facts.map((f) => (
|
||||
<div key={f.label} className="bg-ink p-6 md:p-8 text-right">
|
||||
<dt className="text-xs font-light text-ivory/50">{f.label}</dt>
|
||||
<dd className="mt-3 text-sm font-bold text-ivory">{f.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
|
||||
<div className="mt-10">
|
||||
<ButtonLink to="/contact" variant="outline" className="font-light">
|
||||
پروژهای مشابه دارید؟
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={0.1} className="order-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setLightbox(0)}
|
||||
className="group block w-full overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-copper"
|
||||
>
|
||||
<img
|
||||
src={project.gallery[0] || project.image}
|
||||
alt=""
|
||||
className="aspect-[4/3] w-full object-cover transition-transform duration-500 ease-lux group-hover:scale-105"
|
||||
/>
|
||||
</button>
|
||||
<div className="mt-4 grid grid-cols-3 gap-4">
|
||||
{project.gallery.slice(1).map((img, i) => (
|
||||
<button
|
||||
key={img}
|
||||
type="button"
|
||||
onClick={() => setLightbox(i + 1)}
|
||||
className="group block overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-copper"
|
||||
>
|
||||
<img
|
||||
src={img}
|
||||
alt=""
|
||||
className="aspect-square w-full object-cover transition-transform duration-500 ease-lux group-hover:scale-105"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-ink py-20 lg:py-28" aria-label="پروژههای مرتبط">
|
||||
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||
<h2 className="text-section font-bold text-ivory">پروژههای دیگر</h2>
|
||||
<div className="mt-6 h-px w-24 bg-copper/70" />
|
||||
<div className="mt-12 grid gap-6 lg:grid-cols-3">
|
||||
{related.map((item, i) => (
|
||||
<Reveal key={item.slug} delay={0.06 * i} className="h-full">
|
||||
<ProjectCard project={item} />
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Lightbox
|
||||
images={project.gallery}
|
||||
index={lightbox}
|
||||
onClose={() => setLightbox(null)}
|
||||
onChange={setLightbox}
|
||||
/>
|
||||
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import React, { useState } from 'react';
|
||||
import { PageHero } from '../components/PageHero';
|
||||
import { Reveal } from '../components/Reveal';
|
||||
import { ProjectCard } from '../components/ProjectCard';
|
||||
import { Gallery } from '../components/home/Gallery';
|
||||
import { ContactSection } from '../components/ContactSection';
|
||||
import { IMAGES, type ProjectStatus } from '../data/site';
|
||||
import { useSite } from '../api';
|
||||
|
||||
const FILTERS: {key: ProjectStatus;label: string;}[] = [
|
||||
{ key: 'done', label: 'انجام شده' },
|
||||
{ key: 'progress', label: 'در حال انجام' }];
|
||||
|
||||
|
||||
export function Projects() {
|
||||
const { projects } = useSite();
|
||||
const [status, setStatus] = useState<ProjectStatus>('done');
|
||||
const list = projects.filter((p) => p.status === status);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="پروژهها"
|
||||
lead="مجموعهای از برجهای مسکونی، مراکز تجاری و ویلاهای اختصاصی؛ از پروژههای تحویلشده تا کارگاههای فعال امروز."
|
||||
image={IMAGES[0]} />
|
||||
|
||||
|
||||
<section className="bg-ink py-24 lg:py-32" aria-label="فهرست پروژهها">
|
||||
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||
<div className="flex flex-wrap gap-3" role="tablist" aria-label="فیلتر وضعیت پروژه">
|
||||
{FILTERS.map((filter) =>
|
||||
<button
|
||||
key={filter.key}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={status === filter.key}
|
||||
onClick={() => setStatus(filter.key)}
|
||||
className={`group relative overflow-hidden border px-7 py-3 text-sm transition-colors duration-300 ease-lux ${
|
||||
status === filter.key ?
|
||||
'border-copper bg-copper font-bold text-char' :
|
||||
'border-ivory/15 font-light text-ivory/65'}`
|
||||
}>
|
||||
|
||||
{status !== filter.key &&
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 translate-y-full bg-copper transition-transform duration-300 ease-lux group-hover:translate-y-0" />
|
||||
|
||||
}
|
||||
<span className="relative z-10 transition-colors duration-300 ease-lux group-hover:text-char">
|
||||
{filter.label}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid gap-6 lg:grid-cols-3">
|
||||
{list.map((project, i) =>
|
||||
<Reveal key={project.slug} delay={0.05 * i} className="h-full">
|
||||
<ProjectCard project={project} imageClass="h-80" />
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Gallery />
|
||||
<ContactSection />
|
||||
</>);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import { PageHero } from '../components/PageHero'
|
||||
import { Services } from '../components/home/Services'
|
||||
import { Faq } from '../components/home/Faq'
|
||||
import { ContactSection } from '../components/ContactSection'
|
||||
import { Reveal } from '../components/Reveal'
|
||||
import { SectionHeading } from '../components/SectionHeading'
|
||||
import { IMAGES } from '../data/site'
|
||||
|
||||
const PROCESS = [
|
||||
{ step: '۰۱', title: 'مطالعه و امکانسنجی', desc: 'بررسی زمین، ضوابط شهری و تحلیل بازدهی اقتصادی پروژه.' },
|
||||
{ step: '۰۲', title: 'طراحی و فاز اجرایی', desc: 'کانسپت معماری، طراحی سازه و تأسیسات و تهیه نقشههای اجرایی.' },
|
||||
{ step: '۰۳', title: 'اجرا و کنترل کیفیت', desc: 'ساخت با نظارت مستمر، گزارش ماهانه پیشرفت و کنترل هزینه.' },
|
||||
{ step: '۰۴', title: 'تحویل و پشتیبانی', desc: 'تحویل کلید، آموزش بهرهبرداری و پنج سال پشتیبانی فنی.' },
|
||||
]
|
||||
|
||||
export function ServicesPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
title="خدمات"
|
||||
lead="هشت حوزه تخصصی که چرخه کامل یک پروژه ساختمانی را از ایده تا بهرهبرداری پوشش میدهند."
|
||||
image={IMAGES[1]}
|
||||
/>
|
||||
|
||||
<Services />
|
||||
|
||||
<section className="bg-char py-24 lg:py-32" aria-labelledby="process-title">
|
||||
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||
<SectionHeading title="فرآیند کار ما" lead="چهار مرحله شفاف، از نخستین جلسه تا تحویل کلید." align="center" />
|
||||
<div className="mt-14 grid gap-px bg-ivory/10 md:grid-cols-2 lg:grid-cols-4">
|
||||
{PROCESS.map((item, i) => (
|
||||
<Reveal key={item.step} delay={0.06 * i} className="h-full">
|
||||
<div className="flex h-full flex-col justify-center bg-char px-6 py-14 text-center">
|
||||
<p className="text-4xl font-bold text-copper">{item.step}</p>
|
||||
<h3 className="mt-6 text-xl font-bold text-ivory">{item.title}</h3>
|
||||
<p className="mt-4 text-base font-light leading-9 text-ivory/60">{item.desc}</p>
|
||||
</div>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Faq />
|
||||
<ContactSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user