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>
);
}