Polish portfolio, blog, and site chrome for clearer browsing.

Add patterned title bands, lightbox gallery, project link bar, footer stats/credit, gradient link hovers, and more reliable blog covers/body rendering.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-23 22:17:25 +03:30
co-authored by Cursor
parent 37d8e4be91
commit 908d86939c
19 changed files with 710 additions and 193 deletions
+86 -35
View File
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { BlogCoverImage } from "@/components/BlogCoverImage";
import { ContentPageShell } from "@/components/ContentPageShell";
import { HtmlContent } from "@/components/HtmlContent";
import {
@@ -15,6 +16,28 @@ type BlogDetailPageProps = {
params: Promise<{ slug: string }>;
};
function stripLeadingCoverImage(html: string, cover: string | null): string {
if (!html) return html;
let next = html.replace(
/^\s*<p[^>]*>\s*<img[^>]*>\s*<\/p>/i,
"",
);
if (cover) {
const escaped = cover.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
next = next.replace(
new RegExp(
`<p[^>]*>\\s*<img[^>]*src=["']${escaped}["'][^>]*>\\s*<\\/p>`,
"i",
),
"",
);
}
return next.trim();
}
export async function generateMetadata({
params,
}: BlogDetailPageProps): Promise<Metadata> {
@@ -45,54 +68,82 @@ export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
const cover = getBlogCoverImage(blog);
const date = formatFaDate(blog.publishedAt);
const author = authorName(blog.author);
const category = blog.categoryName;
const bodyHtml = blog.mainTextHtml
? stripLeadingCoverImage(blog.mainTextHtml, cover)
: null;
const titleEn = category || "Blog";
return (
<ContentPageShell>
<ContentPageShell flushTop>
<article className="pb-20 lg:pb-28">
<div className="mx-auto max-w-3xl px-5 lg:px-8">
<div className="reveal mb-8">
<Link
href="/blog"
className="link-hover mb-6 inline-flex text-sm text-muted"
<div className="page-title-band mb-8 md:mb-10">
<div className="relative mx-auto max-w-7xl px-5 pb-4 pt-[calc(var(--header-height)+0.75rem)] lg:px-8 lg:pb-5 lg:pt-[calc(var(--header-height)+1rem)]">
<p
aria-hidden
className="latin pointer-events-none absolute inset-x-5 top-[calc(var(--header-height)+2.4rem)] z-0 text-[clamp(1.75rem,5.5vw,3.75rem)] font-light uppercase leading-none tracking-[0.05em] text-ink/[0.07] lg:inset-x-8 lg:top-[calc(var(--header-height)+2.6rem)]"
>
بازگشت به مقالات
</Link>
{titleEn}
</p>
{blog.categoryName ? (
<p className="latin title-en mb-3 text-muted">
{blog.categoryName}
</p>
) : null}
<div className="reveal relative z-10">
<nav
aria-label="مسیر صفحه"
className="mb-3 flex flex-wrap items-center gap-1.5 text-xs text-muted"
>
<Link href="/blog" className="link-hover">
مقالات
</Link>
{category ? (
<>
<span aria-hidden className="text-line">
/
</span>
<span className="latin uppercase tracking-[0.14em]">
{category}
</span>
</>
) : null}
<span aria-hidden className="text-line">
/
</span>
<span className="line-clamp-1 text-ink-soft">{blog.title}</span>
</nav>
<h1 className="whitespace-normal text-[clamp(1.35rem,3.8vw,2.5rem)] font-normal leading-tight tracking-tight text-ink">
{blog.title}
</h1>
<h1 className="whitespace-normal text-[clamp(1.35rem,3.8vw,2.5rem)] font-normal leading-tight tracking-tight text-ink">
{blog.title}
</h1>
{blog.abstract ? (
<p className="mt-5 text-sm leading-8 text-muted md:text-base">
{blog.abstract}
</p>
) : null}
<div className="mt-5 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted">
{date ? <time dateTime={blog.publishedAt ?? undefined}>{date}</time> : null}
{author ? <span>نویسنده: {author}</span> : null}
{blog.abstract ? (
<p className="mt-2 text-justify text-sm leading-7 text-muted md:text-base">
{blog.abstract}
</p>
) : null}
</div>
</div>
</div>
{cover && blog.titleImageUrl ? (
<div className="reveal reveal-delay-1 mb-10 overflow-hidden rounded-2xl bg-paper-deep">
<img
src={cover}
alt=""
className="mx-auto max-h-[28rem] w-full object-contain"
/>
<div className="mx-auto max-w-3xl px-5 lg:px-8">
{cover ? (
<div className="mb-5 overflow-hidden rounded-2xl">
<BlogCoverImage src={cover} className="mx-auto max-h-[28rem]" />
</div>
) : null}
{blog.mainTextHtml ? (
<div className="reveal reveal-delay-2">
<HtmlContent html={blog.mainTextHtml} />
{(date || author) ? (
<div className="mb-8 flex items-center justify-between gap-4 border-b border-line/70 pb-5 text-xs text-muted">
{author ? <span>نویسنده: {author}</span> : <span />}
{date ? (
<time dateTime={blog.publishedAt ?? undefined}>{date}</time>
) : null}
</div>
) : cover ? (
<div className="mb-8 border-b border-line/70" />
) : null}
{bodyHtml ? (
<div className="reveal">
<HtmlContent html={bodyHtml} />
</div>
) : null}
</div>