feat: redesign site pages and connect contact API

This commit is contained in:
amirhosein.ashourloo
2026-09-22 02:00:26 +03:30
parent 6b3e87a09d
commit 55d0fad2c8
92 changed files with 611 additions and 224 deletions
+212
View File
@@ -0,0 +1,212 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import type { Swiper as SwiperInstance } from "swiper";
import "swiper/css";
import logo from "@/assets/images/brand/logo.png";
import { contact } from "@/data/site";
import "./raoofi-home.css";
const asset = (name: string) => `/raoofi-media/${name}`;
const stories: { title: string; image: string; href: string }[] = [
{ title: "گشایش فصل تازه‌ای از زندگی در الیزیوم", image: asset("news-1.jpg"), href: "/news-and-blog" },
{ title: "بیست و پنج سال خلق فضاهای ماندگار", image: asset("news-2.jpg"), href: "/aboutus" },
{ title: "آغاز مسیری تازه برای پروژه حیات", image: asset("news-3.jpg"), href: "/news-and-blog" },
];
const projects: { title: string; image: string; subtitle: string }[] = [
{ title: "بازآفرینی افق‌ها", subtitle: "الیزیوم", image: asset("project-1.webp") },
{ title: "زندگی در میان شاهکارها", subtitle: "اونیکس", image: asset("project-2.webp") },
{ title: "نگاهی تازه به معماری", subtitle: "حیات", image: asset("project-3.webp") },
];
const partners = [
{ name: "زاها حدید", latin: "Zaha Hadid", image: asset("partner-zaha.webp") },
{ name: "فاستر و همکاران", latin: "Foster + Partners", image: asset("partner-foster.webp") },
{ name: "ژیل و بواسیه", latin: "Gilles & Boissier", image: asset("partner-gilles.webp") },
{ name: "یودیزین", latin: "YODEZEEN", image: asset("partner-yodezeen.jpg") },
{ name: "ولادیمیر جوروویچ", latin: "Vladimir Djurovic", image: asset("partner-vladimir.webp") },
{ name: "دورچستر کالکشن", latin: "Dorchester Collection", image: asset("partner-dorchester.webp") },
];
const menu = [
{ label: "صفحه اصلی", href: "/" },
{ label: "پروژه‌ها", href: "/portfolios" },
{ label: "خدمات ما", href: "/our-services" },
{ label: "آتلیه رئوفی", href: "/atelier" },
{ label: "داستان ما", href: "/aboutus" },
{ label: "اخبار", href: "/news-and-blog" },
{ label: "تماس با ما", href: "/contactus" },
];
function ArrowLink({ href, children }: { href: string; children: React.ReactNode }) {
return <Link className="om-link" href={href}><span>{children}</span><span aria-hidden="true">←</span></Link>;
}
export function RaoofiHome() {
const [menuOpen, setMenuOpen] = useState(false);
const [story, setStory] = useState(0);
const [scrolled, setScrolled] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
const newsRef = useRef<HTMLElement>(null);
const partnerSwiperRef = useRef<SwiperInstance | null>(null);
const partnerProgressRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 40);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
useEffect(() => {
const timer = window.setInterval(() => setStory((value) => (value + 1) % stories.length), 6500);
return () => window.clearInterval(timer);
}, []);
useEffect(() => {
document.body.style.overflow = menuOpen ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [menuOpen]);
useEffect(() => {
const root = rootRef.current;
const news = newsRef.current;
if (!root || !news) return;
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
root.classList.add("om-motion-ready");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
});
}, { threshold: 0.13, rootMargin: "0px 0px -35px 0px" });
root.querySelectorAll(".om-reveal").forEach((element) => observer.observe(element));
let raf = 0;
const updateScroll = () => {
raf = 0;
const rect = news.getBoundingClientRect();
const range = Math.max(1, news.offsetHeight - window.innerHeight);
const progress = reduceMotion.matches ? 1 : Math.min(1, Math.max(0, -rect.top / range));
news.style.setProperty("--grow", progress.toFixed(3));
root.querySelectorAll<HTMLElement>(".om-project, .om-retreat").forEach((element) => {
const box = element.getBoundingClientRect();
const distance = (window.innerHeight / 2 - box.top - box.height / 2) / window.innerHeight;
element.style.setProperty("--parallax", `${Math.max(-36, Math.min(36, distance * 38)).toFixed(1)}px`);
});
};
const requestUpdate = () => { if (!raf) raf = window.requestAnimationFrame(updateScroll); };
updateScroll();
window.addEventListener("scroll", requestUpdate, { passive: true });
window.addEventListener("resize", requestUpdate);
return () => {
observer.disconnect();
window.removeEventListener("scroll", requestUpdate);
window.removeEventListener("resize", requestUpdate);
if (raf) window.cancelAnimationFrame(raf);
};
}, []);
const syncPartnerProgress = (swiper: SwiperInstance) => {
const bar = partnerProgressRef.current;
if (!bar) return;
const index = swiper.realIndex;
const previous = Number(bar.dataset.index || 0);
bar.style.transitionDuration = Math.abs(index - previous) === partners.length - 1 ? "0ms" : `${swiper.params.speed}ms`;
bar.style.transform = `translateX(${index * 100}%)`;
bar.dataset.index = String(index);
};
return <div className="om-home" dir="rtl" ref={rootRef}>
<header className={`om-header ${scrolled ? "om-header-scrolled" : ""}`}>
<button className="om-menu-toggle" type="button" onClick={() => setMenuOpen(true)} aria-label="باز کردن فهرست"><span/><span/></button>
<Link href="/" className="om-brand" aria-label="رئوفی، صفحه اصلی"><Image src={logo} alt="رئوفی" priority /></Link>
<Link href="/contactus" className="om-contact">تماس با ما</Link>
</header>
<div className={`om-drawer ${menuOpen ? "is-open" : ""}`} aria-hidden={!menuOpen}>
<button className="om-drawer-close" onClick={() => setMenuOpen(false)} aria-label="بستن فهرست">×</button>
<nav aria-label="فهرست اصلی">{menu.map((item, index) => <Link key={item.href} href={item.href} onClick={() => setMenuOpen(false)} tabIndex={menuOpen ? 0 : -1}><small>{String(index + 1).padStart(2, "0")}</small>{item.label}<span>←</span></Link>)}</nav>
<span className="om-drawer-bottom">هنرِ ساختنِ زندگی</span>
</div>
<main>
<section className="om-hero">
<video className="om-hero-video" autoPlay muted loop playsInline preload="metadata" poster={asset("hero.jpg")} aria-label="ویدیوی معماری آغاز صفحه">
<source src={asset("hero-video-mobile.mp4")} media="(max-width: 700px)" type="video/mp4" />
<source src={asset("hero-video.mp4")} type="video/mp4" />
</video>
<div className="om-hero-shade" />
<h1>هنرِ تعالی</h1>
<span className="om-hero-bottom">گروه ساختمانی رئوفی <i /> برای کشف، پیمایش کنید</span>
</section>
<section className="om-intro om-container om-reveal">
<p className="om-eyebrow">رئوفی</p>
<h2>هنرِ تعالی</h2>
<p className="om-intro-copy">فراتر از ساختن، فراتر از مکان. جایی که مهارت بی‌همتا، ظرافت و نوآوری را به تجربه‌ای تازه از زندگی بدل می‌کند. ما ناممکن را تصور می‌کنیم و آن را می‌سازیم.</p>
<ArrowLink href="/aboutus">داستان ما را ببینید</ArrowLink>
</section>
<section className="om-news" aria-label="تازه‌ترین‌ها" ref={newsRef}>
<div className="om-news-frame">
{stories.map((item, index) => <div className={`om-news-slide ${story === index ? "is-active" : ""}`} key={item.title}>
<Image src={item.image} alt="" fill sizes="(max-width: 700px) 100vw, 90vw" />
<div className="om-news-overlay"><small>تازه‌ترین‌ها / {String(index + 1).padStart(2, "0")}</small><h3>{item.title}</h3><ArrowLink href={item.href}>بیشتر بدانید</ArrowLink></div>
</div>)}
<div className="om-news-controls"><div className="om-progress">{stories.map((_, i) => <button key={i} className={story === i ? "is-active" : ""} onClick={() => setStory(i)} aria-label={`نمایش خبر ${i + 1}`} />)}</div><button onClick={() => setStory((story + stories.length - 1) % stories.length)} aria-label="خبر قبلی">←</button><button onClick={() => setStory((story + 1) % stories.length)} aria-label="خبر بعدی">→</button></div>
</div>
</section>
<section className="om-section-lead om-container om-reveal"><p className="om-eyebrow">شاهکارها</p><h2>خلق آثارِ زندهٔ هنری</h2><p>هنر تنها نشانی از زیبایی نیست؛ بیانی از یگانگی، خلاقیت و کیفیت است. هر پروژهٔ ما میراثی برای آینده خواهد بود.</p></section>
<section className="om-projects">{projects.map((project, index) => <article className={`om-project om-project-${index + 1}`} key={project.title}>
<Image src={project.image} alt={project.subtitle} fill sizes="100vw" />
<div className="om-project-shade"/><div className="om-project-content om-reveal"><small>پروژه‌های رئوفی / {String(index + 1).padStart(2, "0")}</small><h3>{project.title}</h3><span>{project.subtitle}</span></div>
</article>)}</section>
<section className="om-retreat"><Image src={asset("project-4.webp")} alt="معماری مسکونی" fill sizes="100vw" /><div className="om-retreat-inner"><p className="om-eyebrow">مجموعه‌ای متمایز</p><h2>آرامشی در دلِ شهر</h2><ArrowLink href="/portfolios">کشف پروژه‌ها</ArrowLink></div></section>
<section className="om-partners">
<div className="om-partners-intro om-container om-reveal"><p className="om-eyebrow">همکاران</p><h2>همراهی با صاحبان اندیشه</h2><p>خلاقیت بی‌مرز با پیشگامان این صنعت پیوند می‌خورد و روایت‌هایی تازه در طراحی و زندگی می‌آفریند.</p><a className="om-video-link" href="https://www.youtube.com/watch?v=fy8VlBj1pAU" target="_blank" rel="noreferrer">تماشای ویدیو ↗</a></div>
<div className="om-partner-carousel om-reveal">
<Swiper className="om-partner-swiper" dir="ltr" slidesPerView="auto" spaceBetween={30} loop speed={650} grabCursor simulateTouch threshold={4} onSwiper={(swiper) => { partnerSwiperRef.current = swiper; syncPartnerProgress(swiper); }} onSlideChange={syncPartnerProgress} breakpoints={{ 0: { spaceBetween: 16 }, 701: { spaceBetween: 30 } }}>
{partners.map((item) => <SwiperSlide className="om-partner-card" key={item.name}><article><div className="om-partner-image"><Image src={item.image} alt={item.latin} fill sizes="(max-width: 700px) 85vw, 50vw" draggable={false} /></div><div className="om-partner-caption"><h3>{item.name}</h3><span>{item.latin}</span></div></article></SwiperSlide>)}
</Swiper>
<div className="om-partner-controls"><div className="om-partner-progress"><span ref={partnerProgressRef} data-index="0" /></div><button type="button" onClick={() => partnerSwiperRef.current?.slidePrev()} aria-label="همکار قبلی">←</button><button type="button" onClick={() => partnerSwiperRef.current?.slideNext()} aria-label="همکار بعدی">→</button></div>
</div>
</section>
<section className="om-atelier"><Image src={asset("atelier.png")} alt="فضای طراحی" fill sizes="100vw" /><div className="om-atelier-card"><p className="om-eyebrow">آتلیه رئوفی</p><h2>تصورِ شما، هنرِ ما</h2><p>هر فضا با نگاهی شخصی آغاز می‌شود و با وسواس در جزئیات، به اثری ماندگار بدل می‌گردد.</p></div></section>
<section className="om-story om-container om-reveal">
<div className="om-story-intro">
<p className="om-eyebrow">داستان ما</p>
<h2>پیوندِ مکان<br />و معنا</h2>
<p>با اشتیاق به معماری، مرزهای ممکن را جابه‌جا می‌کنیم و فضاهایی می‌سازیم که افق شهر و کیفیت زندگی را دگرگون می‌کنند.</p>
</div>
<div className="om-stats" aria-label="آمار گروه ساختمانی رئوفی">
<div><strong>۲۵</strong><span>سال تجربه</span></div>
<div><strong>۱۵</strong><span>پروژهٔ شاخص</span></div>
<div><strong>۰۶</strong><span>حوزهٔ تخصصی</span></div>
</div>
<Link className="om-story-cta" href="/aboutus">داستان ما را کشف کنید</Link>
</section>
<section className="om-featured-news om-container om-reveal"><Image src={asset("news-3.jpg")} alt="تازه‌ترین خبر معماری" fill sizes="(max-width: 700px) 100vw, 90vw" /><div><small>تازه‌ترین خبر</small><h2>روایتی تازه از معماری و زندگی</h2><ArrowLink href="/news-and-blog">مطالعهٔ خبر</ArrowLink></div></section>
<section className="om-final"><p className="om-eyebrow">با هم گفت‌وگو کنیم</p><h2>ناممکن را کشف کنید</h2><p>برای آشنایی بیشتر با پروژه‌ها و فرصت‌های همکاری، با ما در تماس باشید.</p><ArrowLink href="/contactus">درخواست تماس</ArrowLink></section>
</main>
<footer className="om-footer"><div className="om-footer-main"><div className="om-footer-brand">رئوفی<small>هنرِ ساختنِ زندگی</small></div><div><h3>کاوش</h3>{menu.slice(1,5).map(item => <Link href={item.href} key={item.href}>{item.label}</Link>)}</div><div><h3>ارتباط</h3><Link href="/contactus">تماس با ما</Link><a href={`tel:${contact.phoneLinks[0]}`}>{contact.phones[0]}</a><a href={`mailto:${contact.email}`}>{contact.email}</a></div><div><h3>نشانی</h3><p>{contact.address}</p></div></div><div className="om-footer-bottom"><span>© {new Date().getFullYear()} گروه ساختمانی رئوفی</span><a href={contact.instagramUrl}>اینستاگرام ↗</a></div></footer>
</div>;
}