213 lines
15 KiB
TypeScript
213 lines
15 KiB
TypeScript
"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: "/projects" },
|
||
{ label: "آتلیه رئوفی", href: "/atelier" },
|
||
{ label: "رئوفی هوم", href: "/raoofi-home" },
|
||
{ 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="/projects">کشف پروژهها</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>;
|
||
}
|