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