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,74 @@
|
||||
.intro,
|
||||
.care {
|
||||
display: grid;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.intro,
|
||||
.care {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.intro h2,
|
||||
.care h2 {
|
||||
font-size: 28px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.intro > div:not(.introArt),
|
||||
.care > div:not(.careArt) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* push the section CTA to the left edge of the text column */
|
||||
.intro :global(.btn),
|
||||
.care :global(.btn) {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.introArt img,
|
||||
.careArt img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.adv {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 28px;
|
||||
border-inline-start: 4px solid var(--cyan);
|
||||
}
|
||||
|
||||
.adv h3 {
|
||||
font-size: 19px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.adv p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.insurances {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.insurances li {
|
||||
background: var(--bg-tint);
|
||||
color: var(--brand);
|
||||
font-weight: 700;
|
||||
padding: 12px 26px;
|
||||
border-radius: 999px;
|
||||
font-size: 15px;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import styles from "./about.module.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "درباره ما",
|
||||
description:
|
||||
"درباره کلینیک دندانپزشکی نگین در قم؛ کادر مجرب، مواد باکیفیت، دسترسی آسان و قرارداد با بیمههای معتبر و بانکها.",
|
||||
};
|
||||
|
||||
const INSURANCES = [
|
||||
"دانا",
|
||||
"نوین",
|
||||
"ایران",
|
||||
"بانک ملت",
|
||||
"معلم",
|
||||
"کوثر",
|
||||
"بانک تجارت",
|
||||
"دی",
|
||||
"سامان",
|
||||
];
|
||||
|
||||
const ADVANTAGES = [
|
||||
{
|
||||
title: "دسترسی آسان",
|
||||
text: "قرار گرفتن کلینیک در یکی از اصلیترین خیابانهای قم و دسترسی آسان از تمامی نقاط شهر.",
|
||||
},
|
||||
{
|
||||
title: "مواد با کیفیت",
|
||||
text: "استفاده از مواد و تجهیزات دندانپزشکی با کیفیت و مطابق با استانداردهای روز دنیا.",
|
||||
},
|
||||
{
|
||||
title: "کادر مجرب",
|
||||
text: "کادر مجرب دندانپزشکی و متخصصان عضو هیئت علمی دانشکده دندانپزشکی.",
|
||||
},
|
||||
{
|
||||
title: "ویژه بانوان و کودکان",
|
||||
text: "محیطی آرام و مناسب برای بانوان و کودکان جهت ارائه خدمات تخصصی و عمومی.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
en="About Us"
|
||||
fa="درباره ما"
|
||||
lead="درباره کلینیک نگین بیشتر بدانید…"
|
||||
/>
|
||||
|
||||
<section className="section">
|
||||
<div className={`container ${styles.intro}`}>
|
||||
<div className={styles.introArt}>
|
||||
<Image
|
||||
src="/assets/img/info3-2.jpg"
|
||||
alt="کلینیک دندانپزشکی نگین"
|
||||
width={720}
|
||||
height={540}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="sec-head__en">More About Our Clinic</span>
|
||||
<h2>درباره کلینیک بیشتر بدانید</h2>
|
||||
<p>
|
||||
دسترسی آسان از تمامی نقاط قم به کلینیک و قرار گرفتن آن در یکی از
|
||||
اصلیترین خیابانهای شهر، از مزیتهای بارز این کلینیک است. استفاده
|
||||
از مواد با کیفیت و کادر مجرب دندانپزشکی از دیگر مزیتهای کلینیک
|
||||
دندانپزشکی نگین است.
|
||||
</p>
|
||||
<p>
|
||||
کلینیک نگین با بهرهگیری از متخصصین مجرب و محیطی آرام و مناسب برای
|
||||
بانوان و کودکان، آماده پاسخگویی و ارائه خدمت به مراجعین محترم میباشد.
|
||||
</p>
|
||||
<Link href="/contact-us" className="btn btn--cyan">
|
||||
با ما در تماس باشید
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section section--tint">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<span className="sec-head__en">Why Negin</span>
|
||||
<h2 className="sec-head__fa">مزیتهای کلینیک نگین</h2>
|
||||
</div>
|
||||
<div className="grid grid--2">
|
||||
{ADVANTAGES.map((a) => (
|
||||
<article key={a.title} className={styles.adv}>
|
||||
<h3>{a.title}</h3>
|
||||
<p>{a.text}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<span className="sec-head__en">Insurance & Banks</span>
|
||||
<h2 className="sec-head__fa">طرف قرارداد با بیمههای معتبر و بانکها</h2>
|
||||
<p className="sec-head__lead">از جمله بیمهها و بانکهای طرف قرارداد کلینیک نگین:</p>
|
||||
</div>
|
||||
<ul className={styles.insurances}>
|
||||
{INSURANCES.map((i) => (
|
||||
<li key={i}>{i}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section section--grey">
|
||||
<div className={`container ${styles.care}`}>
|
||||
<div>
|
||||
<span className="sec-head__en">Dental Care</span>
|
||||
<h2>مراقبت از دندان</h2>
|
||||
<p>
|
||||
یکی از مهمترین اعضای بدن انسان دندانها هستند. بسیاری از افراد با
|
||||
کوتاهی کردن، سلامت دندانهایشان را به خطر میاندازند یا پس از خراب
|
||||
شدن یک دندان خیلی دیر به دندانپزشک مراجعه میکنند که باعث مشکلات و
|
||||
بیماریهای دیگر دندان و لثه میشود.
|
||||
</p>
|
||||
<p>
|
||||
به مرور زمان ترک (شکاف)های ریزی روی مینای دندان ایجاد میشود؛ این
|
||||
شکافها فشار ایجادشده هنگام جویدن را خنثی کرده و از شکسته شدن دندان
|
||||
جلوگیری میکنند.
|
||||
</p>
|
||||
<p>
|
||||
خمیردندانهای معمولی موجود در بازار معمولاً به حد کافی فلوراید دارند
|
||||
که استفاده از آنها در وعدههای مسواک زدن، به سفیدی دندان و محافظت از
|
||||
مینای دندان کمک قابلتوجهی میکند. با این حال، دندانها همیشه نمیتوانند
|
||||
در برابر باکتریهای موجود در دهان پایداری کنند؛ این باکتریها اسیدهایی
|
||||
ترشح میکنند که کلسیم و فسفات مینای دندان را از بین برده و باعث پوسیدگی
|
||||
دندانها میشوند.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.careArt}>
|
||||
<Image
|
||||
src="/assets/img/info3-3.jpg"
|
||||
alt="مراقبت از دندان"
|
||||
width={720}
|
||||
height={540}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
.empty {
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty h2 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.empty p {
|
||||
color: var(--muted);
|
||||
margin-bottom: 28px;
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.cardArt {
|
||||
display: block;
|
||||
aspect-ratio: 16 / 10;
|
||||
background: var(--bg-tint);
|
||||
}
|
||||
|
||||
.cardArt img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.cardArtFallback {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--brand), var(--cyan));
|
||||
opacity: 0.16;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
padding: 22px 24px 26px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cardDate {
|
||||
font-size: 12px;
|
||||
color: var(--cyan-deep);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cardBody h3 {
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cardBody p {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cardMore {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--brand);
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.cardMore::after {
|
||||
content: " ←";
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.pagerLink {
|
||||
min-width: 38px;
|
||||
padding: 8px 12px;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--heading);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pagerLink:hover {
|
||||
border-color: var(--brand);
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.pagerLinkActive {
|
||||
background: var(--brand);
|
||||
border-color: var(--brand);
|
||||
color: #fff;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import { getBlogs, blogTitle, blogExcerpt, blogCover, blogDate, faDate, slugifyFa } from "@/lib/meshkee";
|
||||
import styles from "./blog.module.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "مقالات",
|
||||
description:
|
||||
"مقالات و مطالب آموزشی کلینیک دندانپزشکی نگین درباره سلامت دهان و دندان، مراقبت روزانه از دندانها، ایمپلنت، ارتودنسی و آخرین روشهای درمانی.",
|
||||
};
|
||||
|
||||
export const revalidate = 120;
|
||||
|
||||
export default async function BlogPage({ searchParams }) {
|
||||
const page = Number((await searchParams)?.page) || 1;
|
||||
const { items, total, pageSize } = await getBlogs({ page, pageSize: 12 });
|
||||
const totalPages = Math.max(1, Math.ceil((total || 0) / (pageSize || 12)));
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHero en="Blog" fa="مقالات" lead="مطالب آموزشی درباره سلامت دهان و دندان." />
|
||||
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
{items.length === 0 ? (
|
||||
<div className={styles.empty}>
|
||||
<h2>بهزودی</h2>
|
||||
<p>
|
||||
مقالات کلینیک نگین در حال آمادهسازی است. برای دریافت مشاوره و اطلاعات
|
||||
بیشتر با ما در تماس باشید.
|
||||
</p>
|
||||
<Link href="/contact-us" className="btn">
|
||||
تماس با ما
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="visually-hidden">لیست مقالات</h2>
|
||||
<div className="grid grid--3">
|
||||
{items.map((blog) => {
|
||||
const title = blogTitle(blog);
|
||||
const cover = blogCover(blog);
|
||||
const href = `/blog/${blog.id}/${slugifyFa(title)}`;
|
||||
return (
|
||||
<article key={blog.id} className={styles.card}>
|
||||
<Link href={href} className={styles.cardArt}>
|
||||
{cover ? (
|
||||
<Image src={cover} alt={title} width={420} height={260} />
|
||||
) : (
|
||||
<span className={styles.cardArtFallback} aria-hidden="true" />
|
||||
)}
|
||||
</Link>
|
||||
<div className={styles.cardBody}>
|
||||
{blogDate(blog) ? (
|
||||
<span className={styles.cardDate}>{faDate(blogDate(blog))}</span>
|
||||
) : null}
|
||||
<h3>
|
||||
<Link href={href}>{title}</Link>
|
||||
</h3>
|
||||
{blogExcerpt(blog) ? <p>{blogExcerpt(blog)}</p> : null}
|
||||
<Link href={href} className={styles.cardMore}>
|
||||
ادامه مطلب
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{totalPages > 1 ? (
|
||||
<nav className={styles.pager} aria-label="صفحهبندی مقالات">
|
||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
|
||||
<Link
|
||||
key={p}
|
||||
href={p === 1 ? "/blog" : `/blog?page=${p}`}
|
||||
className={`${styles.pagerLink} ${p === page ? styles.pagerLinkActive : ""}`}
|
||||
>
|
||||
{p}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { submitContact } from "@/lib/meshkee";
|
||||
import styles from "./contact.module.css";
|
||||
|
||||
const EMPTY = { name: "", email: "", phone: "", subject: "", message: "" };
|
||||
|
||||
export default function ContactForm() {
|
||||
const [values, setValues] = useState(EMPTY);
|
||||
const [status, setStatus] = useState("idle"); // idle | sending | error
|
||||
const [error, setError] = useState("");
|
||||
const [toast, setToast] = useState(null); // { text, kind } | null
|
||||
const toastTimer = useRef(null);
|
||||
|
||||
useEffect(() => () => clearTimeout(toastTimer.current), []);
|
||||
|
||||
const showToast = (text, kind = "success") => {
|
||||
setToast({ text, kind });
|
||||
clearTimeout(toastTimer.current);
|
||||
toastTimer.current = setTimeout(() => setToast(null), 4500);
|
||||
};
|
||||
|
||||
const onChange = (e) =>
|
||||
setValues((v) => ({ ...v, [e.target.name]: e.target.value }));
|
||||
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setStatus("sending");
|
||||
setError("");
|
||||
try {
|
||||
await submitContact({
|
||||
title: values.subject,
|
||||
name: values.name,
|
||||
text: values.message,
|
||||
email: values.email || undefined,
|
||||
cellNumber: values.phone || undefined,
|
||||
});
|
||||
setValues(EMPTY);
|
||||
setStatus("idle");
|
||||
showToast("پیام شما با موفقیت ارسال شد. همکاران ما در اسرع وقت با شما تماس خواهند گرفت.");
|
||||
} catch (err) {
|
||||
setStatus("error");
|
||||
setError(err.message || "ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.");
|
||||
showToast("ارسال پیام ناموفق بود. لطفاً دوباره تلاش کنید.", "error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<form className={styles.form} onSubmit={onSubmit} noValidate>
|
||||
<div className={styles.row}>
|
||||
<label>
|
||||
<span>نام و نام خانوادگی</span>
|
||||
<input
|
||||
name="name"
|
||||
value={values.name}
|
||||
onChange={onChange}
|
||||
required
|
||||
autoComplete="name"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>آدرس الکترونیکی</span>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={values.email}
|
||||
onChange={onChange}
|
||||
autoComplete="email"
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className={styles.row}>
|
||||
<label>
|
||||
<span>شماره تماس</span>
|
||||
<input
|
||||
name="phone"
|
||||
value={values.phone}
|
||||
onChange={onChange}
|
||||
autoComplete="tel"
|
||||
inputMode="tel"
|
||||
dir="ltr"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>موضوع</span>
|
||||
<input
|
||||
name="subject"
|
||||
value={values.subject}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span>پیام شما</span>
|
||||
<textarea
|
||||
name="message"
|
||||
rows={6}
|
||||
value={values.message}
|
||||
onChange={onChange}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button type="submit" className="btn" disabled={status === "sending"}>
|
||||
{status === "sending" ? "در حال ارسال…" : "ارسال پیام"}
|
||||
</button>
|
||||
|
||||
{status === "error" && error ? (
|
||||
<p className={styles.formError} role="alert">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
</form>
|
||||
|
||||
{toast ? (
|
||||
<div
|
||||
className={`${styles.toast} ${toast.kind === "error" ? styles.toastError : ""}`}
|
||||
role="status"
|
||||
>
|
||||
{toast.text}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
.infoGrid {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.infoGrid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.infoCard {
|
||||
text-align: center;
|
||||
padding: 34px 24px;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-tint);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.infoCard:hover {
|
||||
background-color: #e2eff9;
|
||||
}
|
||||
|
||||
.infoCard h3 {
|
||||
font-size: 18px;
|
||||
margin: 4px 0 10px;
|
||||
}
|
||||
|
||||
.infoCard p,
|
||||
.infoCard a {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
.infoCard a:hover {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.infoIcon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 0 auto 16px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* ---------- form ---------- */
|
||||
.formWrap {
|
||||
/* same width as the info-card row above, and the same 60px rhythm
|
||||
on top (gap from the row) and bottom (gap to what follows) */
|
||||
margin: 0 0 60px;
|
||||
background: var(--bg-tint);
|
||||
border: none;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 44px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 620px) {
|
||||
.row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
.form input,
|
||||
.form textarea {
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
padding: 13px 16px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.form input:focus,
|
||||
.form textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.form textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form :global(.btn) {
|
||||
align-self: flex-end;
|
||||
justify-content: center;
|
||||
padding: 11px 28px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.formError {
|
||||
margin: 0;
|
||||
padding: 14px 18px;
|
||||
border-radius: 10px;
|
||||
background: #fdecec;
|
||||
color: #b3261e;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
/* ---------- success / error toast ---------- */
|
||||
.toast {
|
||||
position: fixed;
|
||||
inset-inline: 0;
|
||||
bottom: 26px;
|
||||
margin-inline: auto;
|
||||
width: fit-content;
|
||||
max-width: calc(100% - 40px);
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 999px;
|
||||
background: #1f7a4d;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
box-shadow: var(--shadow-md);
|
||||
animation: toast-in 0.25s ease;
|
||||
}
|
||||
|
||||
.toastError {
|
||||
background: #b3261e;
|
||||
}
|
||||
|
||||
@keyframes toast-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- map ---------- */
|
||||
.mapSection {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.mapSection iframe {
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
border: 0;
|
||||
filter: grayscale(0.15);
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.infoGrid {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.formWrap {
|
||||
margin-bottom: 32px;
|
||||
padding: 26px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import PageHero from "@/components/PageHero";
|
||||
import ContactForm from "./ContactForm";
|
||||
import styles from "./contact.module.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "تماس با ما",
|
||||
description:
|
||||
"راههای ارتباطی کلینیک دندانپزشکی نگین در قم: آدرس بلوار امین، تلفن ۰۲۵-۳۲۹۴۵۰۷۷ و فرم تماس آنلاین.",
|
||||
};
|
||||
|
||||
const INFO = [
|
||||
{
|
||||
en: "Address",
|
||||
fa: "آدرس",
|
||||
value: "قم، بلوار امین، کوچه ۲۱، شماره ۵ (روبروی صدا و سیما)",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||
<path d="M12 21s7-5.6 7-11a7 7 0 1 0-14 0c0 5.4 7 11 7 11z" />
|
||||
<circle cx="12" cy="10" r="2.6" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
en: "Phone",
|
||||
fa: "تلفن تماس",
|
||||
value: "۰۲۵ - ۳۲۹۴۵۰۷۷",
|
||||
href: "tel:+982532945077",
|
||||
ltr: true,
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||
<path d="M4 5c0-.6.4-1 1-1h3l2 5-2.5 1.5a12 12 0 0 0 5.5 5.5L18 18l5 2v3c0 .6-.4 1-1 1A17 17 0 0 1 4 5z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
en: "Email",
|
||||
fa: "آدرس الکترونیکی",
|
||||
value: "info@negindentalclinic.ir",
|
||||
href: "mailto:info@negindentalclinic.ir",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2" />
|
||||
<path d="m4 7 8 6 8-6" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
en="Contact Us"
|
||||
fa="ارتباط با ما"
|
||||
lead="نظرات خود را با ما در میان بگذارید…"
|
||||
/>
|
||||
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<h2 className="visually-hidden">راههای ارتباطی</h2>
|
||||
<div className={styles.infoGrid}>
|
||||
{INFO.map((i) => (
|
||||
<article key={i.en} className={styles.infoCard}>
|
||||
<span className={styles.infoIcon}>{i.icon}</span>
|
||||
<span className="card__en">{i.en}</span>
|
||||
<h3>{i.fa}</h3>
|
||||
{i.href ? (
|
||||
<a href={i.href} dir={i.ltr ? "ltr" : undefined}>
|
||||
{i.value}
|
||||
</a>
|
||||
) : (
|
||||
<p dir={i.ltr ? "ltr" : undefined}>{i.value}</p>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.formWrap}>
|
||||
<div className="sec-head" style={{ marginBottom: 32 }}>
|
||||
<span className="sec-head__en">Share your comments with us</span>
|
||||
<h2 className="sec-head__fa">ارسال پیام</h2>
|
||||
</div>
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.mapSection}>
|
||||
<iframe
|
||||
title="نقشه کلینیک دندانپزشکی نگین"
|
||||
src="https://www.google.com/maps?q=%D8%A8%D9%84%D9%88%D8%A7%D8%B1%20%D8%A7%D9%85%DB%8C%D9%86%20%D9%82%D9%85&output=embed"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/* ==========================================================================
|
||||
Negin Dental Clinic — global styles
|
||||
Palette extracted from the original site (negindentalclinic.ir)
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
--brand: #3b559a; /* primary blue (site accent) */
|
||||
--brand-dark: #2c4380;
|
||||
--cyan: #29abe2; /* logo cyan (accents/borders) */
|
||||
--cyan-soft: #6ec9ef;
|
||||
--cyan-deep: #1c7ba6; /* darker blue — used for the light-blue buttons */
|
||||
--en-accent: #22356b; /* darker blue for Latin/English captions & titles */
|
||||
|
||||
--text: #5c6873; /* body copy */
|
||||
--muted: #818695; /* secondary copy */
|
||||
--heading: #333232; /* dark headings */
|
||||
|
||||
--bg: #ffffff;
|
||||
--bg-tint: #eff6fa; /* pale blue section background */
|
||||
--bg-tint-2: #f7fcff;
|
||||
--bg-grey: #f7f7f7;
|
||||
--border: #e6ebf1;
|
||||
|
||||
--radius: 14px;
|
||||
--radius-lg: 28px;
|
||||
--shadow-sm: 0 6px 18px rgba(43, 67, 128, 0.08);
|
||||
--shadow-md: 0 18px 45px rgba(43, 67, 128, 0.12);
|
||||
|
||||
--container: 1180px;
|
||||
--header-h: 92px;
|
||||
|
||||
--font-fa: var(--font-vazirmatn), "Vazirmatn", Tahoma, sans-serif;
|
||||
--font-en: var(--font-cinzel), "Cinzel", "Times New Roman", serif;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--font-fa);
|
||||
font-size: 16px;
|
||||
line-height: 2;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: var(--heading);
|
||||
line-height: 1.6;
|
||||
margin: 0 0 0.6em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1.2em;
|
||||
text-align: justify;
|
||||
text-align-last: right;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* form controls don't inherit the page font by default in most browsers */
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Latin type — the site brand language for English captions/titles */
|
||||
.en,
|
||||
.en-title {
|
||||
font-family: var(--font-en);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* ---------- layout helpers ---------- */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: var(--container);
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 90px 0;
|
||||
}
|
||||
|
||||
.section--tint {
|
||||
background: var(--bg-tint);
|
||||
}
|
||||
|
||||
.section--grey {
|
||||
background: var(--bg-grey);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.grid--2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.grid--3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- section heading (bilingual, like the original) ---------- */
|
||||
.sec-head {
|
||||
text-align: center;
|
||||
max-width: 760px;
|
||||
margin: 0 auto 54px;
|
||||
}
|
||||
|
||||
.sec-head__en {
|
||||
display: block;
|
||||
font-family: var(--font-en);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--en-accent);
|
||||
font-size: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sec-head__fa {
|
||||
font-size: 30px;
|
||||
color: var(--heading);
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.sec-head__lead {
|
||||
color: var(--muted);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
.sec-head::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
background: var(--brand);
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
/* ---------- buttons ---------- */
|
||||
.btn {
|
||||
--_bg: var(--brand);
|
||||
--_fg: #fff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 13px 30px;
|
||||
border-radius: 999px;
|
||||
background: var(--_bg);
|
||||
color: var(--_fg);
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
border: 2px solid var(--_bg);
|
||||
cursor: pointer;
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: var(--_fg);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.btn--cyan {
|
||||
--_bg: var(--cyan-deep);
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
--_bg: transparent;
|
||||
--_fg: var(--brand);
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.btn--ghost:hover {
|
||||
--_fg: #fff;
|
||||
background: var(--brand);
|
||||
}
|
||||
|
||||
/* ---------- cards ---------- */
|
||||
.card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 34px 28px;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.card__icon {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
margin: 0 auto 14px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-tint);
|
||||
}
|
||||
|
||||
.card__icon img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.card__en {
|
||||
display: block;
|
||||
font-family: var(--font-en);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--en-accent);
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card__title {
|
||||
font-size: 20px;
|
||||
margin: 2px 0 12px;
|
||||
}
|
||||
|
||||
.card__text {
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
text-align: justify;
|
||||
text-align-last: right;
|
||||
}
|
||||
|
||||
/* ---------- utilities ---------- */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* keep numeric strings (phone numbers, etc.) in left-to-right order inside RTL text */
|
||||
.ltr,
|
||||
[dir="ltr"] {
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 18px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.section {
|
||||
padding: 60px 0;
|
||||
}
|
||||
.sec-head__fa {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- scroll-in sequence animation (see components/Reveal.js) ---------- */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transition: opacity 0.7s cubic-bezier(0.16, 0.8, 0.24, 1) var(--reveal-delay, 0ms),
|
||||
transform 0.7s cubic-bezier(0.16, 0.8, 0.24, 1) var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
.reveal--up {
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
.reveal--down {
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
|
||||
.reveal--left {
|
||||
transform: translateX(-36px);
|
||||
}
|
||||
|
||||
.reveal--right {
|
||||
transform: translateX(36px);
|
||||
}
|
||||
|
||||
.reveal--scale {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.reveal--in {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal {
|
||||
transition: none;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Cinzel, Vazirmatn } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { getFavicon } from "@/lib/meshkee";
|
||||
|
||||
const DEFAULT_ICON = "/assets/img/Header/header.png";
|
||||
|
||||
const vazirmatn = Vazirmatn({
|
||||
subsets: ["arabic", "latin"],
|
||||
weight: ["300", "400", "500", "700", "800"],
|
||||
variable: "--font-vazirmatn",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const cinzel = Cinzel({
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "500", "600", "700"],
|
||||
variable: "--font-cinzel",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export async function generateMetadata() {
|
||||
// "read favicon from backend, if not exist use default"
|
||||
const favicon = await getFavicon();
|
||||
const icon = favicon?.faviconUrl || DEFAULT_ICON;
|
||||
|
||||
return {
|
||||
metadataBase: new URL("https://www.negindentalclinic.ir"),
|
||||
title: {
|
||||
default: "کلینیک دندانپزشکی نگین | Negin Dental Clinic",
|
||||
template: "%s | کلینیک دندانپزشکی نگین",
|
||||
},
|
||||
description:
|
||||
"کلینیک دندانپزشکی نگین در قم، ارائهدهنده کلیه خدمات دندانپزشکی از جمله ایمپلنت، ارتودنسی نامرئی (الاینر) و خدمات زیبایی توسط دندانپزشکان مجرب و متخصص.",
|
||||
keywords: [
|
||||
"کلینیک دندانپزشکی نگین",
|
||||
"دندانپزشکی قم",
|
||||
"ایمپلنت",
|
||||
"ارتودنسی نامرئی",
|
||||
"الاینر",
|
||||
"دندانپزشک متخصص",
|
||||
],
|
||||
icons: {
|
||||
icon,
|
||||
},
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "fa_IR",
|
||||
siteName: "کلینیک دندانپزشکی نگین",
|
||||
title: "کلینیک دندانپزشکی نگین | Negin Dental Clinic",
|
||||
description:
|
||||
"ارائه کلیه خدمات دندانپزشکی توسط دندانپزشکان مجرب و متخصصان عضو هیئت علمی دانشکده دندانپزشکی.",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="fa" dir="rtl" className={`${vazirmatn.variable} ${cinzel.variable}`}>
|
||||
<body>
|
||||
{/* If JS never runs (blocked/failed), the scroll-reveal sequence on
|
||||
Home would otherwise leave content stuck at opacity:0 forever. */}
|
||||
<noscript>
|
||||
<style>{`.reveal { opacity: 1 !important; transform: none !important; }`}</style>
|
||||
</noscript>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { NextResponse } from "next/server";
|
||||
import { WEBSITE_DOMAIN } from "@/lib/meshkee";
|
||||
|
||||
// Published for the Meshkee business dashboard's "Refresh" sitemap button —
|
||||
// see AI_PROMPT.md ("What the website must publish"). Meshkee's own nginx
|
||||
// proxy serves the actual /sitemap.xml (and the CMS-driven child sitemaps
|
||||
// like /sitemap-blogs.xml) from the API; this route only lists this app's
|
||||
// *static* public pages so the dashboard can fold them into the sitemap
|
||||
// index. Do not add app/sitemap.ts / app/robots.ts — those paths are owned
|
||||
// by the Meshkee proxy once this site is live on a Meshkee-managed domain.
|
||||
|
||||
export const dynamic = "force-static";
|
||||
|
||||
// Route segments that never map to a crawlable public page: dynamic detail
|
||||
// pages (CMS-managed — Meshkee generates their own sitemap entries), and
|
||||
// this "meshkee/*" integration namespace itself.
|
||||
const EXCLUDE_SEGMENT = (segment) =>
|
||||
segment.startsWith("[") || segment === "meshkee" || segment.startsWith("_");
|
||||
|
||||
// Per-path SEO weight. Anything not listed falls back to a sane default.
|
||||
const PAGE_META = {
|
||||
"/": { changefreq: "daily", priority: 1.0 },
|
||||
"/services": { changefreq: "weekly", priority: 0.8 },
|
||||
"/about-us": { changefreq: "monthly", priority: 0.6 },
|
||||
"/blog": { changefreq: "daily", priority: 0.7 },
|
||||
"/contact-us": { changefreq: "monthly", priority: 0.5 },
|
||||
"/virtual-tour": { changefreq: "yearly", priority: 0.4 },
|
||||
};
|
||||
|
||||
/** Scan app/ for `page.js` files and turn each into a public URL path. */
|
||||
function scanStaticPages(dir, baseSegments = []) {
|
||||
const pages = [];
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
return pages;
|
||||
}
|
||||
|
||||
const hasPage = entries.some(
|
||||
(e) => e.isFile() && /^page\.(js|jsx|ts|tsx)$/.test(e.name)
|
||||
);
|
||||
if (hasPage && !baseSegments.some(EXCLUDE_SEGMENT)) {
|
||||
const urlPath = "/" + baseSegments.join("/");
|
||||
pages.push(urlPath === "/" ? "/" : urlPath.replace(/\/$/, ""));
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
// route groups "(name)" don't appear in the URL
|
||||
const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
||||
const nextSegments = isGroup
|
||||
? baseSegments
|
||||
: [...baseSegments, entry.name];
|
||||
if (EXCLUDE_SEGMENT(entry.name)) continue;
|
||||
pages.push(...scanStaticPages(path.join(dir, entry.name), nextSegments));
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const appDir = path.join(process.cwd(), "app");
|
||||
const paths = scanStaticPages(appDir).sort(
|
||||
(a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)
|
||||
);
|
||||
|
||||
const staticPages = paths.map((p) => ({
|
||||
path: p,
|
||||
...(PAGE_META[p] || { changefreq: "monthly", priority: 0.5 }),
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
baseUrl: `https://${WEBSITE_DOMAIN}`,
|
||||
staticPages,
|
||||
});
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { getBlogs, blogTitle, blogExcerpt, blogCover, blogDate, faDate, slugifyFa } from "@/lib/meshkee";
|
||||
import Reveal from "@/components/Reveal";
|
||||
import styles from "./page.module.css";
|
||||
|
||||
export const revalidate = 120;
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
en: "Appointment",
|
||||
fa: "نوبتدهی",
|
||||
text: "برای گرفتن نوبت از متخصصین کلینیک با ما تماس بگیرید.",
|
||||
cta: { label: "درخواست نوبت", href: "/contact-us" },
|
||||
icon: "/assets/img/icons/1.png",
|
||||
},
|
||||
{
|
||||
en: "Emergency Case",
|
||||
fa: "موارد اورژانسی",
|
||||
text: "در صورت بروز موارد اورژانسی با کلینیک نگین تماس بگیرید.",
|
||||
cta: { label: "۰۲۵ - ۳۲۹۴۵۰۷۷", href: "tel:+982532945077" },
|
||||
icon: "/assets/img/icons/2.png",
|
||||
},
|
||||
{
|
||||
en: "Our Doctors",
|
||||
fa: "پزشکان ما",
|
||||
text: "ارائه خدمات توسط دندانپزشکان مجرب و متخصصان عضو هیئت علمی دانشکده دندانپزشکی.",
|
||||
cta: { label: "معرفی پزشکان", href: "/about-us" },
|
||||
icon: "/assets/img/icons/3.png",
|
||||
},
|
||||
];
|
||||
|
||||
const SCHEDULE = [
|
||||
{ day: "شنبه", doctors: ["دکتر شجری", "دکتر صانعیپور", "دکتر دیناروند"] },
|
||||
{ day: "یکشنبه", doctors: ["دکتر صانعیپور", "دکتر دیناروند", "دکتر شجری"] },
|
||||
{ day: "دوشنبه", doctors: ["دکتر کریمی", "دکتر حسینی مطلق", "دکتر صانعیپور"] },
|
||||
{ day: "سهشنبه", doctors: ["دکتر شجری", "دکتر دیناروند", "دکتر رسولی"] },
|
||||
{ day: "چهارشنبه", doctors: ["دکتر صبری", "دکتر صانعیپور", "دکتر دیناروند"] },
|
||||
{ day: "پنجشنبه", doctors: ["دکتر صبری", "دکتر رسولی", "دکتر حسینی مطلق"] },
|
||||
];
|
||||
|
||||
const SERVICES = [
|
||||
{ en: "Prosthesis", fa: "پروتز", text: "روکش ثابت و متحرک، روکش ایمپلنت.", icon: "/assets/img/icons/4.png" },
|
||||
{ en: "Specialized", fa: "خدمات تخصصی", text: "خدمات تخصصی کودکان: کشیدن، ترمیم، عصبکشی، روکش استیل و فضانگهدارنده.", icon: "/assets/img/icons/5.png" },
|
||||
{ en: "Public", fa: "خدمات عمومی", text: "کشیدن، ترمیم (پر کردن)، جرمگیری، درمان ریشه و ساخت پروتز.", icon: "/assets/img/icons/6.png" },
|
||||
{ en: "Root Canal", fa: "درمان ریشه", text: "عصبکشی تخصصی شامل ریشههای عفونی، ریشههای سخت و کانالهای تنگ و باریک.", icon: "/assets/img/icons/7.png" },
|
||||
{ en: "Orthodontic", fa: "ارتودنسی", text: "ردیف کردن دندانها و ساخت پلاک مخصوص کودکان مبتلا به شکاف کام.", icon: "/assets/img/icons/8.png" },
|
||||
{ en: "Prio", fa: "پریو", text: "جراحیهای لثه، جراحیهای نرم و سخت، جراحی ایمپلنت و درمان بیماریهای لثه.", icon: "/assets/img/icons/9.png" },
|
||||
];
|
||||
|
||||
export default async function HomePage() {
|
||||
const { items: latestBlogs } = await getBlogs({ page: 1, pageSize: 3 });
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* ---------- hero ---------- */}
|
||||
<section className={styles.hero}>
|
||||
<div className={`container ${styles.heroInner}`}>
|
||||
<div className={styles.heroCopy}>
|
||||
<Reveal as="span" from="up" delay={0} className={`en-title ${styles.heroEn}`}>
|
||||
Negin Dental Clinic
|
||||
</Reveal>
|
||||
<Reveal as="h1" from="up" delay={110} className={styles.heroTitle}>
|
||||
کلینیک دندانپزشکی نگین
|
||||
</Reveal>
|
||||
<Reveal as="p" from="up" delay={220} className={styles.heroLead}>
|
||||
ارائهدهنده کلیات خدمات دندانپزشکی از جمله ایمپلنت، ارتودنسی شفاف
|
||||
(الاینر) و خدمات زیبایی، در محیطی آرام و مناسب برای بانوان و کودکان.
|
||||
</Reveal>
|
||||
<Reveal as="div" from="up" delay={330} className={styles.heroActions}>
|
||||
<Link href="/contact-us" className="btn">
|
||||
لیست نوبتدهی
|
||||
</Link>
|
||||
<Link href="/virtual-tour" className="btn btn--ghost">
|
||||
سفر به کلینیک
|
||||
</Link>
|
||||
</Reveal>
|
||||
</div>
|
||||
<Reveal as="div" from="scale" delay={200} className={styles.heroArt}>
|
||||
<Image
|
||||
src="/assets/img/slider-1.png"
|
||||
alt="خدمات دندانپزشکی کلینیک نگین"
|
||||
width={754}
|
||||
height={810}
|
||||
priority
|
||||
/>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- feature cards ---------- */}
|
||||
<section className={styles.features}>
|
||||
<h2 className="visually-hidden">دسترسی سریع</h2>
|
||||
<div className={`container grid grid--3`}>
|
||||
{FEATURES.map((f, i) => (
|
||||
<Reveal as="article" from="up" delay={i * 110} key={f.en} className="card">
|
||||
<div className="card__icon">
|
||||
<Image src={f.icon} alt="" width={60} height={60} />
|
||||
</div>
|
||||
<span className="card__en">{f.en}</span>
|
||||
<h3 className="card__title">{f.fa}</h3>
|
||||
<p className="card__text">{f.text}</p>
|
||||
<Link
|
||||
href={f.cta.href}
|
||||
className={styles.cardLink}
|
||||
dir={f.cta.href.startsWith("tel:") ? "ltr" : undefined}
|
||||
>
|
||||
{f.cta.label}
|
||||
</Link>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- weekly schedule ---------- */}
|
||||
<section className="section section--tint">
|
||||
<div className="container">
|
||||
<Reveal as="div" from="up" className="sec-head">
|
||||
<span className="sec-head__en">Weekly Schedule</span>
|
||||
<h2 className="sec-head__fa">خدمات ما در کلینیک</h2>
|
||||
<p className="sec-head__lead">
|
||||
برای درمانهای خاص، به جدول زمانبندی حضور متخصصین در کلینیک توجه کنید.
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
<Reveal as="div" from="up" delay={120} className={styles.tableWrap}>
|
||||
<table className={styles.schedule}>
|
||||
<thead>
|
||||
<tr>
|
||||
{SCHEDULE.map((s) => (
|
||||
<th key={s.day}>{s.day}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{SCHEDULE.map((s) => (
|
||||
<td key={s.day}>
|
||||
{s.doctors.map((d) => (
|
||||
<span key={d}>{d}</span>
|
||||
))}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- info: Negin Dental Clinic ---------- */}
|
||||
<section className="section">
|
||||
<div className={`container ${styles.split}`}>
|
||||
<Reveal as="div" from="right" className={styles.splitArt}>
|
||||
<Image src="/assets/img/info3-2.jpg" alt="کلینیک دندانپزشکی نگین" width={720} height={540} />
|
||||
</Reveal>
|
||||
<Reveal as="div" from="left" delay={120} className={styles.splitCopy}>
|
||||
<span className="sec-head__en">Negin Dental Clinic</span>
|
||||
<h2>دندانپزشکی نگین</h2>
|
||||
<p>
|
||||
این مرکز در تمام ایام هفته با همراهی پزشکان متعهد و مجرب، در زمینه
|
||||
تشخیص و درمان مشکلات و دردهای ناگهانی دندان در خدمت مادران و فرزندان
|
||||
میباشد. لذا برای درمانهای خاص به جدول زمانبندی حضور متخصصین در
|
||||
کلینیک توجه کنید.
|
||||
</p>
|
||||
<Link href="/services" className="btn btn--cyan">
|
||||
مشاهده خدمات
|
||||
</Link>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- info: About Negin Dental ---------- */}
|
||||
<section className="section section--grey">
|
||||
<div className={`container ${styles.split} ${styles.splitReverse}`}>
|
||||
<Reveal as="div" from="left" className={styles.splitArt}>
|
||||
<Image src="/assets/img/info3-3.jpg" alt="درباره کلینیک نگین" width={720} height={540} />
|
||||
</Reveal>
|
||||
<Reveal as="div" from="right" delay={120} className={styles.splitCopy}>
|
||||
<span className="sec-head__en">About Negin Dental</span>
|
||||
<h2>درباره کلینیک نگین</h2>
|
||||
<p>
|
||||
با شمارههای کلینیک نگین تماس بگیرید و بدون حضور در کلینیک، نوبت خود
|
||||
را با دکتر مورد نظر هماهنگ کنید. کادر مجرب دندانپزشکی و استفاده از
|
||||
مواد با کیفیت، از مزیتهای بارز کلینیک نگین است.
|
||||
</p>
|
||||
<Link href="/about-us" className="btn btn--ghost">
|
||||
درباره ما
|
||||
</Link>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- services ---------- */}
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<Reveal as="div" from="up" className="sec-head">
|
||||
<span className="sec-head__en">Services</span>
|
||||
<h2 className="sec-head__fa">خدمات ما در کلینیک</h2>
|
||||
<p className="sec-head__lead">
|
||||
ارائه کلیه خدمات دندانپزشکی توسط دندانپزشکان مجرب و متخصصان عضو هیئت
|
||||
علمی دانشکده دندانپزشکی، با بهرهگیری از تجهیزات پیشرفته و بهروز دنیا.
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid grid--3">
|
||||
{SERVICES.map((s, i) => (
|
||||
<Reveal as="article" from="up" delay={(i % 3) * 110} key={s.en} className="card">
|
||||
<div className="card__icon">
|
||||
<Image src={s.icon} alt="" width={60} height={60} />
|
||||
</div>
|
||||
<span className="card__en">{s.en}</span>
|
||||
<h3 className="card__title">{s.fa}</h3>
|
||||
<p className="card__text">{s.text}</p>
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-center" style={{ marginTop: 44 }}>
|
||||
<Link href="/services" className="btn">
|
||||
لیست کامل خدمات
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ---------- latest blog posts ---------- */}
|
||||
{latestBlogs.length > 0 ? (
|
||||
<section className="section section--tint">
|
||||
<div className="container">
|
||||
<Reveal as="div" from="up" className="sec-head">
|
||||
<span className="sec-head__en">Blog</span>
|
||||
<h2 className="sec-head__fa">آخرین مقالات</h2>
|
||||
</Reveal>
|
||||
|
||||
<div className="grid grid--3">
|
||||
{latestBlogs.map((blog, i) => {
|
||||
const title = blogTitle(blog);
|
||||
const cover = blogCover(blog);
|
||||
const href = `/blog/${blog.id}/${slugifyFa(title)}`;
|
||||
return (
|
||||
<Reveal as="article" from="up" delay={i * 110} key={blog.id} className={styles.blogCard}>
|
||||
<Link href={href} className={styles.blogCardArt}>
|
||||
{cover ? (
|
||||
<Image src={cover} alt={title} width={420} height={260} />
|
||||
) : (
|
||||
<span className={styles.blogCardArtFallback} aria-hidden="true" />
|
||||
)}
|
||||
</Link>
|
||||
<div className={styles.blogCardBody}>
|
||||
{blogDate(blog) ? (
|
||||
<span className={styles.blogCardDate}>{faDate(blogDate(blog))}</span>
|
||||
) : null}
|
||||
<h3>
|
||||
<Link href={href}>{title}</Link>
|
||||
</h3>
|
||||
{blogExcerpt(blog) ? <p>{blogExcerpt(blog)}</p> : null}
|
||||
</div>
|
||||
</Reveal>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="text-center" style={{ marginTop: 44 }}>
|
||||
<Link href="/blog" className="btn btn--ghost">
|
||||
همه مقالات
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{/* ---------- call CTA ---------- */}
|
||||
<section className={styles.cta}>
|
||||
<div className={`container ${styles.ctaInner}`}>
|
||||
<Reveal as="div" from="right">
|
||||
<span className="en-title" style={{ color: "var(--cyan-soft)" }}>
|
||||
Call Us
|
||||
</span>
|
||||
<h2 className={styles.ctaTitle}>با ما در ارتباط باشید</h2>
|
||||
<p className={styles.ctaText}>
|
||||
در صورت موارد اورژانسی و نوبتدهی با ما تماس بگیرید. همکاران مستقر در
|
||||
کلینیک، پروندهای شامل اطلاعات شناسایی اولیه برای شما تشکیل میدهند و
|
||||
سپس شما را به بخش تشخیص بیماریهای دهان و دندان هدایت میکنند.
|
||||
</p>
|
||||
</Reveal>
|
||||
<Reveal
|
||||
as="a"
|
||||
from="left"
|
||||
delay={150}
|
||||
dir="ltr"
|
||||
href="tel:+982532945077"
|
||||
className={styles.ctaPhone}
|
||||
>
|
||||
۰۲۵ - ۳۲۹۴۵۰۷۷
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/* ---------- hero ---------- */
|
||||
.hero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(60% 80% at 85% 20%, rgba(41, 171, 226, 0.12), transparent 70%),
|
||||
linear-gradient(180deg, #f7fcff 0%, #eef6fb 100%);
|
||||
}
|
||||
|
||||
.heroInner {
|
||||
display: grid;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
padding: 70px 24px 80px;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
.heroInner {
|
||||
grid-template-columns: 1.05fr 0.95fr;
|
||||
padding: 90px 24px 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.heroEn {
|
||||
color: var(--en-accent);
|
||||
font-size: 18px;
|
||||
letter-spacing: 0.16em;
|
||||
}
|
||||
|
||||
.heroTitle {
|
||||
font-size: 44px;
|
||||
line-height: 1.35;
|
||||
margin: 12px 0 18px;
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
.heroLead {
|
||||
font-size: 18px;
|
||||
color: var(--muted);
|
||||
max-width: 480px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.heroActions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.heroArt {
|
||||
position: relative;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.heroArt img {
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
height: auto;
|
||||
filter: drop-shadow(0 30px 50px rgba(43, 67, 128, 0.18));
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.heroTitle {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- feature cards ---------- */
|
||||
.features {
|
||||
margin-top: -50px;
|
||||
padding-bottom: 20px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.cardLink {
|
||||
display: inline-block;
|
||||
margin-top: 16px;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.cardLink::after {
|
||||
content: " ←";
|
||||
}
|
||||
|
||||
/* ---------- schedule table ---------- */
|
||||
.tableWrap {
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.schedule {
|
||||
width: 100%;
|
||||
min-width: 720px;
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.schedule th {
|
||||
background: var(--brand);
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
padding: 16px 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.schedule td {
|
||||
padding: 22px 10px;
|
||||
border: 1px solid var(--border);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.schedule td span {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.schedule td span + span {
|
||||
border-top: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
/* ---------- split info sections ---------- */
|
||||
.split {
|
||||
display: grid;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.split {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.splitReverse .splitArt {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.splitArt img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.splitCopy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.splitCopy h2 {
|
||||
font-size: 28px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
/* push the section CTA to the left edge of the text column */
|
||||
.splitCopy :global(.btn) {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
/* ---------- call CTA ---------- */
|
||||
.cta {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(44, 67, 128, 0.9), rgba(44, 67, 128, 0.9)),
|
||||
url("/assets/img/info_items_2/bg_img.jpg") center / cover no-repeat fixed;
|
||||
color: #fff;
|
||||
padding: 80px 0;
|
||||
}
|
||||
|
||||
.ctaInner {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.ctaInner {
|
||||
grid-template-columns: 1.6fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.ctaTitle {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
margin: 8px 0 14px;
|
||||
}
|
||||
|
||||
.ctaText {
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
margin: 0;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.ctaPhone {
|
||||
justify-self: start;
|
||||
font-family: var(--font-en);
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: #fff;
|
||||
padding: 18px 34px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 999px;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.ctaPhone {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
.ctaPhone:hover {
|
||||
background: #fff;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* ---------- latest blog cards ---------- */
|
||||
.blogCard {
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.blogCard:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.blogCardArt {
|
||||
display: block;
|
||||
aspect-ratio: 16 / 10;
|
||||
background: var(--bg-tint);
|
||||
}
|
||||
|
||||
.blogCardArt img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.blogCardArtFallback {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, var(--brand), var(--cyan));
|
||||
opacity: 0.16;
|
||||
}
|
||||
|
||||
.blogCardBody {
|
||||
padding: 22px 24px 26px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.blogCardDate {
|
||||
font-size: 12px;
|
||||
color: var(--cyan-deep);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.blogCardBody h3 {
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.blogCardBody p {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import styles from "./services.module.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "خدمات کلینیک",
|
||||
description:
|
||||
"کلیه خدمات دندانپزشکی کلینیک نگین: پروتز، ارتودنسی، درمان ریشه، جراحی ایمپلنت، ترمیم و زیبایی، خدمات کودکان و مشاوره آنلاین.",
|
||||
};
|
||||
|
||||
const SERVICES = [
|
||||
{ en: "Prosthesis", fa: "پروتز", text: "روکش ثابت و متحرک، روکش ایمپلنت.", icon: "/assets/img/icons/1.png" },
|
||||
{ en: "Specialized", fa: "خدمات تخصصی", text: "خدمات تخصصی کودکان شامل کشیدن، ترمیم، عصبکشی، روکش استیل و فضانگهدارنده.", icon: "/assets/img/icons/2.png" },
|
||||
{ en: "Public", fa: "خدمات عمومی", text: "کشیدن، ترمیم (پر کردن)، جرمگیری، درمان ریشه (عصبکشی) و پروتز (ساخت روکش و دندان مصنوعی).", icon: "/assets/img/icons/3.png" },
|
||||
{ en: "Root Canal", fa: "درمان ریشه", text: "عصبکشی تخصصی شامل ریشههای عفونی، ریشههای سخت و کانالهای تنگ و باریک.", icon: "/assets/img/icons/4.png" },
|
||||
{ en: "Orthodontic", fa: "ارتودنسی", text: "ردیف کردن دندانها و ساخت پلاک مخصوص کودکان مبتلا به شکاف کام.", icon: "/assets/img/icons/5.png" },
|
||||
{ en: "Prio", fa: "پریو", text: "جراحیهای لثه، جراحیهای نرم و سخت، جراحی ایمپلنت و درمان بیماریهای لثه.", icon: "/assets/img/icons/6.png" },
|
||||
{ en: "Composite Veneer", fa: "ترمیم تخصصی", text: "ترمیمی و زیبایی شامل ونیر، لمینیت، ترمیم دندانهای آسیبدیده و سفید کردن دندانها (بلیچینگ).", icon: "/assets/img/icons/7.png" },
|
||||
{ en: "Kids Teeth", fa: "کودکان", text: "ارائه کلیه خدمات دندانپزشکی توسط متخصصین باتجربه کودکان.", icon: "/assets/img/icons/8.png" },
|
||||
{ en: "Implant Surgery", fa: "جراحی ایمپلنت", text: "جراحی ایمپلنت دندان دیجیتال، جراحی با حداقل برش.", icon: "/assets/img/icons/9.png" },
|
||||
];
|
||||
|
||||
export default function ServicesPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
en="Services"
|
||||
fa="خدمات"
|
||||
lead="با خدمات ارائهشده توسط کلینیک بیشتر آشنا شوید."
|
||||
/>
|
||||
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<span className="sec-head__en">Our Services</span>
|
||||
<h2 className="sec-head__fa">خدمات ما در کلینیک</h2>
|
||||
<p className="sec-head__lead">
|
||||
ارائه کلیه خدمات دندانپزشکی توسط دندانپزشکان مجرب و متخصصان عضو هیئت
|
||||
علمی دانشکده دندانپزشکی، با بهرهگیری از تجهیزات پیشرفته و بهروز دنیا.
|
||||
پس با خیالی آسوده به ما مراجعه کنید…
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid--3">
|
||||
{SERVICES.map((s) => (
|
||||
<article key={s.en} className="card">
|
||||
<div className="card__icon">
|
||||
<Image src={s.icon} alt="" width={60} height={60} />
|
||||
</div>
|
||||
<span className="card__en">{s.en}</span>
|
||||
<h3 className="card__title">{s.fa}</h3>
|
||||
<p className="card__text">{s.text}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section section--tint">
|
||||
<div className={`container ${styles.split}`}>
|
||||
<div className={styles.art}>
|
||||
<Image
|
||||
src="/assets/img/info3-2.jpg"
|
||||
alt="مشاوره دندانپزشکی از راه دور"
|
||||
width={720}
|
||||
height={540}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="sec-head__en">Consultation</span>
|
||||
<h2>مشاوره</h2>
|
||||
<p>
|
||||
خدمت مشاوره از راه دور یا مشاوره آنلاین، از خدماتی است که سالهای
|
||||
متمادی است در کشورهای مختلف در حال تجربه است. بسیاری از کشورها
|
||||
توانستهاند با این امکان، بخش زیادی از مراجعات غیرضروری به پزشکها و
|
||||
کلینیکها را حذف کنند.
|
||||
</p>
|
||||
<h3 style={{ marginTop: 28 }}>خدمات حضوری</h3>
|
||||
<p>
|
||||
کلینیک نگین با بهرهگیری از متخصصین مجرب و محیطی آرام و مناسب برای
|
||||
بانوان و کودکان، آماده پاسخگویی و ارائه خدمت به مراجعین محترم میباشد.
|
||||
خدمات این کلینیک اعم از خدمات تخصصی و عمومی، ویژه بانوان و کودکان است.
|
||||
</p>
|
||||
<Link href="/contact-us" className="btn btn--cyan">
|
||||
درخواست نوبت
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
.split {
|
||||
display: grid;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.split {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.art {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.split > div:not(.art) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.split h2 {
|
||||
font-size: 28px;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.split h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* push the section CTA to the left edge of the text column */
|
||||
.split :global(.btn) {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.art img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import PageHero from "@/components/PageHero";
|
||||
import styles from "./virtual-tour.module.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "تور مجازی",
|
||||
description:
|
||||
"پیش از مراجعه حضوری، فضای داخلی کلینیک دندانپزشکی نگین در قم را با تور مجازی ۳۶۰ درجه مشاهده کنید و از امکانات و محیط درمانی کلینیک آگاه شوید.",
|
||||
};
|
||||
|
||||
export default function VirtualTourPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHero
|
||||
en="Virtual Tour"
|
||||
fa="تور مجازی"
|
||||
lead="سفر مجازی به کلینیک دندانپزشکی نگین."
|
||||
/>
|
||||
|
||||
<section className="section">
|
||||
<div className={`container ${styles.wrap}`}>
|
||||
<div className={styles.frame}>
|
||||
<iframe
|
||||
title="تور مجازی کلینیک نگین"
|
||||
src="https://www.negindentalclinic.ir/assets/virtualtour.html"
|
||||
loading="lazy"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
<p className={styles.note}>
|
||||
در صورتی که تور مجازی بارگذاری نشد، میتوانید آن را{" "}
|
||||
<a
|
||||
href="https://www.negindentalclinic.ir/assets/virtualtour.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
در یک صفحه جدید
|
||||
</a>{" "}
|
||||
مشاهده کنید.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
.wrap {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.frame {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--bg-tint);
|
||||
}
|
||||
|
||||
.frame iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.note a {
|
||||
color: var(--brand);
|
||||
font-weight: 700;
|
||||
}
|
||||
Reference in New Issue
Block a user