Initial commit: website

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-03 23:17:38 +03:30
co-authored by Cursor
commit dc93a202bf
160 changed files with 16454 additions and 0 deletions
@@ -0,0 +1,196 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad) 80px;
}
.layout {
margin-top: 36px;
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.55fr);
gap: 42px;
align-items: start;
}
.article {
min-width: 0;
}
.meta {
display: flex;
flex-wrap: wrap;
gap: 18px 28px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 300;
color: var(--color-soft);
}
.meta span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.meta img {
width: 14px;
height: 14px;
opacity: 0.7;
object-fit: contain;
}
.title {
font-size: clamp(28px, 3vw, 40px);
font-weight: 300;
line-height: 1.2;
margin-bottom: 12px;
}
.intro {
font-size: 17px;
font-weight: 300;
line-height: 18px;
color: #9d9d9d;
text-align: justify;
margin-bottom: 28px;
}
.hero {
position: relative;
width: 100%;
aspect-ratio: 1190 / 525;
border: 1px solid #eee;
border-radius: 5px;
overflow: hidden;
margin-bottom: 36px;
background: #f6f6f6;
}
.heroImg {
object-fit: cover;
padding: 1.5%;
border-radius: 5px;
}
.sectionTitle {
font-size: clamp(28px, 3vw, 40px);
font-weight: 300;
margin-bottom: 18px;
}
.actions {
margin-top: 36px;
padding-top: 28px;
border-top: 1px solid #ececec;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 18px;
}
.related {
display: grid;
grid-template-columns: 154px minmax(0, 1fr);
gap: 18px;
padding: 18px;
border: 1px solid #eee;
border-radius: 5px;
transition: border-color 0.25s ease, transform 0.25s ease;
}
.related:hover {
border-color: #ccc;
transform: translateY(-2px);
}
.relatedMedia {
position: relative;
width: 154px;
height: 154px;
border-radius: 5px;
overflow: hidden;
background: #f6f6f6;
}
.relatedImg {
object-fit: cover;
}
.relatedBody {
display: flex;
flex-direction: column;
min-width: 0;
padding-top: 4px;
}
.relatedBody h3 {
font-size: 18px;
font-weight: 300;
margin-bottom: 10px;
}
.relatedBody p {
font-size: 14px;
font-weight: 300;
line-height: 1.45;
color: #9d9d9d;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
flex: 1;
}
.relatedMeta {
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 14px;
font-size: 12px;
font-weight: 300;
color: var(--color-soft);
}
.relatedMeta span {
display: inline-flex;
align-items: center;
gap: 6px;
}
.relatedMeta img {
width: 14px;
height: 14px;
opacity: 0.55;
}
@media (max-width: 1100px) {
.layout {
grid-template-columns: 1fr;
}
.sidebar {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 720px) {
.sidebar {
grid-template-columns: 1fr;
}
.related {
grid-template-columns: 110px minmax(0, 1fr);
}
.relatedMedia {
width: 110px;
height: 110px;
}
}
@@ -0,0 +1,133 @@
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import DetailBanner from "@/components/DetailBanner";
import ArticleBody from "@/components/ArticleBody";
import ShareLikeActions from "@/components/ShareLikeActions";
import {
getAllBusinessSlugs,
getBusinessBlogPost,
getBusinessBySlug,
} from "@/data/business";
import styles from "./blog.module.css";
export function generateStaticParams() {
return getAllBusinessSlugs().flatMap((slug) => {
const business = getBusinessBySlug(slug);
return (business?.posts ?? []).map((post) => ({
slug,
postSlug: post.slug,
}));
});
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string; postSlug: string }>;
}) {
const { slug, postSlug } = await params;
const data = getBusinessBlogPost(slug, postSlug);
if (!data) return { title: "Blog Details | WilaSpace" };
return {
title: `${data.post.title} | Blog Details | WilaSpace`,
description: data.post.intro,
};
}
export default async function BlogDetailPage({
params,
}: {
params: Promise<{ slug: string; postSlug: string }>;
}) {
const { slug, postSlug } = await params;
const data = getBusinessBlogPost(slug, postSlug);
if (!data) notFound();
const { business, post, related } = data;
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<DetailBanner title="Blog Details" breadcrumbs={business.breadcrumbs} />
<div className={styles.layout}>
<article className={styles.article}>
<div className={styles.meta}>
<span>
<img src="/images/icons/calendar.svg" alt="" />
{post.date}
</span>
<span>
<img src="/images/icons/user.svg" alt="" />
{post.author}
</span>
<span>
<img src="/images/icons/category.svg" alt="" />
{post.category}
</span>
</div>
<h2 className={styles.title}>{post.title}.</h2>
<p className={styles.intro}>{post.intro}</p>
<div className={styles.hero}>
<Image
src={post.hero}
alt=""
fill
className={styles.heroImg}
sizes="(max-width: 1100px) 100vw, 1232px"
priority
/>
</div>
<h3 className={styles.sectionTitle}>{post.title}.</h3>
<ArticleBody blocks={post.body} />
<div className={styles.actions}>
<ShareLikeActions />
</div>
</article>
<aside className={styles.sidebar} aria-label="Related posts">
{related.map((item) => (
<Link
key={item.slug}
href={`/businesses/${business.slug}/blog/${item.slug}`}
className={styles.related}
>
<div className={styles.relatedMedia}>
<Image
src={item.image}
alt=""
fill
className={styles.relatedImg}
sizes="154px"
/>
</div>
<div className={styles.relatedBody}>
<h3>{item.title}</h3>
<p>{item.excerpt}</p>
<div className={styles.relatedMeta}>
<span>
<img src="/images/icons/calendar.svg" alt="" />
{item.date}
</span>
<span>
<img src="/images/icons/user.svg" alt="" />
{item.author}
</span>
</div>
</div>
</Link>
))}
</aside>
</div>
</main>
<Footer />
</div>
);
}
+9
View File
@@ -0,0 +1,9 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad);
}
+52
View File
@@ -0,0 +1,52 @@
import { notFound } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import BusinessDetailView from "@/components/BusinessDetail";
import {
getAllBusinessSlugs,
getBusinessBySlug,
} from "@/data/business";
import styles from "./page.module.css";
export function generateStaticParams() {
return getAllBusinessSlugs().map((slug) => ({ slug }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const business = getBusinessBySlug(slug);
if (!business) {
return { title: "Business | WilaSpace" };
}
return {
title: `${business.name} | WilaSpace`,
description: business.description.slice(0, 140),
};
}
export default async function BusinessDetailPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const business = getBusinessBySlug(slug);
if (!business) {
notFound();
}
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<BusinessDetailView business={business} />
</main>
<Footer />
</div>
);
}
@@ -0,0 +1,92 @@
import { notFound } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import DetailBanner from "@/components/DetailBanner";
import ArticleBody from "@/components/ArticleBody";
import ShareLikeActions from "@/components/ShareLikeActions";
import PortfolioGallery from "@/components/PortfolioGallery";
import {
getAllBusinessSlugs,
getBusinessBySlug,
getBusinessPortfolio,
} from "@/data/business";
import styles from "./portfolio.module.css";
export function generateStaticParams() {
return getAllBusinessSlugs().flatMap((slug) => {
const business = getBusinessBySlug(slug);
return (business?.portfolios ?? []).map((item) => ({
slug,
portfolioSlug: item.slug,
}));
});
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string; portfolioSlug: string }>;
}) {
const { slug, portfolioSlug } = await params;
const data = getBusinessPortfolio(slug, portfolioSlug);
if (!data) return { title: "Portfolios details | WilaSpace" };
return {
title: `${data.portfolio.title} | Portfolios details | WilaSpace`,
description: data.portfolio.description,
};
}
export default async function PortfolioDetailPage({
params,
}: {
params: Promise<{ slug: string; portfolioSlug: string }>;
}) {
const { slug, portfolioSlug } = await params;
const data = getBusinessPortfolio(slug, portfolioSlug);
if (!data) notFound();
const { business, portfolio } = data;
return (
<div className={styles.page}>
<Header />
<main className={styles.shell}>
<DetailBanner
title="Portfolios details"
breadcrumbs={business.breadcrumbs}
/>
<div className={styles.layout}>
<PortfolioGallery images={portfolio.images} />
<div className={styles.copy}>
<div className={styles.meta}>
<span>
<img src="/images/icons/calendar.svg" alt="" />
{portfolio.date}
</span>
<span>
<img src="/images/icons/user.svg" alt="" />
{portfolio.author}
</span>
<span>
<img src="/images/icons/category.svg" alt="" />
{portfolio.category}
</span>
</div>
<h2 className={styles.title}>{portfolio.title}.</h2>
<p className={styles.intro}>{portfolio.description}</p>
<hr className={styles.rule} />
<div className={styles.bodyWrap}>
<ArticleBody blocks={portfolio.body} />
</div>
<div className={styles.actions}>
<ShareLikeActions />
</div>
</div>
</div>
</main>
<Footer />
</div>
);
}
@@ -0,0 +1,82 @@
.page {
background: var(--color-white);
}
.shell {
max-width: 1920px;
margin: 0 auto;
padding: 0 var(--page-pad) 80px;
}
.layout {
margin-top: 36px;
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
gap: 42px;
align-items: start;
}
.copy {
min-width: 0;
padding-top: 4px;
}
.meta {
display: flex;
flex-wrap: wrap;
gap: 18px 28px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 300;
color: var(--color-soft);
}
.meta span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.meta img {
width: 14px;
height: 14px;
opacity: 0.7;
object-fit: contain;
}
.title {
font-size: clamp(28px, 3vw, 40px);
font-weight: 300;
line-height: 1.2;
margin-bottom: 10px;
}
.intro {
font-size: 17px;
font-weight: 300;
line-height: 18px;
color: #9d9d9d;
margin-bottom: 18px;
}
.rule {
border: 0;
border-top: 1px solid #ececec;
margin: 0 0 20px;
}
.bodyWrap {
min-height: 280px;
}
.actions {
margin-top: 28px;
padding-top: 24px;
border-top: 1px solid #ececec;
}
@media (max-width: 980px) {
.layout {
grid-template-columns: 1fr;
}
}