Expand HOM studio site with portfolio, blog, auth, and details video frame.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
73971bf6d9
commit
55dd94d2e4
+218
-134
@@ -1,9 +1,14 @@
|
||||
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, resolveName } from "@/lib/content";
|
||||
import { resolveAbout, resolveInstagram, resolveLogo, resolveName } from "@/lib/content";
|
||||
import { getBusinessInfo } from "@/lib/meshkee";
|
||||
import { getPageSlots, slotImages, slotRatio } from "@/lib/static-images";
|
||||
|
||||
@@ -22,47 +27,68 @@ 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 hero = slotImages("hero", slots, [{ url: gallery.hero, titleFa: "نمای آجری در غروب" }])[0];
|
||||
const studio = slotImages("studio-image", slots, [{ url: gallery.studio, titleFa: "ورودی سبز" }])[0];
|
||||
const projectSlots = slots.projects?.images ?? [];
|
||||
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 (
|
||||
<>
|
||||
{/* Hero */}
|
||||
<section className="hero">
|
||||
<div className="container">
|
||||
<div className="hero__grid">
|
||||
<h1 className="hero__title">
|
||||
معماری ساده،
|
||||
<br />
|
||||
<em>زندگی روشن</em>
|
||||
<span className="hero__dot" aria-hidden="true" />
|
||||
</h1>
|
||||
<div className="hero__aside">
|
||||
<p>
|
||||
{name} یک گروه معماری و طراحی داخلی است؛ از طراحی نما و پلان تا جزئیات داخلی و اجرای نهایی.
|
||||
</p>
|
||||
<Link href="/contact" className="btn">
|
||||
شروع یک پروژه
|
||||
<ArrowIcon />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<SlotImage image={hero} alt="نمونه پروژه استودیو معماری هوم" ratio={slotRatio("hero", slots)} className="hero__frame" priority />
|
||||
<div className="hero__caption">
|
||||
<span>{hero.titleFa || hero.titleEn || "پروژه منتخب"}</span>
|
||||
<span className="latin">{site.nameEn}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<HeroSlider
|
||||
slides={heroSlides}
|
||||
title="معماری ساده،"
|
||||
titleLine2="زندگی روشن"
|
||||
lead={`${name} یک گروه معماری و طراحی داخلی است؛ از طراحی نما و پلان تا جزئیات داخلی و اجرای نهایی.`}
|
||||
ctaLabel="شروع یک پروژه"
|
||||
ctaHref="/contact"
|
||||
/>
|
||||
|
||||
{/* Intro */}
|
||||
{/* Intro — RTL: logo (rightmost) · copy · focus (left) */}
|
||||
<section className="section">
|
||||
<div className="container split">
|
||||
<div>
|
||||
<p className="eyebrow">استودیو</p>
|
||||
<h2 className="intro__text">{about[0]}</h2>
|
||||
<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>
|
||||
@@ -71,110 +97,130 @@ export default async function HomePage() {
|
||||
<span>معماری مسکونی، طراحی داخلی، طراحی نما</span>
|
||||
</div>
|
||||
<Link href="/about" className="text-link">
|
||||
بیشتر درباره هوم
|
||||
بیشتر بدانید
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Services */}
|
||||
<section className="section section--surface">
|
||||
{/* four-category — 4 service panels */}
|
||||
<section className="section section--tight">
|
||||
<div className="container">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<p className="eyebrow">خدمات</p>
|
||||
<h2 className="title">چه کاری انجام میدهیم</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="services">
|
||||
{services.map((service) => (
|
||||
<article key={service.index} className="service">
|
||||
<span className="service__index">{service.index}</span>
|
||||
<div>
|
||||
<h3>{service.titleFa}</h3>
|
||||
<span className="latin">{service.titleEn}</span>
|
||||
<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>
|
||||
<p>{service.text}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Projects */}
|
||||
{/* project-images — masonry */}
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<p className="eyebrow">پروژهها</p>
|
||||
<h2 className="title">پروژههای منتخب</h2>
|
||||
</div>
|
||||
<p className="lead" style={{ maxWidth: "34ch" }}>
|
||||
گزیدهای از کارهای اخیر در معماری، طراحی داخلی و نما.
|
||||
</p>
|
||||
</div>
|
||||
<SectionTitle
|
||||
en="projects"
|
||||
title="پروژههای"
|
||||
titleMuted="منتخب"
|
||||
aside={<p className="lead">گزیدهای از کارهای اخیر در معماری، طراحی داخلی و نما.</p>}
|
||||
/>
|
||||
|
||||
<div className="projects">
|
||||
{projectSlots.length
|
||||
? projectSlots.map((image, index) => (
|
||||
<div key={`${image.url}-${index}`} className="project">
|
||||
<SlotImage image={image} alt={image.titleFa || `پروژه ${index + 1}`} ratio={slotRatio("projects", slots)} />
|
||||
<div className="project__meta">
|
||||
<h3>{image.titleFa || image.titleEn || `پروژه ${index + 1}`}</h3>
|
||||
{image.subtext ? <span>{image.subtext}</span> : null}
|
||||
</div>
|
||||
{image.titleFa && image.titleEn ? <p className="project__sub latin">{image.titleEn}</p> : null}
|
||||
{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>
|
||||
))
|
||||
: projects.map((project) => (
|
||||
<article key={project.slug} className="project">
|
||||
<div className="frame" style={{ aspectRatio: slotRatio("projects", slots) }}>
|
||||
<img src={project.image} alt={`${project.titleFa} — ${project.category}`} loading="lazy" decoding="async" />
|
||||
</div>
|
||||
<div className="project__meta">
|
||||
<h3>{project.titleFa}</h3>
|
||||
<span>
|
||||
{project.category} · {project.year}
|
||||
</span>
|
||||
</div>
|
||||
<p className="project__sub latin">{project.titleEn}</p>
|
||||
</article>
|
||||
))}
|
||||
{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>
|
||||
|
||||
{/* Studio */}
|
||||
<section className="section section--line-top">
|
||||
<div className="container split split--equal">
|
||||
<SlotImage image={studio} alt="ورودی یکی از پروژههای استودیو هوم" ratio={slotRatio("studio-image", slots)} className="studio__frame" />
|
||||
<div className="studio__body">
|
||||
<div>
|
||||
<p className="eyebrow">نگاه ما</p>
|
||||
<h2 className="title">فضایی که با شما زندگی میکند</h2>
|
||||
</div>
|
||||
<p>
|
||||
ما طراحی را از نیاز واقعی آغاز میکنیم؛ نه از فرم. نور، مسیر حرکت، متریال و تناسب، ابزار ما برای ساختن فضایی هستند که
|
||||
سالها بعد هم آرام و خوانا بماند.
|
||||
</p>
|
||||
<p>{about[2] ?? site.about[2]}</p>
|
||||
<Link href="/about" className="btn btn--ghost">
|
||||
درباره استودیو
|
||||
<ArrowIcon />
|
||||
</Link>
|
||||
</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">
|
||||
<p className="eyebrow eyebrow--light">فرآیند کار</p>
|
||||
<h2 className="title">از ایده تا تحویل، در چهار گام</h2>
|
||||
<SectionTitle en="process" title="از ایده تا تحویل،" titleMuted="در چهار گام" light />
|
||||
<div className="process">
|
||||
{processSteps.map((item) => (
|
||||
<div key={item.step} className="process__item">
|
||||
<span className="process__step">گام {item.step}</span>
|
||||
<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>
|
||||
@@ -184,30 +230,68 @@ export default async function HomePage() {
|
||||
</section>
|
||||
|
||||
{/* Stats */}
|
||||
<section className="section--tight">
|
||||
<div className="container">
|
||||
<div className="stats">
|
||||
{stats.map((stat) => (
|
||||
<div key={stat.label} className="stat">
|
||||
<strong>{stat.value}</strong>
|
||||
<span>{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="section">
|
||||
<div className="container cta">
|
||||
<div>
|
||||
<p className="eyebrow">شروع همکاری</p>
|
||||
<h2>پروژهای در ذهن دارید؟ دربارهاش صحبت کنیم.</h2>
|
||||
{/* 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>
|
||||
<Link href="/contact" className="btn">
|
||||
تماس با ما
|
||||
<ArrowIcon />
|
||||
</Link>
|
||||
</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>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user