Initial commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Magic Patterns - Vite Template
|
||||||
|
|
||||||
|
This code was generated by [Magic Patterns](https://magicpatterns.com) for this design: [Source Design](https://www.magicpatterns.com/c/ka4i8ikygtakmdobhws6nu)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Run `npm install`
|
||||||
|
2. Run `npm run dev`
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>ArameshPlaza Luxury Website</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+4515
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "magic-patterns-vite-template",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npx vite",
|
||||||
|
"build": "npx vite build",
|
||||||
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||||
|
"preview": "npx vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
|
"react-router-dom": "^6.26.2",
|
||||||
|
"framer-motion": "^11.5.4",
|
||||||
|
"lucide-react": "0.522.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.18",
|
||||||
|
"@types/react": "^18.3.1",
|
||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||||
|
"@typescript-eslint/parser": "^5.54.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"eslint": "^8.50.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.1",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vite": "^5.2.0",
|
||||||
|
"tailwindcss": "3.4.17",
|
||||||
|
"autoprefixer": "latest",
|
||||||
|
"postcss": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { BrowserRouter, Route, Routes, useLocation } from 'react-router-dom';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { Preloader } from './components/Preloader';
|
||||||
|
import { Header } from './components/Header';
|
||||||
|
import { Footer } from './components/Footer';
|
||||||
|
import { FloatingContact } from './components/FloatingContact';
|
||||||
|
import { Home } from './pages/Home';
|
||||||
|
import { About } from './pages/About';
|
||||||
|
import { ServicesPage } from './pages/ServicesPage';
|
||||||
|
import { Projects } from './pages/Projects';
|
||||||
|
import { ProjectDetail } from './pages/ProjectDetail';
|
||||||
|
import { Investment } from './pages/Investment';
|
||||||
|
import { Blog } from './pages/Blog';
|
||||||
|
import { ArticleDetail } from './pages/ArticleDetail';
|
||||||
|
import { FaqPage } from './pages/FaqPage';
|
||||||
|
import { ContactPage } from './pages/ContactPage';
|
||||||
|
|
||||||
|
function ScrollToTop() {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'auto' });
|
||||||
|
}, [pathname]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AnimatedRoutes() {
|
||||||
|
const location = useLocation();
|
||||||
|
return (
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.main
|
||||||
|
key={location.pathname}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.28, ease: [0.23, 1, 0.32, 1] }}>
|
||||||
|
|
||||||
|
<Routes location={location}>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/about" element={<About />} />
|
||||||
|
<Route path="/services" element={<ServicesPage />} />
|
||||||
|
<Route path="/projects" element={<Projects />} />
|
||||||
|
<Route path="/projects/:slug" element={<ProjectDetail />} />
|
||||||
|
<Route path="/investment" element={<Investment />} />
|
||||||
|
<Route path="/blog" element={<Blog />} />
|
||||||
|
<Route path="/blog/:slug" element={<ArticleDetail />} />
|
||||||
|
<Route path="/faq" element={<FaqPage />} />
|
||||||
|
<Route path="/contact" element={<ContactPage />} />
|
||||||
|
<Route path="*" element={<Home />} />
|
||||||
|
</Routes>
|
||||||
|
</motion.main>
|
||||||
|
</AnimatePresence>);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.setAttribute('dir', 'rtl');
|
||||||
|
document.documentElement.setAttribute('lang', 'fa');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div dir="rtl" lang="fa" className="min-h-screen w-full overflow-x-hidden bg-ink font-pinar text-ivory">
|
||||||
|
<BrowserRouter>
|
||||||
|
<Preloader />
|
||||||
|
<ScrollToTop />
|
||||||
|
<Header />
|
||||||
|
<AnimatedRoutes />
|
||||||
|
<Footer />
|
||||||
|
<FloatingContact />
|
||||||
|
</BrowserRouter>
|
||||||
|
</div>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ArrowLeftIcon, ClockIcon } from 'lucide-react';
|
||||||
|
import type { Article } from '../data/site';
|
||||||
|
|
||||||
|
export function ArticleCard({ article }: {article: Article;}) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/blog/${article.slug}`}
|
||||||
|
className="group flex h-full flex-col border border-ivory/10 bg-char transition-[transform,box-shadow,border-color] duration-500 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-lift focus:outline-none focus-visible:ring-2 focus-visible:ring-copper"
|
||||||
|
aria-label={`خواندن مقاله ${article.title}`}>
|
||||||
|
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={article.image}
|
||||||
|
alt={`تصویر مقاله ${article.title}`}
|
||||||
|
className="h-52 w-full object-cover transition-transform duration-500 ease-lux group-hover:scale-105" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 flex-col p-7">
|
||||||
|
<p className="text-xs font-light text-copper">{article.category}</p>
|
||||||
|
<h3 className="mt-3 text-lg font-bold leading-8 text-ivory transition-colors duration-200 ease-lux group-hover:text-copper">
|
||||||
|
{article.title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-3 text-sm font-light leading-8 text-ivory/55">{article.excerpt}</p>
|
||||||
|
<div className="mt-auto flex items-center justify-between border-t border-ivory/10 pt-5 text-xs font-light text-ivory/50">
|
||||||
|
<span className="flex items-center gap-4">
|
||||||
|
<span>{article.date}</span>
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<ClockIcon className="h-3.5 w-3.5" />
|
||||||
|
{article.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>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
// نوع dark-ivory به لیست اضافه شد
|
||||||
|
type Variant = 'copper' | 'outline' | 'dark' | 'dark-ivory'
|
||||||
|
|
||||||
|
type BaseProps = {
|
||||||
|
children: React.ReactNode
|
||||||
|
variant?: Variant
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const STYLES: Record<Variant, { base: string; fill: string; text: string }> = {
|
||||||
|
copper: {
|
||||||
|
base: 'bg-copper text-char',
|
||||||
|
fill: 'bg-char',
|
||||||
|
text: 'group-hover:text-ivory',
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
base: 'border border-ivory/30 text-ivory',
|
||||||
|
fill: 'bg-copper',
|
||||||
|
text: 'group-hover:text-char',
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
base: 'bg-char text-ivory',
|
||||||
|
fill: 'bg-copper',
|
||||||
|
text: 'group-hover:text-char',
|
||||||
|
},
|
||||||
|
// استایل جدید مخصوص سکشنهای مسیرنگ
|
||||||
|
'dark-ivory': {
|
||||||
|
base: 'bg-char text-ivory',
|
||||||
|
fill: 'bg-ivory',
|
||||||
|
text: 'group-hover:text-char',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function Inner({ children, variant = 'copper' }: { children: React.ReactNode; variant?: Variant }) {
|
||||||
|
const style = STYLES[variant]
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={`absolute inset-0 translate-y-full transition-transform duration-[600ms] ease-lux group-hover:translate-y-0 ${style.fill}`}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`relative z-10 flex items-center justify-center gap-3 transition-colors duration-[600ms] ease-lux ${style.text}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHELL =
|
||||||
|
'group relative inline-flex items-center justify-center overflow-hidden px-8 py-4 text-sm font-bold focus:outline-none focus-visible:ring-2 focus-visible:ring-copper focus-visible:ring-offset-2 focus-visible:ring-offset-ink'
|
||||||
|
|
||||||
|
export function ButtonLink({
|
||||||
|
to,
|
||||||
|
children,
|
||||||
|
variant = 'copper',
|
||||||
|
className = '',
|
||||||
|
}: BaseProps & { to: string }) {
|
||||||
|
return (
|
||||||
|
<Link to={to} className={`${SHELL} ${STYLES[variant].base} ${className}`}>
|
||||||
|
<Inner variant={variant}>{children}</Inner>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonAnchor({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
variant = 'copper',
|
||||||
|
className = '',
|
||||||
|
...rest
|
||||||
|
}: BaseProps & { href: string } & React.AnchorHTMLAttributes<HTMLAnchorElement>) {
|
||||||
|
return (
|
||||||
|
<a href={href} className={`${SHELL} ${STYLES[variant].base} ${className}`} {...rest}>
|
||||||
|
<Inner variant={variant}>{children}</Inner>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
children,
|
||||||
|
variant = 'copper',
|
||||||
|
className = '',
|
||||||
|
...rest
|
||||||
|
}: BaseProps & React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
||||||
|
return (
|
||||||
|
<button className={`${SHELL} ${STYLES[variant].base} ${className}`} {...rest}>
|
||||||
|
<Inner variant={variant}>{children}</Inner>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { CheckCircle2Icon, MailIcon, MapPinIcon, PhoneIcon } from 'lucide-react'
|
||||||
|
import { Reveal } from './Reveal'
|
||||||
|
import { Button } from './Button'
|
||||||
|
import { BRAND } from '../data/site'
|
||||||
|
|
||||||
|
export function ContactSection() {
|
||||||
|
const [form, setForm] = useState({ name: '', phone: '', note: '' })
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
const [sent, setSent] = useState(false)
|
||||||
|
|
||||||
|
const submit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (form.phone.trim().length < 8) {
|
||||||
|
setError('لطفاً شماره تلفن معتبر وارد کنید.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setError('')
|
||||||
|
setSent(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="contact" className="relative overflow-hidden py-24 lg:py-32" aria-labelledby="contact-title">
|
||||||
|
<img
|
||||||
|
src={BRAND.contactBg}
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<div className="mx-auto grid max-w-6xl items-stretch gap-6 lg:grid-cols-12">
|
||||||
|
|
||||||
|
{/* باکس اول: اطلاعات تماس (مقدار blur تنظیم شد) */}
|
||||||
|
<div className="border border-ivory/20 bg-char/45 p-8 backdrop-blur-[8px] md:p-10 lg:col-span-5">
|
||||||
|
<Reveal>
|
||||||
|
<p className="text-sm font-light text-copper">تماس با ما</p>
|
||||||
|
<h2 id="contact-title" className="mt-5 text-section font-bold text-ivory">
|
||||||
|
پروژهتان را با ما در میان بگذارید
|
||||||
|
</h2>
|
||||||
|
<p className="mt-6 text-base font-light leading-9 text-ivory/75">
|
||||||
|
شمارهتان را بگذارید؛ کارشناس ما در کمتر از یک روز کاری برای یک جلسه مشاوره رایگان تماس میگیرد.
|
||||||
|
</p>
|
||||||
|
</Reveal>
|
||||||
|
|
||||||
|
<Reveal delay={0.1}>
|
||||||
|
<ul className="mt-10 space-y-6">
|
||||||
|
<li className="flex items-start gap-4">
|
||||||
|
<MapPinIcon className="mt-1 h-5 w-5 shrink-0 text-copper" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold text-ivory">دفتر مرکزی</p>
|
||||||
|
<p className="mt-1 text-sm font-light leading-7 text-ivory/70">{BRAND.address}</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-4">
|
||||||
|
<PhoneIcon className="mt-1 h-5 w-5 shrink-0 text-copper" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold text-ivory">تلفن</p>
|
||||||
|
<a
|
||||||
|
href={BRAND.phoneHref}
|
||||||
|
dir="ltr"
|
||||||
|
className="mt-1 block text-sm font-light text-ivory/70 transition-colors hover:text-copper"
|
||||||
|
>
|
||||||
|
{BRAND.phone}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-4">
|
||||||
|
<MailIcon className="mt-1 h-5 w-5 shrink-0 text-copper" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold text-ivory">ایمیل</p>
|
||||||
|
<a
|
||||||
|
href={`mailto:${BRAND.email}`}
|
||||||
|
dir="ltr"
|
||||||
|
className="mt-1 block text-sm font-light text-ivory/70 transition-colors hover:text-copper"
|
||||||
|
>
|
||||||
|
{BRAND.email}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* باکس دوم: فرم تماس (مقدار blur تنظیم شد) */}
|
||||||
|
<div className="border border-ivory/20 bg-char/45 p-8 backdrop-blur-[8px] md:p-10 lg:col-span-7">
|
||||||
|
{sent ? (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center py-14 text-center">
|
||||||
|
<CheckCircle2Icon className="h-12 w-12 text-copper" />
|
||||||
|
<h3 className="mt-6 text-xl font-bold text-ivory">درخواست شما ثبت شد</h3>
|
||||||
|
<p className="mt-3 max-w-sm text-sm font-light leading-8 text-ivory/70">
|
||||||
|
از اعتماد شما سپاسگزاریم. کارشناسان ما بهزودی با شما تماس میگیرند.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
className="mt-8 font-light"
|
||||||
|
onClick={() => {
|
||||||
|
setSent(false)
|
||||||
|
setForm({ name: '', phone: '', note: '' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ثبت درخواست جدید
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={submit} noValidate className="grid gap-6 md:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="contact-name" className="block text-sm font-bold text-ivory/90">
|
||||||
|
نام
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="contact-name"
|
||||||
|
type="text"
|
||||||
|
value={form.name}
|
||||||
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||||
|
placeholder="نام و نام خانوادگی"
|
||||||
|
className="mt-3 w-full border border-ivory/20 bg-ink/45 px-5 py-4 text-sm font-light text-ivory placeholder:text-ivory/40 transition-colors duration-200 ease-lux focus:border-copper focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="contact-phone" className="block text-sm font-bold text-ivory/90">
|
||||||
|
شماره تلفن <span className="text-copper">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="contact-phone"
|
||||||
|
type="tel"
|
||||||
|
dir="ltr"
|
||||||
|
required
|
||||||
|
value={form.phone}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, phone: e.target.value })
|
||||||
|
if (error) setError('')
|
||||||
|
}}
|
||||||
|
placeholder="09xx xxx xxxx"
|
||||||
|
aria-invalid={Boolean(error)}
|
||||||
|
className="mt-3 w-full border border-ivory/20 bg-ink/45 px-5 py-4 text-right text-sm font-light text-ivory placeholder:text-ivory/40 transition-colors duration-200 ease-lux focus:border-copper focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<label htmlFor="contact-note" className="block text-sm font-bold text-ivory/90">
|
||||||
|
توضیحات <span className="font-light text-ivory/50">(اختیاری)</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="contact-note"
|
||||||
|
rows={6}
|
||||||
|
value={form.note}
|
||||||
|
onChange={(e) => setForm({ ...form, note: e.target.value })}
|
||||||
|
placeholder="درباره پروژه یا درخواست خود بنویسید…"
|
||||||
|
className="mt-3 w-full resize-none border border-ivory/20 bg-ink/45 px-5 py-4 text-sm font-light leading-8 text-ivory placeholder:text-ivory/40 transition-colors duration-200 ease-lux focus:border-copper focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm font-light text-copper md:col-span-2" role="alert">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<Button type="submit" variant="copper" className="w-full md:w-auto md:px-12">
|
||||||
|
ارسال درخواست مشاوره
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PhoneIcon } from 'lucide-react';
|
||||||
|
import { BRAND } from '../data/site';
|
||||||
|
|
||||||
|
export function FloatingContact() {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={BRAND.phoneHref}
|
||||||
|
aria-label="تماس تلفنی با آرامش پلازا"
|
||||||
|
className="group fixed bottom-8 left-8 z-40 flex h-14 w-14 items-center justify-center rounded-full bg-copper text-char shadow-[0_10px_30px_rgba(0,0,0,0.45)] transition-transform duration-200 ease-lux hover:scale-105">
|
||||||
|
|
||||||
|
<span className="absolute inset-0 rounded-full border border-copper/50 opacity-0 transition-transform duration-300 ease-lux group-hover:scale-125 group-hover:opacity-100" />
|
||||||
|
<PhoneIcon className="h-5 w-5" />
|
||||||
|
</a>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { InstagramIcon, LinkedinIcon, MailIcon, MapPinIcon, PhoneIcon, SendIcon } from 'lucide-react';
|
||||||
|
import { BRAND, NAV, SERVICES } from '../data/site';
|
||||||
|
|
||||||
|
export function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="border-t border-ivory/10 bg-char">
|
||||||
|
<div className="mx-auto max-w-site px-6 py-16 lg:px-10">
|
||||||
|
<div className="grid gap-12 lg:grid-cols-12">
|
||||||
|
<div className="lg:col-span-4">
|
||||||
|
<img src={BRAND.logo} alt={BRAND.name} className="h-20 w-auto object-contain" />
|
||||||
|
<p className="mt-6 max-w-sm text-sm font-light leading-8 text-ivory/55">
|
||||||
|
آرامش پلازا از سال ۱۳۸۳ ساختمانهایی میسازد که قرار است چند نسل در آنها زندگی کنند؛ معماری آرام،
|
||||||
|
اجرای دقیق و سرمایهگذاری شفاف.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 flex gap-3">
|
||||||
|
{[
|
||||||
|
{ Icon: InstagramIcon, label: 'اینستاگرام' },
|
||||||
|
{ Icon: LinkedinIcon, label: 'لینکدین' },
|
||||||
|
{ Icon: SendIcon, label: 'تلگرام' }].
|
||||||
|
map(({ Icon, label }) =>
|
||||||
|
<a
|
||||||
|
key={label}
|
||||||
|
href="#"
|
||||||
|
aria-label={label}
|
||||||
|
className="flex h-10 w-10 items-center justify-center border border-ivory/15 text-ivory/70 transition-colors duration-200 ease-lux hover:border-copper hover:text-copper">
|
||||||
|
|
||||||
|
<Icon className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<h3 className="text-sm font-bold text-copper">صفحات</h3>
|
||||||
|
<ul className="mt-5 space-y-3">
|
||||||
|
{NAV.map((item) =>
|
||||||
|
<li key={item.to}>
|
||||||
|
<Link
|
||||||
|
to={item.to}
|
||||||
|
className="text-sm font-light text-ivory/60 transition-colors duration-200 ease-lux hover:text-copper">
|
||||||
|
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-3">
|
||||||
|
<h3 className="text-sm font-bold text-copper">خدمات</h3>
|
||||||
|
<ul className="mt-5 space-y-3">
|
||||||
|
{SERVICES.map((service) =>
|
||||||
|
<li key={service.title} className="text-sm font-light text-ivory/60">
|
||||||
|
{service.title}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-3">
|
||||||
|
<h3 className="text-sm font-bold text-copper">ارتباط با ما</h3>
|
||||||
|
<ul className="mt-5 space-y-4 text-sm font-light text-ivory/60">
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<MapPinIcon className="mt-1 h-4 w-4 shrink-0 text-copper" />
|
||||||
|
<span className="leading-7">{BRAND.address}</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-3">
|
||||||
|
<PhoneIcon className="h-4 w-4 shrink-0 text-copper" />
|
||||||
|
<a href={BRAND.phoneHref} dir="ltr" className="transition-colors hover:text-copper">
|
||||||
|
{BRAND.phone}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-3">
|
||||||
|
<MailIcon className="h-4 w-4 shrink-0 text-copper" />
|
||||||
|
<a href={`mailto:${BRAND.email}`} dir="ltr" className="transition-colors hover:text-copper">
|
||||||
|
{BRAND.email}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-14 flex flex-col items-center justify-between gap-4 border-t border-ivory/10 pt-6 text-xs font-light text-ivory/40 md:flex-row">
|
||||||
|
<p>© ۱۴۰۳ آرامش پلازا — تمامی حقوق محفوظ است.</p>
|
||||||
|
<p>طراحی و ساخت با دقتِ معماری</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { Link, NavLink, useLocation } from 'react-router-dom'
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
|
import { MenuIcon, PhoneIcon, XIcon } from 'lucide-react'
|
||||||
|
import { BRAND, NAV } from '../data/site'
|
||||||
|
|
||||||
|
export function Header() {
|
||||||
|
const [scrolled, setScrolled] = useState(false)
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const { pathname } = useLocation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setScrolled(window.scrollY > 24)
|
||||||
|
onScroll()
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true })
|
||||||
|
return () => window.removeEventListener('scroll', onScroll)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpen(false)
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
|
const isSolid = scrolled || open
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`fixed inset-x-0 top-0 z-50 transition-[background-color,border-color,backdrop-filter] duration-500 ease-lux ${
|
||||||
|
isSolid ? 'border-b border-ivory/10 bg-ink/95 backdrop-blur-md' : 'border-b border-transparent bg-transparent'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="mx-auto flex max-w-site items-center justify-between gap-8 px-6 py-4 lg:px-10">
|
||||||
|
<Link to="/" className="flex items-center gap-3" aria-label={BRAND.name}>
|
||||||
|
<img
|
||||||
|
src={BRAND.logo}
|
||||||
|
alt={BRAND.name}
|
||||||
|
className={`w-auto object-contain transition-[height] duration-300 ease-lux ${
|
||||||
|
scrolled ? 'h-14' : 'h-20'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<nav aria-label="ناوبری اصلی" className="hidden xl:block">
|
||||||
|
<ul className="flex items-center gap-7">
|
||||||
|
{NAV.map((item) => (
|
||||||
|
<li key={item.to}>
|
||||||
|
<NavLink
|
||||||
|
to={item.to}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`group relative py-2 text-[0.95rem] font-light transition-colors duration-300 ease-lux hover:text-copper ${
|
||||||
|
isActive ? 'text-copper' : 'text-ivory/80'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<>
|
||||||
|
{item.label}
|
||||||
|
{/* خط زیرین که از وسط باز میشود */}
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={`absolute -bottom-0.5 right-0 left-0 h-px bg-copper transition-transform duration-500 ease-lux origin-center ${
|
||||||
|
isActive ? 'scale-x-100' : 'scale-x-0 group-hover:scale-x-100'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href={BRAND.phoneHref}
|
||||||
|
className="group relative hidden items-center gap-2 overflow-hidden border border-copper/50 px-5 py-2.5 text-sm font-light text-copper lg:flex"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 translate-y-full bg-copper transition-transform duration-[600ms] ease-lux group-hover:translate-y-0"
|
||||||
|
/>
|
||||||
|
<span className="relative z-10 flex items-center gap-2 transition-colors duration-[600ms] ease-lux group-hover:text-char">
|
||||||
|
<PhoneIcon className="h-4 w-4" />
|
||||||
|
<span dir="ltr">{BRAND.phone}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen((v) => !v)}
|
||||||
|
className="flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper xl:hidden"
|
||||||
|
aria-label={open ? 'بستن منو' : 'باز کردن منو'}
|
||||||
|
aria-expanded={open}
|
||||||
|
>
|
||||||
|
{open ? <XIcon className="h-5 w-5" /> : <MenuIcon className="h-5 w-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{open && (
|
||||||
|
<motion.nav
|
||||||
|
aria-label="ناوبری موبایل"
|
||||||
|
className="overflow-hidden border-t border-ivory/10 bg-ink/95 xl:hidden"
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.5, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
>
|
||||||
|
<ul className="grid gap-1 px-6 py-4">
|
||||||
|
{NAV.map((item, i) => (
|
||||||
|
<motion.li
|
||||||
|
key={item.to}
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.4, delay: i * 0.05, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
>
|
||||||
|
<NavLink
|
||||||
|
to={item.to}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`block border-b border-ivory/5 py-3 text-base font-light transition-colors duration-200 ease-lux ${
|
||||||
|
isActive ? 'text-copper' : 'text-ivory/80 hover:text-copper'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</NavLink>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</motion.nav>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { useCallback, useEffect } from 'react'
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
|
import { XIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
images: string[]
|
||||||
|
index: number | null
|
||||||
|
onClose: () => void
|
||||||
|
onChange: (index: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Lightbox({ images, index, onClose, onChange }: Props) {
|
||||||
|
const isOpen = index !== null
|
||||||
|
|
||||||
|
const next = useCallback(() => {
|
||||||
|
if (index === null) return
|
||||||
|
onChange((index + 1) % images.length)
|
||||||
|
}, [index, images.length, onChange])
|
||||||
|
|
||||||
|
const prev = useCallback(() => {
|
||||||
|
if (index === null) return
|
||||||
|
onChange((index - 1 + images.length) % images.length)
|
||||||
|
}, [index, images.length, onChange])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose()
|
||||||
|
if (e.key === 'ArrowRight') prev()
|
||||||
|
if (e.key === 'ArrowLeft') next()
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
document.body.style.overflow = 'hidden'
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', onKey)
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
}, [isOpen, onClose, next, prev])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<motion.div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
className="fixed inset-0 z-[90] flex items-center justify-center p-5 md:p-10"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-ink/90 backdrop-blur-md"
|
||||||
|
onClick={onClose}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="absolute left-6 top-6 z-10 flex h-12 w-12 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper md:left-10 md:top-10"
|
||||||
|
>
|
||||||
|
<XIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<motion.img
|
||||||
|
key={index}
|
||||||
|
src={images[index as number]}
|
||||||
|
alt=""
|
||||||
|
className="relative max-h-[80vh] w-auto max-w-full object-contain"
|
||||||
|
initial={{ opacity: 0, scale: 0.96 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.96 }}
|
||||||
|
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="absolute bottom-6 z-10 flex items-center gap-4 md:bottom-10">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={prev}
|
||||||
|
className="flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<span className="text-sm font-light text-ivory/70" dir="ltr">
|
||||||
|
{(index as number) + 1} / {images.length}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={next}
|
||||||
|
className="flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper"
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { ChevronLeftIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
type PageHeroProps = {
|
||||||
|
title: string;
|
||||||
|
lead: string;
|
||||||
|
image: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PageHero({ title, lead, image }: PageHeroProps) {
|
||||||
|
return (
|
||||||
|
<section className="relative flex h-[58vh] min-h-[420px] items-end overflow-hidden">
|
||||||
|
<img src={image} alt="" aria-hidden="true" className="absolute inset-0 h-full w-full object-cover" />
|
||||||
|
<div className="absolute inset-0 bg-ink/80" aria-hidden="true" />
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{ background: 'linear-gradient(to top, #27221c 6%, rgba(39,34,28,0.2) 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" />
|
||||||
|
<span className="text-copper">{title}</span>
|
||||||
|
</nav>
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 24 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="mt-5 max-w-[700px] text-hero font-bold text-ivory">
|
||||||
|
|
||||||
|
{title}
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, delay: 0.1, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="mt-5 max-w-2xl text-base font-light leading-9 text-ivory/65">
|
||||||
|
|
||||||
|
{lead}
|
||||||
|
</motion.p>
|
||||||
|
<div className="mt-8 h-px w-28 bg-copper" />
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
|
import { BRAND } from '../data/site'
|
||||||
|
|
||||||
|
export function Preloader() {
|
||||||
|
const [done, setDone] = useState(false)
|
||||||
|
const [progress, setProgress] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const start = Date.now()
|
||||||
|
const total = 1800
|
||||||
|
const id = window.setInterval(() => {
|
||||||
|
const p = Math.min(100, ((Date.now() - start) / total) * 100)
|
||||||
|
setProgress(p)
|
||||||
|
if (p >= 100) {
|
||||||
|
window.clearInterval(id)
|
||||||
|
window.setTimeout(() => setDone(true), 180)
|
||||||
|
}
|
||||||
|
}, 25)
|
||||||
|
return () => window.clearInterval(id)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.style.overflow = done ? '' : 'hidden'
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = ''
|
||||||
|
}
|
||||||
|
}, [done])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{!done && (
|
||||||
|
<motion.div
|
||||||
|
className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-ink"
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.5, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
aria-live="polite"
|
||||||
|
aria-label="در حال بارگذاری"
|
||||||
|
>
|
||||||
|
<div className="relative h-36 w-36">
|
||||||
|
<img
|
||||||
|
src={BRAND.logo}
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-0 h-full w-full object-contain opacity-[0.12]"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 overflow-hidden" style={{ clipPath: `inset(${100 - progress}% 0 0 0)` }}>
|
||||||
|
<img src={BRAND.logo} alt="" aria-hidden="true" className="h-full w-full object-contain" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute inset-x-0 h-px bg-copper"
|
||||||
|
style={{ bottom: `${progress}%` }}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-10 h-[2px] w-48 overflow-hidden bg-ivory/10">
|
||||||
|
<div className="h-full bg-copper" style={{ width: `${progress}%`, transition: 'width 120ms linear' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* نام برند با فونت پینار و استایل لوکس */}
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 6 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.2 }}
|
||||||
|
className="mt-4 text-sm font-light tracking-widest text-copper font-pinar"
|
||||||
|
>
|
||||||
|
{BRAND.name}
|
||||||
|
</motion.p>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ArrowLeftIcon } from 'lucide-react';
|
||||||
|
import { STATUS_LABEL, type Project } from '../data/site';
|
||||||
|
|
||||||
|
type ProjectCardProps = {
|
||||||
|
project: Project;
|
||||||
|
imageClass?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ProjectCard({ project, imageClass = 'h-72' }: ProjectCardProps) {
|
||||||
|
const isDone = project.status === 'done';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/projects/${project.slug}`}
|
||||||
|
className="group flex h-full flex-col border border-ivory/10 bg-char transition-[transform,box-shadow,border-color] duration-500 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-lift focus:outline-none focus-visible:ring-2 focus-visible:ring-copper"
|
||||||
|
aria-label={`مشاهده جزئیات پروژه ${project.title}`}>
|
||||||
|
|
||||||
|
<div className="relative overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={`تصویر پروژه ${project.title}`}
|
||||||
|
className={`w-full object-cover transition-transform duration-500 ease-lux group-hover:scale-105 ${imageClass}`} />
|
||||||
|
|
||||||
|
<span className="absolute inset-0 bg-ink/55 opacity-0 transition-opacity duration-300 ease-lux group-hover:opacity-100" />
|
||||||
|
<span className="absolute inset-0 flex items-center justify-center gap-3 text-sm font-bold text-ivory opacity-0 transition-opacity duration-300 ease-lux group-hover:opacity-100">
|
||||||
|
مشاهده پروژه
|
||||||
|
<ArrowLeftIcon className="h-4 w-4 text-copper" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`absolute right-5 top-5 flex items-center gap-2 bg-ink/85 px-3 py-1.5 text-xs font-light backdrop-blur-sm ${
|
||||||
|
isDone ? 'text-ivory/85' : 'text-copper'}`
|
||||||
|
}>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={`h-1.5 w-1.5 rounded-full ${isDone ? 'bg-ivory/60' : 'animate-pulse bg-copper'}`}
|
||||||
|
aria-hidden="true" />
|
||||||
|
|
||||||
|
{STATUS_LABEL[project.status]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 flex-col p-7 text-center">
|
||||||
|
<h3 className="text-xl font-bold text-ivory transition-colors duration-200 ease-lux group-hover:text-copper">
|
||||||
|
{project.title}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-sm font-light text-ivory/50">{project.location}</p>
|
||||||
|
<div className="mt-auto flex items-center justify-between border-t border-ivory/10 pt-5 text-xs font-light text-ivory/55">
|
||||||
|
<span>سال: {project.year}</span>
|
||||||
|
<span>{project.area}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
type RevealProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
delay?: number;
|
||||||
|
y?: number;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Reveal({ children, delay = 0, y = 28, className }: RevealProps) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={className}
|
||||||
|
initial={{ opacity: 0, y }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true, margin: '-80px' }}
|
||||||
|
transition={{ duration: 0.7, delay, ease: [0.23, 1, 0.32, 1] }}>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</motion.div>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Reveal } from './Reveal'
|
||||||
|
|
||||||
|
type SectionHeadingProps = {
|
||||||
|
title: string
|
||||||
|
lead?: string
|
||||||
|
align?: 'right' | 'center'
|
||||||
|
tone?: 'copper' | 'ivory'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SectionHeading({
|
||||||
|
title,
|
||||||
|
lead,
|
||||||
|
align = 'right', // برای بقیه سکشنها راستچین میمونه
|
||||||
|
tone = 'ivory',
|
||||||
|
}: SectionHeadingProps) {
|
||||||
|
const isCenter = align === 'center'
|
||||||
|
return (
|
||||||
|
<Reveal className={isCenter ? 'mx-auto max-w-2xl text-center' : 'max-w-2xl text-right'}>
|
||||||
|
{/* سرتیتر اصلی (بولد و بزرگ) */}
|
||||||
|
<h2 className={`text-section font-bold ${tone === 'copper' ? 'text-copper' : 'text-ivory'}`}>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* جمله توضیحات (کمرنگ/خاکستری) */}
|
||||||
|
{lead && <p className="mt-5 text-base font-light leading-9 text-ivory/60">{lead}</p>}
|
||||||
|
|
||||||
|
{/* خط مسیرنگ */}
|
||||||
|
<div className={`mt-6 h-px w-24 bg-copper/70 ${isCenter ? 'mx-auto' : ''}`} />
|
||||||
|
</Reveal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { Service } from '../data/site';
|
||||||
|
|
||||||
|
type ServiceIconProps = {
|
||||||
|
name: Service['icon'];
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stroke = {
|
||||||
|
fill: 'none',
|
||||||
|
stroke: 'currentColor',
|
||||||
|
strokeWidth: 1.2,
|
||||||
|
strokeLinecap: 'round' as const,
|
||||||
|
strokeLinejoin: 'round' as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ServiceIcon({ name, className = 'h-12 w-12' }: ServiceIconProps) {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 48 48" className={className} aria-hidden="true" role="presentation">
|
||||||
|
{name === 'draft' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M6 40h36" />
|
||||||
|
<path d="M10 40V14l14-8 14 8v26" />
|
||||||
|
<path d="M10 22h28M24 6v34" />
|
||||||
|
<circle cx="24" cy="22" r="3" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'tower' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M8 42h32" />
|
||||||
|
<path d="M15 42V10l9-5 9 5v32" />
|
||||||
|
<path d="M19 15h10M19 21h10M19 27h10M19 33h10" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'mall' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M6 42h36" />
|
||||||
|
<path d="M9 42V20h30v22" />
|
||||||
|
<path d="M9 20l6-10h18l6 10" />
|
||||||
|
<path d="M19 42V30h10v12" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'villa' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M5 42h38" />
|
||||||
|
<path d="M9 42V22l15-10 15 10v20" />
|
||||||
|
<path d="M18 42V30h12v12" />
|
||||||
|
<path d="M9 26h30" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'renew' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M10 42h28V20L24 10 10 20z" />
|
||||||
|
<path d="M31 6a9 9 0 1 0 6 15" />
|
||||||
|
<path d="M38 5v7h-7" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'manage' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<rect x="8" y="8" width="32" height="32" rx="2" />
|
||||||
|
<path d="M8 17h32" />
|
||||||
|
<path d="M14 25h8M14 32h14M27 24l4 4 7-8" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'invest' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M7 40h34" />
|
||||||
|
<path d="M12 40V26M22 40V18M32 40V30" />
|
||||||
|
<path d="M9 16l10-6 8 5 12-9" />
|
||||||
|
<path d="M39 6h-7M39 6v7" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
{name === 'landscape' &&
|
||||||
|
<g {...stroke}>
|
||||||
|
<path d="M5 40h38" />
|
||||||
|
<path d="M14 40V24M14 24c-4 0-7-3-7-6s3-5 7-5 7 2 7 5-3 6-7 6z" />
|
||||||
|
<path d="M31 40V22l7-7 4 5v20" />
|
||||||
|
<path d="M24 40c2-4 5-6 9-6" />
|
||||||
|
</g>
|
||||||
|
}
|
||||||
|
</svg>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ArrowLeftIcon } from 'lucide-react';
|
||||||
|
import { Reveal } from '../Reveal';
|
||||||
|
import { ButtonLink } from '../Button';
|
||||||
|
import { ArticleCard } from '../ArticleCard';
|
||||||
|
import { ARTICLES } from '../../data/site';
|
||||||
|
|
||||||
|
export function Articles() {
|
||||||
|
return (
|
||||||
|
<section className="bg-char py-24 lg:py-32" aria-labelledby="articles-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<Reveal className="max-w-2xl text-right">
|
||||||
|
<p className="text-sm font-light text-copper">وبلاگ</p>
|
||||||
|
<h2 id="articles-title" className="mt-5 text-section font-bold text-ivory">
|
||||||
|
آخرین مقالات
|
||||||
|
</h2>
|
||||||
|
<p className="mt-5 text-base font-light leading-9 text-ivory/60">
|
||||||
|
یادداشتهای تیم معماری، اجرا و سرمایهگذاری آرامش پلازا درباره آنچه در کارگاه و بازار میگذرد.
|
||||||
|
</p>
|
||||||
|
<div className="mt-6 h-px w-24 bg-copper/70" />
|
||||||
|
</Reveal>
|
||||||
|
|
||||||
|
<div className="mt-14 grid gap-6 lg:grid-cols-3">
|
||||||
|
{ARTICLES.slice(0, 3).map((article, i) =>
|
||||||
|
<Reveal key={article.slug} delay={0.06 * i} className="h-full">
|
||||||
|
<ArticleCard article={article} />
|
||||||
|
</Reveal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Reveal delay={0.1}>
|
||||||
|
<div className="mt-12 flex justify-center">
|
||||||
|
<ButtonLink to="/blog" variant="outline" className="font-light">
|
||||||
|
همه مقالات
|
||||||
|
<ArrowLeftIcon className="h-4 w-4" />
|
||||||
|
</ButtonLink>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { MoveHorizontalIcon } from 'lucide-react'
|
||||||
|
import { Reveal } from '../Reveal'
|
||||||
|
import { SectionHeading } from '../SectionHeading'
|
||||||
|
import { BEFORE_AFTER } from '../../data/site'
|
||||||
|
|
||||||
|
export function BeforeAfter() {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const [position, setPosition] = useState(52)
|
||||||
|
const dragging = useRef(false)
|
||||||
|
|
||||||
|
const setFromClientX = useCallback((clientX: number) => {
|
||||||
|
const el = containerRef.current
|
||||||
|
if (!el) return
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
const ratio = ((clientX - rect.left) / rect.width) * 100
|
||||||
|
setPosition(Math.min(98, Math.max(2, ratio)))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const move = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!dragging.current) return
|
||||||
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
|
||||||
|
setFromClientX(clientX)
|
||||||
|
}
|
||||||
|
const stop = () => {
|
||||||
|
dragging.current = false
|
||||||
|
}
|
||||||
|
window.addEventListener('mousemove', move)
|
||||||
|
window.addEventListener('touchmove', move)
|
||||||
|
window.addEventListener('mouseup', stop)
|
||||||
|
window.addEventListener('touchend', stop)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('mousemove', move)
|
||||||
|
window.removeEventListener('touchmove', move)
|
||||||
|
window.removeEventListener('mouseup', stop)
|
||||||
|
window.removeEventListener('touchend', stop)
|
||||||
|
}
|
||||||
|
}, [setFromClientX])
|
||||||
|
|
||||||
|
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'ArrowLeft') setPosition((p) => Math.max(2, p - 4))
|
||||||
|
if (e.key === 'ArrowRight') setPosition((p) => Math.min(98, p + 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-char py-24 lg:py-32" aria-labelledby="ba-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
|
||||||
|
<SectionHeading
|
||||||
|
title={BEFORE_AFTER.title}
|
||||||
|
lead={BEFORE_AFTER.lead}
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Reveal delay={0.1}>
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
// کادر متناسب (اصلاح نسبت ابعاد برای پر شدن کامل بدون پدینگ کناری)
|
||||||
|
className="relative mx-auto mt-14 aspect-[4/3] w-full max-w-4xl select-none overflow-hidden rounded-xl border border-ivory/10 shadow-2xl md:aspect-[16/10]"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
dragging.current = true
|
||||||
|
setFromClientX(e.clientX)
|
||||||
|
}}
|
||||||
|
onTouchStart={(e) => {
|
||||||
|
dragging.current = true
|
||||||
|
setFromClientX(e.touches[0].clientX)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* عکس اول (قبل) - پر کننده کامل کادر */}
|
||||||
|
<img
|
||||||
|
src={BEFORE_AFTER.before}
|
||||||
|
alt={BEFORE_AFTER.beforeLabel}
|
||||||
|
className="absolute inset-0 h-full w-full object-cover object-center"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* عکس دوم (بعد) با قابلیت برش اسلایدر */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden" style={{ clipPath: `inset(0 0 0 ${position}%)` }} aria-hidden="true">
|
||||||
|
<img
|
||||||
|
src={BEFORE_AFTER.after}
|
||||||
|
alt=""
|
||||||
|
className="absolute inset-0 h-full w-full object-cover object-center"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* لیبلها */}
|
||||||
|
<span className="pointer-events-none absolute left-6 top-6 z-30 rounded-md bg-ink/85 px-4 py-2 text-xs font-light text-ivory backdrop-blur-md border border-ivory/10 shadow-lg">
|
||||||
|
{BEFORE_AFTER.beforeLabel}
|
||||||
|
</span>
|
||||||
|
<span className="pointer-events-none absolute right-6 top-6 z-30 rounded-md bg-ink/85 px-4 py-2 text-xs font-light text-copper backdrop-blur-md border border-copper/30 shadow-lg">
|
||||||
|
{BEFORE_AFTER.afterLabel}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* خط جداکننده */}
|
||||||
|
<div className="pointer-events-none absolute inset-y-0 z-30" style={{ left: `${position}%` }}>
|
||||||
|
<div className="h-full w-[2px] bg-copper shadow-[0_0_12px_rgba(213,135,76,0.9)]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* دکمه وسط */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="slider"
|
||||||
|
aria-label="مقایسه کانسپت و پروژه ساختهشده"
|
||||||
|
aria-valuemin={0}
|
||||||
|
aria-valuemax={100}
|
||||||
|
aria-valuenow={Math.round(position)}
|
||||||
|
onKeyDown={onKeyDown}
|
||||||
|
onMouseDown={() => {
|
||||||
|
dragging.current = true
|
||||||
|
}}
|
||||||
|
onTouchStart={() => {
|
||||||
|
dragging.current = true
|
||||||
|
}}
|
||||||
|
className="absolute top-1/2 z-40 flex h-14 w-14 -translate-x-1/2 -translate-y-1/2 cursor-ew-resize items-center justify-center rounded-full bg-copper text-char shadow-2xl transition-transform duration-200 ease-lux hover:scale-110 focus:outline-none focus-visible:ring-2 focus-visible:ring-ivory"
|
||||||
|
style={{ left: `${position}%` }}
|
||||||
|
>
|
||||||
|
<MoveHorizontalIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { PlusIcon } from 'lucide-react';
|
||||||
|
import { Reveal } from '../Reveal';
|
||||||
|
import { SectionHeading } from '../SectionHeading';
|
||||||
|
import { FAQS } from '../../data/site';
|
||||||
|
|
||||||
|
export function Faq() {
|
||||||
|
const [open, setOpen] = useState<number | null>(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-ink py-24 lg:py-32" aria-labelledby="faq-title">
|
||||||
|
<div className="mx-auto grid max-w-site gap-14 px-6 lg:grid-cols-12 lg:px-10">
|
||||||
|
<div className="lg:col-span-4">
|
||||||
|
<SectionHeading
|
||||||
|
title="سوالات متداول"
|
||||||
|
lead="پاسخ کوتاه به پرتکرارترین پرسشهای سرمایهگذاران و خریداران." />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-8">
|
||||||
|
<ul>
|
||||||
|
{FAQS.map((faq, i) => {
|
||||||
|
const isOpen = open === i;
|
||||||
|
return (
|
||||||
|
<Reveal key={faq.q} delay={0.04 * i}>
|
||||||
|
<li className="border-b border-ivory/10">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(isOpen ? null : i)}
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
className="flex w-full items-center justify-between gap-6 py-6 text-right transition-colors duration-200 ease-lux hover:text-copper">
|
||||||
|
|
||||||
|
<span className={`text-base font-bold md:text-lg ${isOpen ? 'text-copper' : 'text-ivory'}`}>
|
||||||
|
{faq.q}
|
||||||
|
</span>
|
||||||
|
<PlusIcon
|
||||||
|
className={`h-5 w-5 shrink-0 text-copper transition-transform duration-300 ease-lux ${
|
||||||
|
isOpen ? 'rotate-45' : ''}`
|
||||||
|
} />
|
||||||
|
|
||||||
|
</button>
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{isOpen &&
|
||||||
|
<motion.div
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.28, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="overflow-hidden">
|
||||||
|
|
||||||
|
<p className="pb-7 pl-10 text-sm font-light leading-9 text-ivory/60">{faq.a}</p>
|
||||||
|
</motion.div>
|
||||||
|
}
|
||||||
|
</AnimatePresence>
|
||||||
|
</li>
|
||||||
|
</Reveal>);
|
||||||
|
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { ArrowLeftIcon } from 'lucide-react'
|
||||||
|
import { Reveal } from '../Reveal'
|
||||||
|
import { ButtonLink } from '../Button'
|
||||||
|
import { ProjectCard } from '../ProjectCard'
|
||||||
|
import { SectionHeading } from '../SectionHeading'
|
||||||
|
import { PROJECTS, type ProjectStatus } from '../../data/site'
|
||||||
|
|
||||||
|
const FILTERS: { key: ProjectStatus; label: string }[] = [
|
||||||
|
{ key: 'done', label: 'انجام شده' },
|
||||||
|
{ key: 'progress', label: 'در حال انجام' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function FeaturedProjects() {
|
||||||
|
const [status, setStatus] = useState<ProjectStatus>('done')
|
||||||
|
const list = PROJECTS.filter((p) => p.status === status).slice(0, 3)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-ink py-24 lg:py-32" aria-labelledby="projects-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<SectionHeading
|
||||||
|
title="پروژههای شاخص"
|
||||||
|
lead="کارهایی که بیشترین چیز را درباره استاندارد ما میگویند؛ چه آنهایی که تحویل شدهاند و چه آنهایی که همین امروز در حال ساختاند."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-10 flex flex-wrap items-center 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-10 grid gap-6 lg:grid-cols-3">
|
||||||
|
{list.map((project, i) => (
|
||||||
|
<Reveal key={project.slug} delay={0.06 * i} className="h-full">
|
||||||
|
<ProjectCard project={project} />
|
||||||
|
</Reveal>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Reveal delay={0.1}>
|
||||||
|
<div className="mt-12 flex justify-center">
|
||||||
|
<ButtonLink to="/projects" variant="outline" className="font-light">
|
||||||
|
همه پروژهها
|
||||||
|
<ArrowLeftIcon className="h-4 w-4" />
|
||||||
|
</ButtonLink>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon, XIcon } from 'lucide-react'
|
||||||
|
import { Reveal } from '../Reveal'
|
||||||
|
import { SectionHeading } from '../SectionHeading'
|
||||||
|
import { IMAGES } from '../../data/site'
|
||||||
|
|
||||||
|
const GRID = [IMAGES[0], IMAGES[1], IMAGES[2], IMAGES[3], IMAGES[4], IMAGES[1]]
|
||||||
|
|
||||||
|
export function Gallery() {
|
||||||
|
const [active, setActive] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const close = useCallback(() => setActive(null), [])
|
||||||
|
const step = useCallback((dir: number) => {
|
||||||
|
setActive((prev) => (prev === null ? prev : (prev + dir + GRID.length) % GRID.length))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (active === null) return
|
||||||
|
const onKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') close()
|
||||||
|
if (e.key === 'ArrowRight') step(-1)
|
||||||
|
if (e.key === 'ArrowLeft') step(1)
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
return () => window.removeEventListener('keydown', onKey)
|
||||||
|
}, [active, close, step])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-ink py-24 lg:py-32" aria-labelledby="gallery-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<SectionHeading
|
||||||
|
title="گالری تصاویر"
|
||||||
|
lead="قابهایی از کارگاهها و پروژههای تحویلشده آرامش پلازا."
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-14 grid grid-cols-2 gap-4 lg:grid-cols-3">
|
||||||
|
{GRID.map((src, i) => (
|
||||||
|
<Reveal key={`${src}-${i}`} delay={0.04 * i}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setActive(i)}
|
||||||
|
className="group block w-full overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-copper"
|
||||||
|
aria-label={`بزرگنمایی تصویر شماره ${(i + 1).toLocaleString('fa-IR')}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={`تصویری از پروژههای آرامش پلازا، شماره ${(i + 1).toLocaleString('fa-IR')}`}
|
||||||
|
className="aspect-[4/3] w-full object-cover transition-transform duration-500 ease-lux group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</Reveal>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{active !== null && (
|
||||||
|
<motion.div
|
||||||
|
// پسزمینه تاریک با افکت بلور (شیشهای تار) برای وضوح بیشتر عکس
|
||||||
|
className="fixed inset-0 z-[80] flex items-center justify-center bg-char/80 backdrop-blur-lg p-6"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
// زمان انیمیشن برای نرمتر شدن افزایش یافت
|
||||||
|
transition={{ duration: 0.5, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="نمایش تصویر"
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={close}
|
||||||
|
className="absolute left-8 top-8 flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper"
|
||||||
|
aria-label="بستن"
|
||||||
|
>
|
||||||
|
<XIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
step(-1)
|
||||||
|
}}
|
||||||
|
className="absolute right-6 flex h-12 w-12 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper md:right-10"
|
||||||
|
aria-label="تصویر قبلی"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
step(1)
|
||||||
|
}}
|
||||||
|
className="absolute left-6 flex h-12 w-12 items-center justify-center border border-ivory/20 text-ivory transition-colors duration-200 ease-lux hover:border-copper hover:text-copper md:left-10"
|
||||||
|
aria-label="تصویر بعدی"
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<motion.img
|
||||||
|
key={active}
|
||||||
|
src={GRID[active]}
|
||||||
|
alt=""
|
||||||
|
// انیمیشن نرمتر ورود عکس با تغییر Scale
|
||||||
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.95 }}
|
||||||
|
transition={{ duration: 0.5, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
// اضافه شدن سایه خیلی نرم پشت عکس برای برجسته شدن
|
||||||
|
className="max-h-[85vh] w-auto max-w-[85vw] object-contain shadow-[0_30px_80px_-20px_rgba(0,0,0,0.9)]"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||||
|
import { ArrowLeftIcon, ChevronDownIcon } from 'lucide-react';
|
||||||
|
import { ButtonLink } from '../Button';
|
||||||
|
import { BRAND } from '../../data/site';
|
||||||
|
|
||||||
|
export function Hero() {
|
||||||
|
const { scrollY } = useScroll();
|
||||||
|
const imageY = useTransform(scrollY, [0, 700], [0, 110]);
|
||||||
|
const contentY = useTransform(scrollY, [0, 700], [0, -55]);
|
||||||
|
const overlay = useTransform(scrollY, [0, 600], [0.5, 0.78]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative h-screen min-h-[640px] w-full overflow-hidden" aria-label="معرفی آرامش پلازا">
|
||||||
|
<motion.img
|
||||||
|
src={BRAND.hero}
|
||||||
|
alt="نمای شبانه برج مسکونی ساختهشده توسط آرامش پلازا"
|
||||||
|
style={{ y: imageY }}
|
||||||
|
className="absolute inset-0 h-[114%] w-full object-cover" />
|
||||||
|
|
||||||
|
<motion.div className="absolute inset-0 bg-ink" style={{ opacity: overlay }} aria-hidden="true" />
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
'linear-gradient(to top, #27221c 5%, rgba(39,34,28,0.2) 60%, rgba(39,34,28,0.6) 100%)'
|
||||||
|
}}
|
||||||
|
aria-hidden="true" />
|
||||||
|
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
style={{ y: contentY }}
|
||||||
|
className="relative mx-auto flex h-full max-w-site flex-col justify-center px-6 lg:px-10">
|
||||||
|
|
||||||
|
<div className="w-full max-w-[700px] lg:max-w-[58%]">
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 18 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.7, delay: 2.1, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="text-sm font-light text-copper">
|
||||||
|
|
||||||
|
از سال ۱۳۸۳ — تهران، اصفهان، سواحل شمال
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 28 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.85, delay: 2.2, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="mt-6 text-hero font-bold text-ivory">
|
||||||
|
|
||||||
|
ساختمانی که آرامش را
|
||||||
|
<span className="text-copper"> معماری </span>
|
||||||
|
میکند
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 22 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.85, delay: 2.32, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="mt-7 max-w-xl text-base font-light leading-9 text-ivory/70">
|
||||||
|
|
||||||
|
طراحی، ساخت و سرمایهگذاری در پروژههای مسکونی و تجاری لوکس؛ با استانداردی که در جزئیات دیده میشود و در
|
||||||
|
سالها دوام میآورد.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 22 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.85, delay: 2.44, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="mt-10 flex flex-wrap items-center gap-4">
|
||||||
|
|
||||||
|
<ButtonLink to="/projects" variant="copper">
|
||||||
|
مشاهده پروژهها
|
||||||
|
<ArrowLeftIcon className="h-4 w-4" />
|
||||||
|
</ButtonLink>
|
||||||
|
<ButtonLink to="/contact" variant="outline" className="font-light">
|
||||||
|
مشاوره سرمایهگذاری
|
||||||
|
</ButtonLink>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: -24 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.9, delay: 2.6, ease: [0.23, 1, 0.32, 1] }}
|
||||||
|
className="absolute bottom-28 left-6 hidden w-60 border border-ivory/15 bg-char/60 p-6 backdrop-blur-md lg:block lg:left-10">
|
||||||
|
|
||||||
|
<p className="text-3xl font-bold text-copper">۴۷</p>
|
||||||
|
<p className="mt-2 text-sm font-light leading-7 text-ivory/70">
|
||||||
|
پروژه تحویلشده در بیستویک سال فعالیت حرفهای
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#story"
|
||||||
|
aria-label="رفتن به بخش بعدی"
|
||||||
|
className="absolute bottom-8 right-1/2 z-10 flex translate-x-1/2 flex-col items-center gap-2 text-ivory/50 transition-colors duration-200 ease-lux hover:text-copper">
|
||||||
|
|
||||||
|
<span className="text-xs font-light">اسکرول کنید</span>
|
||||||
|
<ChevronDownIcon className="h-4 w-4 animate-bounce" />
|
||||||
|
</a>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { CheckIcon, SendIcon } from 'lucide-react'
|
||||||
|
import { Reveal } from '../Reveal'
|
||||||
|
import { Button } from '../Button'
|
||||||
|
|
||||||
|
export function Newsletter() {
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [status, setStatus] = useState<'idle' | 'error' | 'done'>('idle')
|
||||||
|
|
||||||
|
const submit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!/^\S+@\S+\.\S+$/.test(email)) {
|
||||||
|
setStatus('error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setStatus('done')
|
||||||
|
setEmail('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-copper py-16" aria-labelledby="newsletter-title">
|
||||||
|
<div className="mx-auto grid max-w-site items-center gap-8 px-6 lg:grid-cols-2 lg:px-10">
|
||||||
|
<Reveal>
|
||||||
|
<h2 id="newsletter-title" className="text-2xl font-bold leading-relaxed text-char md:text-3xl">
|
||||||
|
خبرنامه آرامش پلازا
|
||||||
|
</h2>
|
||||||
|
<p className="mt-3 max-w-md text-sm font-light leading-8 text-char/75">
|
||||||
|
هر ماه یک ایمیل: پروژههای جدید، فرصتهای سرمایهگذاری و یادداشتهای تیم معماری.
|
||||||
|
</p>
|
||||||
|
</Reveal>
|
||||||
|
|
||||||
|
<Reveal delay={0.08}>
|
||||||
|
<form onSubmit={submit} className="flex flex-col gap-3 sm:flex-row" noValidate>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label htmlFor="newsletter-email" className="sr-only">
|
||||||
|
نشانی ایمیل
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="newsletter-email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => {
|
||||||
|
setEmail(e.target.value)
|
||||||
|
if (status !== 'idle') setStatus('idle')
|
||||||
|
}}
|
||||||
|
placeholder="نشانی ایمیل شما"
|
||||||
|
className="w-full border border-transparent bg-char px-5 py-4 text-sm font-light text-ivory placeholder:text-ivory/40 transition-all duration-300 ease-lux focus:border-ivory focus:outline-none"
|
||||||
|
aria-invalid={status === 'error'}
|
||||||
|
/>
|
||||||
|
{status === 'error' && (
|
||||||
|
<p className="mt-2 text-xs font-light text-char" role="alert">
|
||||||
|
لطفاً یک ایمیل معتبر وارد کنید.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{status === 'done' && (
|
||||||
|
<p className="mt-2 flex items-center gap-1.5 text-xs font-light text-char" role="status">
|
||||||
|
<CheckIcon className="h-3.5 w-3.5" />
|
||||||
|
عضویت شما ثبت شد.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* استفاده از واریانت جدید که با رنگ روشن پر میشه */}
|
||||||
|
<Button type="submit" variant="dark-ivory">
|
||||||
|
<SendIcon className="h-4 w-4" />
|
||||||
|
عضویت
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Reveal } from '../Reveal'
|
||||||
|
import { ServiceIcon } from '../ServiceIcon'
|
||||||
|
import { SectionHeading } from '../SectionHeading'
|
||||||
|
import { SERVICES } from '../../data/site'
|
||||||
|
|
||||||
|
export function Services() {
|
||||||
|
return (
|
||||||
|
<section className="bg-ink py-24 lg:py-32" aria-labelledby="services-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<SectionHeading
|
||||||
|
title="خدمات "
|
||||||
|
lead="از نخستین خط روی کاغذ تا تحویل کلید، مدیریت سرمایه و طراحی محوطه؛ همه مراحل در یک مجموعه و زیر یک نظام کنترل کیفیت."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-14 grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{SERVICES.map((service, i) => (
|
||||||
|
<Reveal key={service.title} delay={0.04 * i} className="h-full">
|
||||||
|
<article className="group flex h-full flex-col items-center border border-ivory/10 bg-char p-8 text-center transition-[transform,box-shadow,border-color] duration-500 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-lift">
|
||||||
|
<ServiceIcon
|
||||||
|
name={service.icon}
|
||||||
|
className="h-12 w-12 text-copper/85 transition-colors duration-300 ease-lux group-hover:text-copper"
|
||||||
|
/>
|
||||||
|
<h3 className="mt-6 text-lg font-bold text-ivory">{service.title}</h3>
|
||||||
|
<p className="mt-3 text-sm font-light leading-8 text-ivory/55">{service.desc}</p>
|
||||||
|
</article>
|
||||||
|
</Reveal>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useInView } from 'framer-motion';
|
||||||
|
import { STATS } from '../../data/site';
|
||||||
|
|
||||||
|
function toFa(n: number) {
|
||||||
|
return n.toLocaleString('fa-IR');
|
||||||
|
}
|
||||||
|
|
||||||
|
function Counter({ value }: {value: number;}) {
|
||||||
|
const ref = useRef<HTMLSpanElement>(null);
|
||||||
|
const inView = useInView(ref, { once: true, margin: '-60px' });
|
||||||
|
const [display, setDisplay] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!inView) return;
|
||||||
|
let frame = 0;
|
||||||
|
const total = 60;
|
||||||
|
const id = window.setInterval(() => {
|
||||||
|
frame += 1;
|
||||||
|
const progress = 1 - Math.pow(1 - frame / total, 3);
|
||||||
|
setDisplay(Math.round(value * progress));
|
||||||
|
if (frame >= total) window.clearInterval(id);
|
||||||
|
}, 20);
|
||||||
|
return () => window.clearInterval(id);
|
||||||
|
}, [inView, value]);
|
||||||
|
|
||||||
|
return <span ref={ref}>{toFa(display)}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Stats() {
|
||||||
|
return (
|
||||||
|
<section className="bg-char py-16" aria-label="آمار و ارقام">
|
||||||
|
<div className="mx-auto grid max-w-site grid-cols-2 gap-y-12 px-6 lg:grid-cols-4 lg:px-10">
|
||||||
|
{STATS.map((stat) =>
|
||||||
|
<div key={stat.label} className="text-center">
|
||||||
|
<p className="text-4xl font-bold text-copper md:text-5xl">
|
||||||
|
<Counter value={stat.value} />
|
||||||
|
{stat.suffix && <span className="mr-1 text-2xl">{stat.suffix}</span>}
|
||||||
|
</p>
|
||||||
|
<p className="mt-3 text-sm font-light text-ivory/55">{stat.label}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ArrowLeftIcon } from 'lucide-react'
|
||||||
|
import { Reveal } from '../Reveal'
|
||||||
|
import { ButtonLink } from '../Button'
|
||||||
|
import { SectionHeading } from '../SectionHeading'
|
||||||
|
import { IMAGES } from '../../data/site'
|
||||||
|
|
||||||
|
const BENTO = [
|
||||||
|
{ src: IMAGES[0], alt: 'نمای برج مسکونی آرامش در تهران' },
|
||||||
|
{ src: IMAGES[1], alt: 'آتریوم مرکزی مجتمع تجاری پارسه' },
|
||||||
|
{ src: IMAGES[2], alt: 'ویلاهای ساختهشده در سواحل شمال' },
|
||||||
|
{ src: IMAGES[3], alt: 'نمای شبانه برج دوقلوی نیاوران' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function Story() {
|
||||||
|
return (
|
||||||
|
<section id="story" className="bg-ink py-24 lg:py-32" aria-labelledby="story-title">
|
||||||
|
<div className="mx-auto grid max-w-site items-center gap-14 px-6 lg:grid-cols-2 lg:px-10">
|
||||||
|
<div>
|
||||||
|
{/* اینجا متن کوچک بالای تیتر حذف شد تا کاملاً یکدست بشه */}
|
||||||
|
<SectionHeading
|
||||||
|
title="درباره ی ما"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Reveal delay={0.08}>
|
||||||
|
<p className="mt-7 text-base font-light leading-9 text-ivory/65">
|
||||||
|
آرامش پلازا با یک دفتر کوچک معماری آغاز شد و امروز یکی از مجموعههای شناختهشده ساختوساز لوکس در ایران است. در تمام این سالها یک اصل تغییر نکرده است: ساختمان باید برای زندگی ساخته شود، نه برای فروش سریع.
|
||||||
|
</p>
|
||||||
|
<p className="mt-5 text-base font-light leading-9 text-ivory/65">
|
||||||
|
هر پروژه با مطالعه زمین، نور، اقلیم و الگوی زندگی ساکنان آغاز میشود؛ سپس تیم طراحی، سازه و اجرا در یک فرآیند واحد آن را تا تحویل کلید پیش میبرند.
|
||||||
|
</p>
|
||||||
|
</Reveal>
|
||||||
|
<Reveal delay={0.16}>
|
||||||
|
<div className="mt-10 grid grid-cols-2 gap-8 border-t border-ivory/10 pt-8">
|
||||||
|
<div>
|
||||||
|
<p className="text-2xl font-bold text-copper">۹۴٪</p>
|
||||||
|
<p className="mt-2 text-sm font-light text-ivory/55">تحویل بهموقع پروژهها</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-2xl font-bold text-copper">۵ سال</p>
|
||||||
|
<p className="mt-2 text-sm font-light text-ivory/55">پشتیبانی پس از تحویل</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
<Reveal delay={0.22}>
|
||||||
|
<ButtonLink to="/about" variant="outline" className="mt-10 font-light">
|
||||||
|
داستان کامل ما
|
||||||
|
<ArrowLeftIcon className="h-4 w-4" />
|
||||||
|
</ButtonLink>
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
{BENTO.map((item, i) => (
|
||||||
|
<Reveal key={item.src + i} delay={0.06 * i}>
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={item.src}
|
||||||
|
alt={item.alt}
|
||||||
|
className="aspect-square w-full object-cover transition-transform duration-500 ease-lux hover:scale-105"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Reveal } from '../Reveal';
|
||||||
|
import { SectionHeading } from '../SectionHeading';
|
||||||
|
import { TEAM } from '../../data/site';
|
||||||
|
|
||||||
|
export function Team() {
|
||||||
|
return (
|
||||||
|
<section className="bg-ink py-24 lg:py-32" aria-labelledby="team-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<SectionHeading
|
||||||
|
title="تیم ما"
|
||||||
|
lead="کسانی که تصمیمهای اصلی هر پروژه را میگیرند و پاسخگوی آن هستند."
|
||||||
|
align="center" />
|
||||||
|
|
||||||
|
|
||||||
|
<div className="mt-14 grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{TEAM.map((member, i) =>
|
||||||
|
<Reveal key={member.name} delay={0.06 * i} className="h-full">
|
||||||
|
<article className="group flex h-full flex-col items-center border border-ivory/10 bg-char p-8 text-center transition-[transform,box-shadow,border-color] duration-500 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-lift">
|
||||||
|
<div className="flex h-24 w-24 items-center justify-center rounded-full border border-copper/40 text-3xl font-bold text-copper">
|
||||||
|
{member.name.trim().charAt(0)}
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-6 text-lg font-bold text-ivory">{member.name}</h3>
|
||||||
|
<p className="mt-1 text-sm font-light text-copper">{member.role}</p>
|
||||||
|
<p className="mt-auto pt-4 text-sm font-light leading-7 text-ivory/50">{member.bio}</p>
|
||||||
|
</article>
|
||||||
|
</Reveal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon, QuoteIcon } from 'lucide-react';
|
||||||
|
import { SectionHeading } from '../SectionHeading';
|
||||||
|
import { TESTIMONIALS } from '../../data/site';
|
||||||
|
|
||||||
|
export function Testimonials() {
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
const item = TESTIMONIALS[index];
|
||||||
|
|
||||||
|
const go = (dir: number) => {
|
||||||
|
setIndex((prev) => (prev + dir + TESTIMONIALS.length) % TESTIMONIALS.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="border-y border-ivory/10 bg-char py-24 lg:py-32" aria-labelledby="testimonials-title">
|
||||||
|
<div className="mx-auto max-w-site px-6 lg:px-10">
|
||||||
|
<SectionHeading title="نظرات مشتریان" align="center" />
|
||||||
|
|
||||||
|
<div className="relative mx-auto mt-14 max-w-3xl text-center">
|
||||||
|
<QuoteIcon className="mx-auto h-8 w-8 text-copper/60" aria-hidden="true" />
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.blockquote
|
||||||
|
key={item.name}
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -12 }}
|
||||||
|
transition={{ duration: 0.28, ease: [0.23, 1, 0.32, 1] }}>
|
||||||
|
|
||||||
|
<p className="mt-8 text-lg font-light leading-[2.4rem] text-ivory/85 md:text-xl">«{item.quote}»</p>
|
||||||
|
<footer className="mt-8">
|
||||||
|
<p className="text-base font-bold text-copper">{item.name}</p>
|
||||||
|
<p className="mt-1 text-sm font-light text-ivory/45">{item.role}</p>
|
||||||
|
</footer>
|
||||||
|
</motion.blockquote>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<div className="mt-10 flex items-center justify-center gap-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => go(1)}
|
||||||
|
aria-label="نظر قبلی"
|
||||||
|
className="flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory/70 transition-colors duration-200 ease-lux hover:border-copper hover:text-copper">
|
||||||
|
|
||||||
|
<ChevronRightIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
<div className="flex gap-2" role="tablist" aria-label="انتخاب نظر">
|
||||||
|
{TESTIMONIALS.map((t, i) =>
|
||||||
|
<button
|
||||||
|
key={t.name}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={i === index}
|
||||||
|
aria-label={`نظر ${t.name}`}
|
||||||
|
onClick={() => setIndex(i)}
|
||||||
|
className={`h-1.5 transition-[width,background-color] duration-200 ease-lux ${
|
||||||
|
i === index ? 'w-8 bg-copper' : 'w-3 bg-ivory/25'}`
|
||||||
|
} />
|
||||||
|
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => go(-1)}
|
||||||
|
aria-label="نظر بعدی"
|
||||||
|
className="flex h-11 w-11 items-center justify-center border border-ivory/20 text-ivory/70 transition-colors duration-200 ease-lux hover:border-copper hover:text-copper">
|
||||||
|
|
||||||
|
<ChevronLeftIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import React, { useRef } from 'react'
|
||||||
|
import { motion, useScroll, useTransform, useInView } from 'framer-motion'
|
||||||
|
import { TIMELINE } from '../../data/site'
|
||||||
|
import { SectionHeading } from '../SectionHeading'
|
||||||
|
|
||||||
|
const ease = [0.23, 1, 0.32, 1] as const
|
||||||
|
|
||||||
|
type Milestone = (typeof TIMELINE)[0]
|
||||||
|
|
||||||
|
function Item({ item, index }: { item: Milestone; index: number }) {
|
||||||
|
const ref = useRef<HTMLLIElement>(null)
|
||||||
|
const active = useInView(ref, { once: true, amount: 0.6 })
|
||||||
|
const onRight = index % 2 === 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li ref={ref} className="relative md:grid md:grid-cols-2 md:gap-16">
|
||||||
|
{/* نقطه روی خط تایملاین */}
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className={`absolute right-[-5px] top-6 z-10 h-2.5 w-2.5 rounded-full transition-all duration-500 ease-lux md:right-1/2 md:translate-x-1/2 ${
|
||||||
|
active
|
||||||
|
? 'bg-copper shadow-[0_0_0_5px_rgba(213,135,76,0.22)]'
|
||||||
|
: 'bg-ivory/25'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* باکس محتوا */}
|
||||||
|
<motion.div
|
||||||
|
className={`pr-8 md:pr-0 ${
|
||||||
|
onRight ? 'md:col-start-1 md:pl-16 md:text-left' : 'md:col-start-2 md:pr-16'
|
||||||
|
}`}
|
||||||
|
initial={{ opacity: 0, y: 24 }}
|
||||||
|
animate={active ? { opacity: 1, y: 0 } : { opacity: 0, y: 24 }}
|
||||||
|
transition={{ duration: 0.55, ease }}
|
||||||
|
>
|
||||||
|
<div className="border border-ivory/10 bg-ink p-6 md:p-7">
|
||||||
|
<span className="text-sm font-light text-copper">{item.year}</span>
|
||||||
|
<h3 className="mt-3 text-lg font-bold text-ivory">{item.title}</h3>
|
||||||
|
<p className="mt-3 text-sm font-light leading-8 text-ivory/60">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Timeline() {
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
const { scrollYProgress } = useScroll({
|
||||||
|
target: ref,
|
||||||
|
offset: ['start 70%', 'end 60%'],
|
||||||
|
})
|
||||||
|
const scaleY = useTransform(scrollYProgress, [0, 1], [0, 1])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-char py-24 md:py-32" aria-labelledby="timeline-title">
|
||||||
|
<div className="mx-auto w-full max-w-site px-6 lg:px-10">
|
||||||
|
|
||||||
|
<div className="mb-16">
|
||||||
|
<SectionHeading
|
||||||
|
title="مسیر ما"
|
||||||
|
lead="از یک دفتر چهارنفره تا ۴۷ پروژه. خط زمان پروژههای ما را ببینید."
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref={ref} className="relative mx-auto max-w-4xl">
|
||||||
|
{/* خط پسزمینه کمرنگ */}
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-y-0 right-0 w-px bg-ivory/12 md:right-1/2"
|
||||||
|
/>
|
||||||
|
{/* خط پرشونده رنگی */}
|
||||||
|
<motion.span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute inset-y-0 right-0 w-px origin-top bg-copper md:right-1/2"
|
||||||
|
style={{ scaleY }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ol className="flex flex-col gap-12 md:gap-16">
|
||||||
|
{TIMELINE.map((item, i) => (
|
||||||
|
<Item key={item.year} item={item} index={i} />
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Reveal } from '../Reveal';
|
||||||
|
import { SectionHeading } from '../SectionHeading';
|
||||||
|
import { WHY, IMAGES } from '../../data/site';
|
||||||
|
|
||||||
|
export function WhyUs() {
|
||||||
|
return (
|
||||||
|
<section className="bg-ink py-24 lg:py-32" aria-labelledby="why-title">
|
||||||
|
<div className="mx-auto grid max-w-site gap-14 px-6 lg:grid-cols-12 lg:px-10">
|
||||||
|
<div className="lg:col-span-5">
|
||||||
|
<SectionHeading
|
||||||
|
title="چرا آرامش پلازا"
|
||||||
|
lead="شش دلیلی که کارفرمایان و سرمایهگذاران بار دوم هم به ما برمیگردند." />
|
||||||
|
|
||||||
|
<Reveal delay={0.12}>
|
||||||
|
<img
|
||||||
|
src={IMAGES[3]}
|
||||||
|
alt="نمای شبانه برج دوقلوی نیاوران"
|
||||||
|
className="mt-10 h-80 w-full object-cover" />
|
||||||
|
|
||||||
|
</Reveal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-7">
|
||||||
|
<div className="grid gap-x-10 gap-y-px sm:grid-cols-2">
|
||||||
|
{WHY.map((item, i) =>
|
||||||
|
<Reveal key={item.title} delay={0.05 * i}>
|
||||||
|
<div className="flex h-full flex-col border-t border-ivory/10 py-8">
|
||||||
|
<span className="text-xs font-light text-copper">۰{(i + 1).toLocaleString('fa-IR')}</span>
|
||||||
|
<h3 className="mt-4 text-lg font-bold text-ivory">{item.title}</h3>
|
||||||
|
<p className="mt-3 text-sm font-light leading-8 text-ivory/55">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
export const BRAND = {
|
||||||
|
name: 'آرامش پلازا',
|
||||||
|
logo: 'https://i.postimg.cc/XYNrsHsM/Chat-GPT-Image-Aug-6-2026-04-34-46-PM.png',
|
||||||
|
hero: 'https://i.postimg.cc/J0bKgQVg/Gemini-Generated-Image-mdh969mdh969mdh9.png',
|
||||||
|
contactBg: 'https://i.postimg.cc/BbccNQHr/Gemini-Generated-Image-dafc2hdafc2hdafc.png',
|
||||||
|
phone: '۰۲۱ ۸۸ ۷۴ ۲۲ ۱۰',
|
||||||
|
phoneHref: 'tel:+982188742210',
|
||||||
|
email: 'info@arameshplaza.com',
|
||||||
|
address: 'تهران، خیابان ولیعصر، برج آرامش، طبقه هجدهم'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IMAGES = [
|
||||||
|
'https://i.postimg.cc/wTQ0QFkG/Gemini-Generated-Image-1tjszd1tjszd1tjs.png',
|
||||||
|
'https://i.postimg.cc/1zfBPS1G/Gemini-Generated-Image-at69yhat69yhat69.png',
|
||||||
|
'https://i.postimg.cc/BbccNQHr/Gemini-Generated-Image-dafc2hdafc2hdafc.png',
|
||||||
|
'https://i.postimg.cc/4xFvW0HX/Gemini-Generated-Image-n7mhonn7mhonn7mh.png',
|
||||||
|
'https://i.postimg.cc/N036cr4s/Gemini-Generated-Image-ytbhvvytbhvvytbh.png'];
|
||||||
|
|
||||||
|
|
||||||
|
export const NAV = [
|
||||||
|
{ label: 'صفحه اصلی', to: '/' },
|
||||||
|
{ label: 'درباره ما', to: '/about' },
|
||||||
|
{ label: 'خدمات', to: '/services' },
|
||||||
|
{ label: 'پروژهها', to: '/projects' },
|
||||||
|
{ label: 'سرمایهگذاری', to: '/investment' },
|
||||||
|
{ label: 'وبلاگ', to: '/blog' },
|
||||||
|
{ label: 'سوالات متداول', to: '/faq' },
|
||||||
|
{ label: 'تماس با ما', to: '/contact' }];
|
||||||
|
|
||||||
|
|
||||||
|
export type Service = {
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
icon: 'draft' | 'tower' | 'mall' | 'villa' | 'renew' | 'manage' | 'invest' | 'landscape';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SERVICES: Service[] = [
|
||||||
|
{
|
||||||
|
title: 'طراحی معماری',
|
||||||
|
desc: 'خلق فرمهای معاصر با نگاه ایرانی؛ از کانسپت اولیه تا نقشههای اجرایی و جزئیات نما.',
|
||||||
|
icon: 'draft'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ساخت برجهای مسکونی',
|
||||||
|
desc: 'اجرای برجهای بلندمرتبه با استانداردهای سازهای بینالمللی و کیفیت تحویل بینقص.',
|
||||||
|
icon: 'tower'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'مجتمعهای تجاری',
|
||||||
|
desc: 'طراحی و ساخت مراکز تجاری با مطالعه دقیق جریان مخاطب و بازدهی بلندمدت سرمایه.',
|
||||||
|
icon: 'mall'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ویلاسازی',
|
||||||
|
desc: 'ویلاهای اختصاصی همنشین با طبیعت؛ محرمانگی، نور طبیعی و مصالح ماندگار.',
|
||||||
|
icon: 'villa'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'بازسازی',
|
||||||
|
desc: 'بازآفرینی ساختمانهای موجود با ارتقای سازه، نما و تجربه زندگی در فضا.',
|
||||||
|
icon: 'renew'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'مدیریت پروژه',
|
||||||
|
desc: 'کنترل همزمان زمان، هزینه و کیفیت با گزارشدهی شفاف در تمام مراحل ساخت.',
|
||||||
|
icon: 'manage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'سرمایهگذاری ساختمانی',
|
||||||
|
desc: 'ساختارهای مشارکت شفاف، ارزیابی ریسک و طراحی سبد ملکی متناسب با سرمایه شما.',
|
||||||
|
icon: 'invest'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'محوطهسازی و لنداسکیپ',
|
||||||
|
desc: 'طراحی حیاط، بام سبز و مسیرهای دسترسی؛ ادامه معماری در فضای باز پروژه.',
|
||||||
|
icon: 'landscape'
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
export const STATS = [
|
||||||
|
{ value: 21, suffix: '+', label: 'سال تجربه ساخت' },
|
||||||
|
{ value: 47, suffix: '', label: 'پروژه تحویلشده' },
|
||||||
|
{ value: 860, suffix: 'هزار', label: 'مترمربع زیربنا' },
|
||||||
|
{ value: 1200, suffix: '+', label: 'خانواده ساکن' }];
|
||||||
|
|
||||||
|
|
||||||
|
export type ProjectStatus = 'done' | 'progress';
|
||||||
|
|
||||||
|
export type Project = {
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
location: string;
|
||||||
|
year: string;
|
||||||
|
status: ProjectStatus;
|
||||||
|
image: string;
|
||||||
|
gallery: string[];
|
||||||
|
area: string;
|
||||||
|
floors: string;
|
||||||
|
units: string;
|
||||||
|
client: string;
|
||||||
|
summary: string;
|
||||||
|
body: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const STATUS_LABEL: Record<ProjectStatus, string> = {
|
||||||
|
done: 'انجام شده',
|
||||||
|
progress: 'در حال انجام'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PROJECTS: Project[] = [
|
||||||
|
{
|
||||||
|
slug: 'aramesh-elahieh',
|
||||||
|
title: 'برج آرامش الهیه',
|
||||||
|
location: 'تهران، الهیه',
|
||||||
|
year: '۱۴۰۲',
|
||||||
|
status: 'done',
|
||||||
|
image: IMAGES[0],
|
||||||
|
gallery: [IMAGES[0], IMAGES[3], IMAGES[1]],
|
||||||
|
area: '۲۴٬۰۰۰ متر مربع',
|
||||||
|
floors: '۱۸ طبقه',
|
||||||
|
units: '۵۶ واحد',
|
||||||
|
client: 'سرمایهگذاری خصوصی',
|
||||||
|
summary:
|
||||||
|
'یک برج مسکونی لوکس با نمای سنگ تراورتن و پوسته دوپوستهای که مصرف انرژی را تا ۳۵ درصد کاهش داده است.',
|
||||||
|
body: [
|
||||||
|
'برج آرامش الهیه روی زمینی با شیب طبیعی شمالی طراحی شد؛ همین شیب فرصت داد تا لابی، استخر و فضاهای مشترک در سه تراز مختلف بنشینند و همه واحدها دید بیواسطه به شهر داشته باشند.',
|
||||||
|
'پوسته بیرونی ترکیبی از سنگ تراورتن روشن و پنجرههای قدی با شیشه سهجداره است. سایهبانهای عمودی مسیرنگ، نور مستقیم تابستان را میشکنند و در زمستان اجازه ورود کامل نور را میدهند.',
|
||||||
|
'اجرای پروژه در سیودو ماه به پایان رسید و تمام واحدها پیش از تحویل کلید به فروش رسیدند.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'parseh-mall',
|
||||||
|
title: 'مجتمع تجاری پارسه',
|
||||||
|
location: 'اصفهان، مرکز شهر',
|
||||||
|
year: '۱۴۰۱',
|
||||||
|
status: 'done',
|
||||||
|
image: IMAGES[1],
|
||||||
|
gallery: [IMAGES[1], IMAGES[2], IMAGES[4]],
|
||||||
|
area: '۳۸٬۵۰۰ متر مربع',
|
||||||
|
floors: '۷ طبقه',
|
||||||
|
units: '۱۴۰ واحد تجاری',
|
||||||
|
client: 'گروه سرمایهگذاری پارسه',
|
||||||
|
summary:
|
||||||
|
'مرکز تجاری با آتریوم مرکزی و نورگیر سقفی که جریان مخاطب را بهصورت طبیعی در همه ترازها توزیع میکند.',
|
||||||
|
body: [
|
||||||
|
'مطالعه رفتار مخاطب پیش از طراحی نشان داد کوتاهترین مسیر عمودی، کلید موفقیت تجاری این مجموعه است؛ بنابراین آتریوم مرکزی با پلهبرقیهای قطری در قلب پلان قرار گرفت.',
|
||||||
|
'نورگیر سقفی با شیشه لو-ای، روشنایی روز را تا تراز منفی دو میرساند و مصرف روشنایی مصنوعی را بهطور محسوس کاهش میدهد.',
|
||||||
|
'مجموعه دو ماه پیش از موعد قراردادی به بهرهبرداری رسید.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'daryakenar-villas',
|
||||||
|
title: 'ویلاهای دریاکنار',
|
||||||
|
location: 'مازندران، نوشهر',
|
||||||
|
year: '۱۴۰۰',
|
||||||
|
status: 'done',
|
||||||
|
image: IMAGES[2],
|
||||||
|
gallery: [IMAGES[2], IMAGES[4], IMAGES[0]],
|
||||||
|
area: '۹٬۲۰۰ متر مربع',
|
||||||
|
floors: '۲ طبقه',
|
||||||
|
units: '۱۲ ویلا',
|
||||||
|
client: 'مجموعه خصوصی دریاکنار',
|
||||||
|
summary:
|
||||||
|
'دوازده ویلای اختصاصی با چیدمان شانهای که برای هر واحد، دید مستقیم به دریا و محرمانگی کامل فراهم میکند.',
|
||||||
|
body: [
|
||||||
|
'چیدمان شانهای ویلاها نتیجه دهها بررسی زاویه دید بود؛ هیچ ویلایی در دید ویلای دیگر نیست و همه به دریا باز میشوند.',
|
||||||
|
'مصالح، پاسخی به اقلیم مرطوب شمال است: چوب ترموود، بتن اکسپوز و پوششهای ضد نمک برای فلزات نما.',
|
||||||
|
'محوطه با گیاهان بومی طراحی شد تا نیاز آبیاری در طول سال حداقل باشد.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'niavaran-twins',
|
||||||
|
title: 'برج دوقلوی نیاوران',
|
||||||
|
location: 'تهران، نیاوران',
|
||||||
|
year: '۱۳۹۹',
|
||||||
|
status: 'done',
|
||||||
|
image: IMAGES[3],
|
||||||
|
gallery: [IMAGES[3], IMAGES[0], IMAGES[2]],
|
||||||
|
area: '۳۱٬۷۰۰ متر مربع',
|
||||||
|
floors: '۲۰ طبقه',
|
||||||
|
units: '۸۰ واحد',
|
||||||
|
client: 'مشارکت با مالکان زمین',
|
||||||
|
summary: 'دو برج همسان با پادست مشترک که فضاهای رفاهی و پارکینگها را در یک تراز واحد ادغام میکند.',
|
||||||
|
body: [
|
||||||
|
'دو برج روی یک پادست مشترک نشستهاند؛ این تصمیم اجازه داد فضاهای رفاهی، سالن اجتماعات و پارکینگها بین دو ساختمان مشترک شوند و سطح اشغال باغ کاهش یابد.',
|
||||||
|
'سازه با سیستم دیوار برشی و دال پسکشیده اجرا شد تا دهانههای داخلی آزاد و پلانها قابل شخصیسازی باشند.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'kish-garden-tower',
|
||||||
|
title: 'باغبرج کیش',
|
||||||
|
location: 'کیش، مروارید',
|
||||||
|
year: '۱۴۰۵ (پیشبینی)',
|
||||||
|
status: 'progress',
|
||||||
|
image: IMAGES[4],
|
||||||
|
gallery: [IMAGES[4], IMAGES[1], IMAGES[3]],
|
||||||
|
area: '۱۸٬۴۰۰ متر مربع',
|
||||||
|
floors: '۱۴ طبقه',
|
||||||
|
units: '۶۲ واحد اقامتی',
|
||||||
|
client: 'صندوق پروژهمحور آرامش',
|
||||||
|
summary:
|
||||||
|
'باغبرجی اقامتی با تراسهای سبز پیوسته که در حال ساخت است؛ پیشرفت فیزیکی پروژه ۴۸ درصد.',
|
||||||
|
body: [
|
||||||
|
'تراسهای سبز پیوسته، پوستهای زنده برای ساختمان میسازند که در اقلیم گرم جزیره، دمای سطح نما را بهطور محسوس پایین میآورد.',
|
||||||
|
'در حال حاضر سازه تا تراز طبقه نهم اجرا شده و نصب نما از ضلع جنوبی آغاز شده است.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'aramesh-business-center',
|
||||||
|
title: 'مرکز تجاری آرامش تهران',
|
||||||
|
location: 'تهران، سعادتآباد',
|
||||||
|
year: '۱۴۰۶ (پیشبینی)',
|
||||||
|
status: 'progress',
|
||||||
|
image: IMAGES[1],
|
||||||
|
gallery: [IMAGES[1], IMAGES[3], IMAGES[4]],
|
||||||
|
area: '۲۷٬۳۰۰ متر مربع',
|
||||||
|
floors: '۱۲ طبقه',
|
||||||
|
units: '۹۰ دفتر اداری',
|
||||||
|
client: 'مشارکت با هلدینگ آرامش',
|
||||||
|
summary: 'مجتمع اداری-تجاری با هسته مرکزی شفاف و لابی دوبلکس؛ در مرحله اجرای سفتکاری.',
|
||||||
|
body: [
|
||||||
|
'برنامهریزی فضایی این مجموعه بر اساس دفاتر انعطافپذیر انجام شد؛ هر تراز میتواند به دو، سه یا شش دفتر مستقل تقسیم شود.',
|
||||||
|
'اجرای تأسیسات مکانیکی بهصورت همزمان با سفتکاری پیش میرود تا زمان کل پروژه کوتاهتر شود.']
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
export const BEFORE_AFTER = {
|
||||||
|
title: 'از کانسپت تا خلق واقعیت',
|
||||||
|
lead: 'دستگیره را بکشید و ببینید نمای ساختمانی چگونه از حالت اولیه به فرم مدرن تبدیل میشود.',
|
||||||
|
before: 'https://cdn.housedesign.ai/se/housedesign/assets/housedesign-home/compare/1_before.webp',
|
||||||
|
after: 'https://cdn.housedesign.ai/se/housedesign/assets/housedesign-home/compare/1_after.webp',
|
||||||
|
beforeLabel: 'کانسپت اولیه',
|
||||||
|
afterLabel: 'پروژه تکمیلشده',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TIMELINE = [
|
||||||
|
{
|
||||||
|
year: '۱۳۸۳',
|
||||||
|
title: 'آغاز مسیر',
|
||||||
|
desc: 'تأسیس دفتر معماری آرامش با تیمی چهارنفره و نخستین پروژه بازسازی در تهران.',
|
||||||
|
image: IMAGES[2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '۱۳۸۵',
|
||||||
|
title: 'نخستین ساخت مستقل',
|
||||||
|
desc: 'تحویل اولین ساختمان مسکونی هفتطبقه؛ آغاز همکاری با پیمانکاران سازهای بزرگ.',
|
||||||
|
image: IMAGES[0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '۱۳۸۷',
|
||||||
|
title: 'ورود به بازار تجاری',
|
||||||
|
desc: 'طراحی و اجرای اولین مجتمع تجاری با رویکرد بازدهی سرمایه و جریان مخاطب.',
|
||||||
|
image: IMAGES[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '۱۳۸۹',
|
||||||
|
title: 'استاندارد بینالمللی',
|
||||||
|
desc: 'استقرار نظام کنترل کیفیت و دریافت گواهینامههای مدیریت پروژه.',
|
||||||
|
image: IMAGES[4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '۱۳۹۵',
|
||||||
|
title: 'بلندمرتبهسازی',
|
||||||
|
desc: 'آغاز ساخت نخستین برج بیستطبقه شرکت در شمال تهران.',
|
||||||
|
image: IMAGES[3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: '۱۴۰۳',
|
||||||
|
title: 'آرامش پلازا امروز',
|
||||||
|
desc: 'پرتفوی ۴۷ پروژه، دفاتر در سه شهر و صندوق سرمایهگذاری ساختمانی اختصاصی.',
|
||||||
|
image: IMAGES[0]
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
export const WHY = [
|
||||||
|
{ title: 'کیفیت بدون مصالحه', desc: 'انتخاب مصالح و مجریان بر اساس ماندگاری چنددههای، نه سریعترین تحویل.' },
|
||||||
|
{ title: 'شفافیت مالی کامل', desc: 'گزارش ماهانه هزینهها و پیشرفت فیزیکی برای همه شرکای پروژه.' },
|
||||||
|
{ title: 'تحویل بهموقع', desc: '۹۴٪ پروژههای ما در بازه زمانی قراردادی یا زودتر تحویل داده شدهاند.' },
|
||||||
|
{ title: 'طراحی اختصاصی', desc: 'هیچ نقشهای دو بار اجرا نمیشود؛ هر پروژه هویت معماری خودش را دارد.' },
|
||||||
|
{ title: 'خدمات پس از تحویل', desc: 'پنج سال پشتیبانی فنی و نگهداری تأسیسات پس از تحویل کلید.' },
|
||||||
|
{ title: 'پایداری و انرژی', desc: 'طراحی پوسته و تأسیسات با هدف کاهش مصرف انرژی تا ۳۵ درصد.' }];
|
||||||
|
|
||||||
|
|
||||||
|
export const TESTIMONIALS = [
|
||||||
|
{
|
||||||
|
name: 'مهندس سارا کیانی',
|
||||||
|
role: 'سرمایهگذار پروژه پارسه',
|
||||||
|
quote:
|
||||||
|
'گزارشهای ماهانه آرامش پلازا آنقدر دقیق بود که هیچوقت نیاز نشد نگران سرمایهام باشم. پروژه دو ماه زودتر از موعد به بهرهبرداری رسید.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'دکتر امیرحسین راد',
|
||||||
|
role: 'ساکن برج الهیه',
|
||||||
|
quote:
|
||||||
|
'کیفیت اجرا در جزئیاتی دیده میشود که معمولاً کسی به آنها فکر نمیکند؛ از عایق صوتی پنجرهها تا نور غیرمستقیم راهروها.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'مریم دلاور',
|
||||||
|
role: 'مدیر مجموعه ویلاهای دریاکنار',
|
||||||
|
quote:
|
||||||
|
'همکاری با تیم طراحی، تجربهای واقعاً حرفهای بود. ایدههای ما را شنیدند و به زبان معماری ترجمه کردند.'
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
export const TEAM = [
|
||||||
|
{ name: 'کامران آرامش', role: 'بنیانگذار و مدیرعامل', bio: 'بیش از سه دهه تجربه در ساخت بلندمرتبه.' },
|
||||||
|
{ name: 'نگار شریفی', role: 'مدیر ارشد طراحی', bio: 'معمار ارشد، فارغالتحصیل دانشگاه تهران و میلان.' },
|
||||||
|
{ name: 'بهزاد مرادی', role: 'مدیر اجرایی پروژهها', bio: 'مسئول کنترل کیفیت و زمانبندی همه کارگاهها.' },
|
||||||
|
{ name: 'الهام نیکآیین', role: 'مدیر سرمایهگذاری', bio: 'طراح ساختارهای مشارکت و تحلیل بازدهی ملکی.' }];
|
||||||
|
|
||||||
|
|
||||||
|
export type Article = {
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
date: string;
|
||||||
|
category: string;
|
||||||
|
image: string;
|
||||||
|
read: string;
|
||||||
|
excerpt: string;
|
||||||
|
body: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ARTICLES: Article[] = [
|
||||||
|
{
|
||||||
|
slug: 'natural-light',
|
||||||
|
title: 'معماری آرام: چرا نور طبیعی گرانترین مصالح ساختمان است؟',
|
||||||
|
date: '۲۴ مرداد ۱۴۰۳',
|
||||||
|
category: 'معماری',
|
||||||
|
image: IMAGES[1],
|
||||||
|
read: '۶ دقیقه',
|
||||||
|
excerpt:
|
||||||
|
'نور طبیعی نهتنها کیفیت زندگی در فضا را تعیین میکند، بلکه مستقیماً بر مصرف انرژی و ارزش بلندمدت ملک اثر میگذارد.',
|
||||||
|
body: [
|
||||||
|
'در بازار ساختوساز، ارزش یک واحد اغلب با متراژ سنجیده میشود؛ اما تجربه ما در بیست سال گذشته نشان داده کیفیت نور، تعیینکنندهتر از چند متر اضافه است.',
|
||||||
|
'جهتگیری ساختمان، عمق دهانه و ارتفاع کف تا سقف، سه متغیر اصلی نور طبیعی هستند. تصمیم درباره هر سه، پیش از ترسیم اولین پلان گرفته میشود و بعد از آن تقریباً برگشتپذیر نیست.',
|
||||||
|
'در برج آرامش الهیه، چرخش ۱۲ درجهای توده ساختمان نسبت به شمال، دو ساعت نور مفید بیشتر در روزهای زمستان به واحدها اضافه کرد؛ تصمیمی بیهزینه با اثری چنددههای.',
|
||||||
|
'برای انتخاب یک واحد، پیشنهاد ما ساده است: بازدید را در ساعتهای میانی صبح و اواخر بعدازظهر انجام دهید و کیفیت نور را در همان ساعتهایی بسنجید که واقعاً در خانه هستید.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'real-estate-portfolio',
|
||||||
|
title: 'راهنمای انتخاب سبد سرمایهگذاری ملکی در بازار امروز',
|
||||||
|
date: '۱۰ مرداد ۱۴۰۳',
|
||||||
|
category: 'سرمایهگذاری',
|
||||||
|
image: IMAGES[3],
|
||||||
|
read: '۹ دقیقه',
|
||||||
|
excerpt: 'سه معیار کلیدی برای تشکیل سبدی متعادل از دارایی ملکی، بر اساس افق زمانی و سطح ریسک شما.',
|
||||||
|
body: [
|
||||||
|
'سبد ملکی متعادل، ترکیبی از داراییهای با نقدشوندگی متفاوت است؛ نه همه سرمایه در یک پروژه بلندمدت، نه همه در واحدهای آماده.',
|
||||||
|
'افق زمانی، نخستین معیار است. اگر برنامه شما کمتر از دو سال است، پیشخرید در پروژهای که تازه شروع شده انتخاب پرریسکی خواهد بود.',
|
||||||
|
'معیار دوم شفافیت گزارشدهی است. بدون گزارش ماهانه پیشرفت فیزیکی و مالی، ارزیابی وضعیت سرمایه شما عملاً غیرممکن است.',
|
||||||
|
'معیار سوم، تنوع کاربری است. ترکیب مسکونی و تجاری، نوسان بازدهی سبد را در دورههای رکود محسوس کاهش میدهد.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'facade-quality',
|
||||||
|
title: 'پنج نکته فنی که کیفیت نما را پس از ده سال مشخص میکند',
|
||||||
|
date: '۲۸ تیر ۱۴۰۳',
|
||||||
|
category: 'اجرا',
|
||||||
|
image: IMAGES[4],
|
||||||
|
read: '۵ دقیقه',
|
||||||
|
excerpt: 'نمای ساختمان در سال اول زیبا به نظر میرسد؛ تفاوت کیفیت اجرا در سال دهم آشکار میشود.',
|
||||||
|
body: [
|
||||||
|
'اتصالات مکانیکی سنگ نما، مهمتر از خود سنگ است. اتصال چسبی در اقلیم سردوگرم ایران عمر کوتاهی دارد.',
|
||||||
|
'درزهای انقباضی باید در نقشه دیده شوند، نه در کارگاه تصمیمگیری شوند؛ نبودشان اولین دلیل ترکهای افقی نما است.',
|
||||||
|
'آببندی دور پنجره، پرتکرارترین نقطه شکست است. اجرای نوار آببند پیش از نصب قرنیز، این ریسک را حذف میکند.',
|
||||||
|
'زیرسازی گالوانیزه گرم، در برابر زیرسازی رنگشده، تفاوت قیمت اندکی دارد و تفاوت عمر بسیار زیادی.',
|
||||||
|
'در پایان، برنامه بازدید دورهای سالانه نما را جدی بگیرید؛ ترمیم یک درز، هزینهای در حد هیچ در برابر بازسازی کل نما است.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'commercial-yield',
|
||||||
|
title: 'چگونه بازدهی یک پروژه تجاری را پیش از ساخت تخمین بزنیم؟',
|
||||||
|
date: '۱۵ تیر ۱۴۰۳',
|
||||||
|
category: 'سرمایهگذاری',
|
||||||
|
image: IMAGES[2],
|
||||||
|
read: '۷ دقیقه',
|
||||||
|
excerpt: 'مدل سادهای برای برآورد بازدهی، بر پایه جریان مخاطب، ترکیب واحدها و نرخ اجاره منطقه.',
|
||||||
|
body: [
|
||||||
|
'برآورد بازدهی تجاری با سه عدد آغاز میشود: جریان روزانه مخاطب، نرخ اجاره هر مترمربع در منطقه و نسبت سطح مفید به کل.',
|
||||||
|
'خطای رایج، نادیدهگرفتن ترازهای بالاتر است. بدون طراحی درست مسیر عمودی، ترازهای سه به بالا معمولاً نیمی از نرخ تراز همکف را هم بهدست نمیآورند.',
|
||||||
|
'در نهایت، سناریوی محافظهکارانه را هم بنویسید؛ اگر پروژه در سناریوی بدبینانه هم توجیه داشت، سرمایهگذاری منطقی است.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'facade-materials',
|
||||||
|
title: 'مصالح نما: مقایسه سنگ، کامپوزیت و ترموود در اقلیم ایران',
|
||||||
|
date: '۲ تیر ۱۴۰۳',
|
||||||
|
category: 'اجرا',
|
||||||
|
image: IMAGES[0],
|
||||||
|
read: '۸ دقیقه',
|
||||||
|
excerpt: 'هر مصالح نما در اقلیم متفاوتی درست کار میکند؛ انتخاب اشتباه، هزینه نگهداری را چند برابر میکند.',
|
||||||
|
body: [
|
||||||
|
'سنگ در اقلیم خشک، بهترین ماندگاری را دارد؛ اما وزن آن نیازمند زیرسازی دقیق و محاسبه سازهای است.',
|
||||||
|
'کامپوزیت انتخاب سریع و سبکی است، مشروط بر انتخاب گرید ضد آتش و درنظرگرفتن انقباض حرارتی در طول ورق.',
|
||||||
|
'ترموود در اقلیم مرطوب شمال، بهترین پاسخ زیباییشناسانه است، ولی برنامه نگهداری دوسالانه دارد.']
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'renovate-or-rebuild',
|
||||||
|
title: 'بازسازی یا نوسازی؟ معیارهای تصمیمگیری برای مالکان',
|
||||||
|
date: '۲۰ خرداد ۱۴۰۳',
|
||||||
|
category: 'معماری',
|
||||||
|
image: IMAGES[1],
|
||||||
|
read: '۶ دقیقه',
|
||||||
|
excerpt: 'اگر عمر سازه و ضوابط شهری اجازه دهند، بازسازی میتواند بازدهی بالاتری از تخریب کامل داشته باشد.',
|
||||||
|
body: [
|
||||||
|
'نخست وضعیت سازه ارزیابی میشود؛ اگر مقاومسازی هزینهای بیش از ۴۰ درصد ساخت نو دارد، نوسازی منطقیتر است.',
|
||||||
|
'دومین معیار، ضوابط شهری روز است. گاهی تخریب، سطح زیربنای مجاز کمتری به شما میدهد و این تصمیم را کاملاً تغییر میدهد.',
|
||||||
|
'در بازسازی، بیشترین ارزش افزوده از سه جا میآید: نما، ورودی و بازآرایی پلان داخلی.']
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
export const FAQS = [
|
||||||
|
{
|
||||||
|
q: 'حداقل سرمایه برای مشارکت در پروژههای آرامش پلازا چقدر است؟',
|
||||||
|
a: 'ساختار مشارکت هر پروژه متفاوت است، اما معمولاً واحدهای سرمایهگذاری از سطح مشخصی آغاز میشوند و جزئیات دقیق در جلسه ارزیابی اولیه ارائه میشود.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'مدت زمان معمول ساخت یک برج مسکونی چقدر است؟',
|
||||||
|
a: 'بسته به تعداد طبقات و شرایط زمین، میانگین اجرای یک برج مسکونی بین ۲۸ تا ۴۲ ماه از زمان صدور پروانه تا تحویل کلید است.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'آیا امکان تغییر نقشه داخلی واحد وجود دارد؟',
|
||||||
|
a: 'بله. تا پیش از پایان مرحله سفتکاری، امکان شخصیسازی پلان داخلی با هماهنگی تیم طراحی و تأیید سازه فراهم است.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'خدمات پس از تحویل شامل چه مواردی میشود؟',
|
||||||
|
a: 'پنج سال پشتیبانی فنی تأسیسات مکانیکی و برقی، بازدید دورهای سالانه و تضمین اجرای نما و عایقکاری.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'پروژهها در چه شهرهایی اجرا میشوند؟',
|
||||||
|
a: 'در حال حاضر تهران، اصفهان و سواحل شمالی کشور مناطق اصلی فعالیت ما هستند و پروژهای نیز در جزیره کیش در دست ساخت است.'
|
||||||
|
}];
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* @import url() FONT IMPORTS MUST ALWAYS BE AT THE VERY TOP OF THIS FILE, ABOVE THE TAILWIND IMPORTS — DO NOT DELETE THIS COMMENT */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100..900&display=swap');
|
||||||
|
|
||||||
|
/* CRITICAL: THE FOLLOWING TAILWIND IMPORTS MUST NEVER BE DELETED OR REORDERED — DO NOT DELETE THIS COMMENT */
|
||||||
|
@import 'tailwindcss/base';
|
||||||
|
@import 'tailwindcss/components';
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
|
||||||
|
/* END TAILWIND IMPORTS — ALL OTHER CSS MUST GO BELOW THIS LINE */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Pinar';
|
||||||
|
src: url('https://cdn.fontcdn.ir/Font/Persian/Pinar/Pinar.woff2') format('woff2'),
|
||||||
|
url('https://cdn.fontcdn.ir/Font/Persian/Pinar/Pinar.woff') format('woff');
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Pinar';
|
||||||
|
src: url('https://cdn.fontcdn.ir/Font/Persian/Pinar/Pinar-Bold.woff2') format('woff2'),
|
||||||
|
url('https://cdn.fontcdn.ir/Font/Persian/Pinar/Pinar-Bold.woff') format('woff');
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #27221c;
|
||||||
|
color: #f5f3ef;
|
||||||
|
font-family: 'Pinar', 'Vazirmatn', Tahoma, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
::selection {
|
||||||
|
background-color: #d5874c;
|
||||||
|
color: #1e1a16;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import "./index.css";
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import { App } from "./App";
|
||||||
|
|
||||||
|
const rootEl = document.getElementById("root");
|
||||||
|
if (rootEl) {
|
||||||
|
ReactDOM.createRoot(rootEl).render(<App />);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "magic-patterns-project",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-router-dom": "6.30.2",
|
||||||
|
"lucide-react": "0.577.0",
|
||||||
|
"framer-motion": "11.18.2",
|
||||||
|
"@radix-ui/react-icons": "1.3.2",
|
||||||
|
"date-fns": "4.1.0",
|
||||||
|
"tailwind-merge": "2.6.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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 />
|
||||||
|
<Stats />
|
||||||
|
<Timeline />
|
||||||
|
<WhyUs />
|
||||||
|
<Team />
|
||||||
|
<ContactSection />
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
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 { ARTICLES } from '../data/site';
|
||||||
|
|
||||||
|
export function ArticleDetail() {
|
||||||
|
const { slug } = useParams<{slug: string;}>();
|
||||||
|
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,72 @@
|
|||||||
|
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 { ARTICLES, IMAGES } from '../data/site';
|
||||||
|
|
||||||
|
export function Blog() {
|
||||||
|
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-5 text-2xl font-bold leading-[1.7] text-ivory transition-colors duration-200 ease-lux group-hover:text-copper md:text-3xl">
|
||||||
|
{featured.title}
|
||||||
|
</h2>
|
||||||
|
<p className="mt-5 text-sm font-light leading-9 text-ivory/65">{featured.excerpt}</p>
|
||||||
|
<div className="mt-8 flex items-center justify-between border-t border-ivory/10 pt-6 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,41 @@
|
|||||||
|
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[3]} />
|
||||||
|
|
||||||
|
|
||||||
|
<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 p-8 text-center">
|
||||||
|
<h2 className="text-xl font-bold text-copper">{office.city}</h2>
|
||||||
|
<p className="mt-4 text-sm font-light leading-8 text-ivory/60">{office.address}</p>
|
||||||
|
<p className="mt-auto border-t border-ivory/10 pt-5 text-xs font-light text-ivory/45">
|
||||||
|
{office.hours}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</Reveal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<ContactSection />
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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 />
|
||||||
|
<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,68 @@
|
|||||||
|
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="flex h-full flex-col border border-ivory/10 bg-char p-8 transition-[transform,box-shadow,border-color] duration-300 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-lift">
|
||||||
|
<p className="text-xs font-light text-copper">۰{(i + 1).toLocaleString('fa-IR')}</p>
|
||||||
|
<h3 className="mt-4 text-xl font-bold text-ivory">{model.title}</h3>
|
||||||
|
<p className="mt-4 text-sm font-light leading-8 text-ivory/60">{model.desc}</p>
|
||||||
|
<ul className="mt-auto space-y-3 border-t border-ivory/10 pt-6">
|
||||||
|
{model.points.map((point) =>
|
||||||
|
<li key={point} className="flex items-start gap-3 text-sm font-light text-ivory/60">
|
||||||
|
<span className="mt-2 h-1 w-1 shrink-0 rounded-full bg-copper" aria-hidden="true" />
|
||||||
|
{point}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</Reveal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Stats />
|
||||||
|
<ContactSection />
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
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 { PROJECTS, STATUS_LABEL } from '../data/site'
|
||||||
|
|
||||||
|
export function ProjectDetail() {
|
||||||
|
const { slug } = useParams<{ slug: string }>()
|
||||||
|
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-5">
|
||||||
|
<dt className="text-xs font-light text-ivory/50">{f.label}</dt>
|
||||||
|
<dd className="mt-1 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,69 @@
|
|||||||
|
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, PROJECTS, type ProjectStatus } from '../data/site';
|
||||||
|
|
||||||
|
const FILTERS: {key: ProjectStatus;label: string;}[] = [
|
||||||
|
{ key: 'done', label: 'انجام شده' },
|
||||||
|
{ key: 'progress', label: 'در حال انجام' }];
|
||||||
|
|
||||||
|
|
||||||
|
export function Projects() {
|
||||||
|
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,48 @@
|
|||||||
|
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 bg-char p-8 text-center">
|
||||||
|
<p className="text-3xl font-bold text-copper">{item.step}</p>
|
||||||
|
<h3 className="mt-5 text-lg font-bold text-ivory">{item.title}</h3>
|
||||||
|
<p className="mt-3 text-sm font-light leading-8 text-ivory/55">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
</Reveal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Faq />
|
||||||
|
<ContactSection />
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export default {
|
||||||
|
content: ['./**/*.{js,jsx,ts,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
ink: '#27221c',
|
||||||
|
char: '#1e1a16',
|
||||||
|
copper: '#d5874c',
|
||||||
|
ivory: '#F5F3EF',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
pinar: ['Pinar', 'Vazirmatn', 'Tahoma', 'sans-serif'],
|
||||||
|
},
|
||||||
|
transitionTimingFunction: {
|
||||||
|
lux: 'cubic-bezier(0.23, 1, 0.32, 1)',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
lift: '0 24px 50px -18px rgba(0, 0, 0, 0.75)',
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
// سایز این بخش یک درجه کوچکتر شد (حداکثر سایز از 4rem به 3.4rem کاهش یافت)
|
||||||
|
hero: ['clamp(1.75rem, 3.6vw, 3.4rem)', { lineHeight: '1.35' }],
|
||||||
|
section: ['clamp(1.6rem, 2.4vw, 2.4rem)', { lineHeight: '1.4' }],
|
||||||
|
},
|
||||||
|
maxWidth: {
|
||||||
|
site: '1440px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user