Initial commit: Negin Dental Clinic website
Next.js (App Router) clone of negindentalclinic.ir, wired to the Meshkee Website API (blog, contact form, favicon), with on-page SEO and a scroll-reveal animation sequence on the homepage. See context.md for full project context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
.wrap {
|
||||
max-width: 820px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.back {
|
||||
display: inline-block;
|
||||
margin-bottom: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 0 0 28px;
|
||||
}
|
||||
|
||||
.cover {
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
margin-bottom: 34px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.cover img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
line-height: 2.1;
|
||||
}
|
||||
|
||||
.content :global(p) {
|
||||
text-align: justify;
|
||||
text-align-last: right;
|
||||
margin: 0 0 1.4em;
|
||||
}
|
||||
|
||||
.content :global(h2),
|
||||
.content :global(h3) {
|
||||
color: var(--heading);
|
||||
margin: 1.6em 0 0.7em;
|
||||
}
|
||||
|
||||
.content :global(img) {
|
||||
max-width: 100%;
|
||||
border-radius: var(--radius);
|
||||
margin: 1.2em 0;
|
||||
}
|
||||
|
||||
.content :global(a) {
|
||||
color: var(--brand);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content :global(ul),
|
||||
.content :global(ol) {
|
||||
padding-inline-start: 1.4em;
|
||||
margin: 0 0 1.4em;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.title {
|
||||
font-size: 25px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import {
|
||||
getBlogById,
|
||||
blogTitle,
|
||||
blogExcerpt,
|
||||
blogCover,
|
||||
blogDate,
|
||||
blogContentHtml,
|
||||
faDate,
|
||||
} from "@/lib/meshkee";
|
||||
import styles from "./blog-detail.module.css";
|
||||
|
||||
export const revalidate = 120;
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { id } = await params;
|
||||
const blog = await getBlogById(id);
|
||||
if (!blog) return { title: "مقاله" };
|
||||
const title = blogTitle(blog);
|
||||
const description = blogExcerpt(blog) || undefined;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
images: blogCover(blog) ? [blogCover(blog)] : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function BlogDetailPage({ params }) {
|
||||
const { id } = await params;
|
||||
const blog = await getBlogById(id);
|
||||
if (!blog) notFound();
|
||||
|
||||
const title = blogTitle(blog);
|
||||
const cover = blogCover(blog);
|
||||
const html = blogContentHtml(blog);
|
||||
|
||||
return (
|
||||
<article className="section">
|
||||
<div className={`container ${styles.wrap}`}>
|
||||
<Link href="/blog" className={styles.back}>
|
||||
← بازگشت به مقالات
|
||||
</Link>
|
||||
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
{blogDate(blog) ? <p className={styles.date}>{faDate(blogDate(blog))}</p> : null}
|
||||
|
||||
{cover ? (
|
||||
<div className={styles.cover}>
|
||||
<Image src={cover} alt={title} width={1200} height={640} priority />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{html ? (
|
||||
<div className={styles.content} dangerouslySetInnerHTML={{ __html: html }} />
|
||||
) : (
|
||||
<p className={styles.content}>{blogExcerpt(blog)}</p>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user