300 lines
11 KiB
TypeScript
300 lines
11 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
|
|
import { DetailsFrame } from "@/components/DetailsFrame";
|
|
import { HeroSlider } from "@/components/HeroSlider";
|
|
import { InstagramFollow } from "@/components/InstagramFollow";
|
|
import { OpeningFrame } from "@/components/OpeningFrame";
|
|
import { SectionTitle } from "@/components/SectionTitle";
|
|
import { SlotImage } from "@/components/SlotImage";
|
|
import { gallery, processSteps, projects, services, site, stats } from "@/data/site";
|
|
import { resolveAbout, resolveInstagram, resolveLogo, resolveName } from "@/lib/content";
|
|
import { getBusinessInfo } from "@/lib/meshkee";
|
|
import { getPageSlots, slotImages, slotRatio } from "@/lib/static-images";
|
|
|
|
export const metadata: Metadata = {
|
|
description:
|
|
"استودیو معماری هوم؛ طراحی معماری، طراحی داخلی، طراحی نما و اجرای پروژههای مسکونی و تجاری با نگاهی مینیمال و دقیق.",
|
|
};
|
|
|
|
const ArrowIcon = () => (
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
|
|
<path d="M4 12h16M13 5l7 7-7 7" />
|
|
</svg>
|
|
);
|
|
|
|
export default async function HomePage() {
|
|
const [info, slots] = await Promise.all([getBusinessInfo(), getPageSlots("home")]);
|
|
const name = resolveName(info);
|
|
const about = resolveAbout(info);
|
|
const logo = resolveLogo(info);
|
|
const instagram = resolveInstagram(info);
|
|
|
|
const heroSlides = slotImages("slider", slots, gallery.heroSlides);
|
|
const categoryImages = slotImages(
|
|
"four-category",
|
|
slots,
|
|
services.map((service) => ({
|
|
url: service.image,
|
|
titleFa: service.titleFa,
|
|
titleEn: service.titleEn,
|
|
subtext: service.text,
|
|
linkUrl: `/services/${service.slug}`,
|
|
})),
|
|
);
|
|
const projectSlots = slotImages(
|
|
"project-images",
|
|
slots,
|
|
projects.map((project) => ({
|
|
url: project.image,
|
|
titleFa: project.titleFa,
|
|
titleEn: project.titleEn,
|
|
subtext: `${project.category} · ${project.year}`,
|
|
linkUrl: `/portfolio/${project.slug}`,
|
|
})),
|
|
);
|
|
const sticky = slotImages("sticky-frame-bg", slots, [
|
|
{ url: gallery.studio, titleFa: "ورودی سبز", titleEn: "Studio", subtext: about[2] ?? site.about[2] },
|
|
])[0];
|
|
const openingImage = sticky.url || gallery.studio;
|
|
const instagramImage = slotImages("instagram-row", slots, [
|
|
{
|
|
url: gallery.instagram,
|
|
titleFa: "ما را در اینستاگرام دنبال کنید",
|
|
titleEn: "Follow us",
|
|
subtext: "پروژههای تازه و جزئیات اجرا را در صفحه اینستاگرام ببینید.",
|
|
linkUrl: instagram.href,
|
|
},
|
|
])[0];
|
|
const instagramRatio = slotRatio("instagram-row", slots);
|
|
const brandLogos = slotImages("brands", slots, gallery.brands);
|
|
const brandRatio = slotRatio("brands", slots);
|
|
|
|
return (
|
|
<>
|
|
<HeroSlider
|
|
slides={heroSlides}
|
|
title="معماری ساده،"
|
|
titleLine2="زندگی روشن"
|
|
lead={`${name} یک گروه معماری و طراحی داخلی است؛ از طراحی نما و پلان تا جزئیات داخلی و اجرای نهایی.`}
|
|
ctaLabel="شروع یک پروژه"
|
|
ctaHref="/contact"
|
|
/>
|
|
|
|
{/* Intro — RTL: logo (rightmost) · copy · focus (left) */}
|
|
<section className="section">
|
|
<div className="container intro">
|
|
<div className="intro__brand">
|
|
<img src={logo} alt={name} className="intro__logo" width={480} height={186} />
|
|
</div>
|
|
<div className="intro__copy">
|
|
<SectionTitle en="studio" title={about[0]} className="section-title--compact section-title--story" />
|
|
</div>
|
|
<div className="intro__meta">
|
|
<p>{about[1] ?? site.about[1]}</p>
|
|
<div>
|
|
<strong>تمرکز ما</strong>
|
|
<span>معماری مسکونی، طراحی داخلی، طراحی نما</span>
|
|
</div>
|
|
<Link href="/about" className="text-link">
|
|
بیشتر بدانید
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* four-category — 4 service panels */}
|
|
<section className="section section--tight">
|
|
<div className="container">
|
|
<SectionTitle en="services" title="ما در هوم." titleMuted="چه کارهایی انجام میدهیم" />
|
|
</div>
|
|
<div className="service-panels" role="list">
|
|
{Array.from({ length: 4 }, (_, index) => {
|
|
const service = services[index];
|
|
const cms = categoryImages[index];
|
|
const image = cms?.url || service?.image || gallery.heroSlides[0].url;
|
|
const titleFa = cms?.titleFa || service?.titleFa || `خدمت ${index + 1}`;
|
|
const titleEn = cms?.titleEn || service?.titleEn || "";
|
|
const desc = cms?.subtext || service?.text || "";
|
|
const href = cms?.linkUrl || (service ? `/services/${service.slug}` : "/services");
|
|
const external = /^https?:\/\//.test(href);
|
|
return (
|
|
<Link
|
|
key={service?.index ?? index}
|
|
href={href}
|
|
className="service-panel"
|
|
role="listitem"
|
|
aria-label={`${titleFa}${titleEn ? ` — ${titleEn}` : ""}`}
|
|
target={external ? "_blank" : undefined}
|
|
rel={external ? "noreferrer" : undefined}
|
|
>
|
|
<img
|
|
src={image!}
|
|
alt={`${titleFa}${titleEn ? ` / ${titleEn}` : ""}`}
|
|
className="service-panel__image"
|
|
loading="lazy"
|
|
decoding="async"
|
|
/>
|
|
<div className="service-panel__shade" aria-hidden="true" />
|
|
<div className="service-panel__body">
|
|
<span className="service-panel__index">{service?.index ?? String(index + 1).padStart(2, "0")}</span>
|
|
<h3>{titleFa}</h3>
|
|
{titleEn ? <span className="latin">{titleEn}</span> : null}
|
|
{desc ? <p className="service-panel__desc">{desc}</p> : null}
|
|
</div>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
|
|
{/* project-images — masonry */}
|
|
<section className="section">
|
|
<div className="container">
|
|
<SectionTitle
|
|
en="projects"
|
|
title="پروژههای"
|
|
titleMuted="منتخب"
|
|
aside={<p className="lead">گزیدهای از کارهای اخیر در معماری، طراحی داخلی و نما.</p>}
|
|
/>
|
|
|
|
<div className="projects">
|
|
{projectSlots.map((image, index) => {
|
|
const title = image.titleFa || image.titleEn || `پروژه ${index + 1}`;
|
|
const media = { ...image, linkUrl: null };
|
|
const card = (
|
|
<>
|
|
<SlotImage image={media} alt={title} />
|
|
<div className="project__meta">
|
|
<h3>{title}</h3>
|
|
{image.subtext ? <span>{image.subtext}</span> : null}
|
|
</div>
|
|
{image.titleEn ? <p className="project__sub latin">{image.titleEn}</p> : null}
|
|
</>
|
|
);
|
|
if (image.linkUrl) {
|
|
const external = /^https?:\/\//.test(image.linkUrl);
|
|
return (
|
|
<Link
|
|
key={`${image.url}-${index}`}
|
|
href={image.linkUrl}
|
|
className="project"
|
|
target={external ? "_blank" : undefined}
|
|
rel={external ? "noreferrer" : undefined}
|
|
>
|
|
{card}
|
|
</Link>
|
|
);
|
|
}
|
|
return (
|
|
<div key={`${image.url}-${index}`} className="project">
|
|
{card}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<DetailsFrame />
|
|
|
|
{/* sticky-frame-bg */}
|
|
<OpeningFrame
|
|
image={openingImage}
|
|
imageAlt={sticky.titleFa || sticky.titleEn || "نگاه استودیو هوم"}
|
|
eyebrow={sticky.titleEn || "نگاه ما"}
|
|
headline="معماری ساده،"
|
|
headlineMuted="زندگی روشن"
|
|
headlineEn="SIMPLE ARCHITECTURE, BRIGHT LIVING"
|
|
body={sticky.subtext || about[2] || site.about[2]}
|
|
ctaLabel="درباره استودیو"
|
|
ctaHref="/about"
|
|
/>
|
|
|
|
{/* Process */}
|
|
<section className="section section--dark">
|
|
<div className="container">
|
|
<SectionTitle en="process" title="از ایده تا تحویل،" titleMuted="در چهار گام" light />
|
|
<div className="process">
|
|
{processSteps.map((item) => (
|
|
<div key={item.step} className="process__item">
|
|
<span className="process__num latin" lang="en" dir="ltr" aria-hidden="true">
|
|
{String(item.step).padStart(2, "0")}
|
|
</span>
|
|
<h3>{item.titleFa}</h3>
|
|
<p>{item.text}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Stats */}
|
|
<section className="stats-band" aria-label="آمار استودیو">
|
|
<div className="stats">
|
|
{stats.map((stat) => (
|
|
<div key={stat.label} className="stat">
|
|
<strong className="latin" lang="en" dir="ltr">
|
|
{stat.value}
|
|
</strong>
|
|
<span>{stat.label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{/* brands */}
|
|
{brandLogos.length ? (
|
|
<section className="section section--tight brands" aria-label="همکاران">
|
|
<div className="container">
|
|
<SectionTitle en="partners" title="برندها و" titleMuted="همکاران" />
|
|
<ul className="brands__grid">
|
|
{brandLogos.map((brand, index) => {
|
|
const alt = brand.titleFa || brand.titleEn || `برند ${index + 1}`;
|
|
return (
|
|
<li key={`${brand.url}-${index}`}>
|
|
<SlotImage image={brand} alt={alt} ratio={brandRatio} className="brands__logo" />
|
|
{brand.titleFa || brand.titleEn ? (
|
|
<p className="brands__caption">
|
|
{brand.titleFa ? <span>{brand.titleFa}</span> : null}
|
|
{brand.titleEn ? <span className="latin">{brand.titleEn}</span> : null}
|
|
</p>
|
|
) : null}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
) : null}
|
|
|
|
{/* instagram-row */}
|
|
<InstagramFollow
|
|
handle={instagram.handle}
|
|
href={instagram.href}
|
|
image={instagramImage}
|
|
ratio={instagramRatio}
|
|
/>
|
|
|
|
{/* CTA */}
|
|
<section className="cta-band">
|
|
<div className="cta-band__glow" aria-hidden="true" />
|
|
<div className="container cta">
|
|
<SectionTitle
|
|
en="collaborate"
|
|
title="پروژهای در ذهن دارید؟"
|
|
titleMuted="دربارهاش صحبت کنیم."
|
|
className="section-title--inline section-title--light"
|
|
aside={
|
|
<Link href="/contact" className="btn btn--light">
|
|
تماس با ما
|
|
<ArrowIcon />
|
|
</Link>
|
|
}
|
|
/>
|
|
</div>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|