Files
raoufi-group/src/components/home/RaoofiHome.tsx
T

213 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>;
}