انتقال تمام محتوای متنی سایت به دیتابیس — همه متن‌های فرانت‌اند از GET /api/site خوانده می‌شوند

This commit is contained in:
MMD
2026-09-06 20:16:17 +03:30
parent 8bb303dda2
commit 1fde4e30fc
36 changed files with 1096 additions and 454 deletions
+39
View File
@@ -14,17 +14,43 @@ from ..schemas import (
BeforeAfterOut,
BrandIn,
BrandOut,
ContactSectionIn,
ContactSectionOut,
ContactMessageOut,
FaqIn,
FaqOut,
FooterIn,
FooterOut,
GalleryImageIn,
GalleryImageOut,
HeroIn,
HeroOut,
InvestmentModelIn,
InvestmentModelOut,
LoginIn,
MessageReadIn,
NavItemIn,
NavItemOut,
NewsletterSectionIn,
NewsletterOut,
OfficeIn,
OfficeOut,
PageHeroIn,
PageHeroOut,
ProcessStepIn,
ProcessStepOut,
ProjectIn,
ProjectOut,
SectionHeadingIn,
SectionHeadingOut,
ServiceIn,
ServiceOut,
StatIn,
StatOut,
StatusLabelIn,
StatusLabelOut,
StoryIn,
StoryOut,
SubscriberOut,
TeamMemberIn,
TeamMemberOut,
@@ -107,6 +133,11 @@ def register_crud(path: str, model, out_schema, create_schema) -> None:
# CRUD محتوا
register_crud("brand", models.Brand, BrandOut, BrandIn)
register_crud("nav", models.NavItem, NavItemOut, NavItemIn)
register_crud("hero", models.Hero, HeroOut, HeroIn)
register_crud("story", models.StorySection, StoryOut, StoryIn)
register_crud("section-headings", models.SectionHeadingContent, SectionHeadingOut, SectionHeadingIn)
register_crud("page-heroes", models.PageHeroContent, PageHeroOut, PageHeroIn)
register_crud("services", models.Service, ServiceOut, ServiceIn)
register_crud("projects", models.Project, ProjectOut, ProjectIn)
register_crud("articles", models.Article, ArticleOut, ArticleIn)
@@ -117,6 +148,14 @@ register_crud("timeline", models.TimelineItem, TimelineItemOut, TimelineItemIn)
register_crud("stats", models.Stat, StatOut, StatIn)
register_crud("why-us", models.WhyUsItem, WhyUsItemOut, WhyUsItemIn)
register_crud("before-after", models.BeforeAfter, BeforeAfterOut, BeforeAfterIn)
register_crud("investment-models", models.InvestmentModel, InvestmentModelOut, InvestmentModelIn)
register_crud("process-steps", models.ProcessStep, ProcessStepOut, ProcessStepIn)
register_crud("offices", models.Office, OfficeOut, OfficeIn)
register_crud("gallery", models.GalleryImage, GalleryImageOut, GalleryImageIn)
register_crud("status-labels", models.StatusLabel, StatusLabelOut, StatusLabelIn)
register_crud("contact-section", models.ContactSectionContent, ContactSectionOut, ContactSectionIn)
register_crud("newsletter", models.NewsletterSectionContent, NewsletterOut, NewsletterSectionIn)
register_crud("footer", models.FooterContent, FooterOut, FooterIn)
# پیام‌های تماس
+13
View File
@@ -109,6 +109,11 @@ def get_site(db: Session = Depends(get_db)):
"""همه محتوای سایت در یک درخواست — مناسب برای بارگذاری اولیه فرانت‌اند."""
return {
"brand": db.query(models.Brand).first(),
"nav": db.query(models.NavItem).order_by(models.NavItem.id).all(),
"hero": db.query(models.Hero).first(),
"story": db.query(models.StorySection).first(),
"section_headings": db.query(models.SectionHeadingContent).order_by(models.SectionHeadingContent.id).all(),
"page_heroes": db.query(models.PageHeroContent).order_by(models.PageHeroContent.id).all(),
"services": db.query(models.Service).all(),
"projects": db.query(models.Project).order_by(models.Project.id).all(),
"articles": db.query(models.Article).order_by(models.Article.id).all(),
@@ -119,4 +124,12 @@ def get_site(db: Session = Depends(get_db)):
"stats": db.query(models.Stat).all(),
"why_us": db.query(models.WhyUsItem).all(),
"before_after": db.query(models.BeforeAfter).first(),
"investment_models": db.query(models.InvestmentModel).order_by(models.InvestmentModel.id).all(),
"process_steps": db.query(models.ProcessStep).order_by(models.ProcessStep.id).all(),
"offices": db.query(models.Office).order_by(models.Office.id).all(),
"gallery": db.query(models.GalleryImage).order_by(models.GalleryImage.id).all(),
"status_labels": db.query(models.StatusLabel).order_by(models.StatusLabel.id).all(),
"contact_section": db.query(models.ContactSectionContent).first(),
"newsletter": db.query(models.NewsletterSectionContent).first(),
"footer": db.query(models.FooterContent).first(),
}
+173
View File
@@ -139,6 +139,179 @@ class BeforeAfter(Base):
after_label: Mapped[str] = mapped_column(String(128), default="")
class NavItem(Base):
__tablename__ = "nav_items"
id: Mapped[int] = mapped_column(primary_key=True)
label: Mapped[str] = mapped_column(String(64))
href: Mapped[str] = mapped_column(String(128))
class Hero(Base):
"""محتوای هیرو صفحه اصلی — رکورد یگانه."""
__tablename__ = "hero"
id: Mapped[int] = mapped_column(primary_key=True)
kicker: Mapped[str] = mapped_column(Text, default="")
title: Mapped[str] = mapped_column(Text, default="")
title_highlight: Mapped[str] = mapped_column(String(128), default="")
subtitle: Mapped[str] = mapped_column(Text, default="")
badge_value: Mapped[str] = mapped_column(String(32), default="")
badge_label: Mapped[str] = mapped_column(Text, default="")
cta_primary_label: Mapped[str] = mapped_column(String(128), default="")
cta_primary_href: Mapped[str] = mapped_column(String(128), default="")
cta_secondary_label: Mapped[str] = mapped_column(String(128), default="")
cta_secondary_href: Mapped[str] = mapped_column(String(128), default="")
scroll_label: Mapped[str] = mapped_column(String(64), default="")
class StorySection(Base):
"""بخش «درباره ما» در خانه و صفحه درباره — رکورد یگانه."""
__tablename__ = "story"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(256), default="")
paragraphs: Mapped[list] = mapped_column(JSON, default=list)
quote: Mapped[str] = mapped_column(Text, default="")
quote_author: Mapped[str] = mapped_column(Text, default="")
stat1_value: Mapped[str] = mapped_column(String(32), default="")
stat1_label: Mapped[str] = mapped_column(String(128), default="")
stat2_value: Mapped[str] = mapped_column(String(32), default="")
stat2_label: Mapped[str] = mapped_column(String(128), default="")
link_label: Mapped[str] = mapped_column(String(128), default="")
link_href: Mapped[str] = mapped_column(String(128), default="")
bento: Mapped[list] = mapped_column(JSON, default=list) # [{src, alt}]
class SectionHeadingContent(Base):
"""تیتر و توضیح بخش‌ها — فرانت‌اند بر اساس key آن را پیدا می‌کند."""
__tablename__ = "section_headings"
id: Mapped[int] = mapped_column(primary_key=True)
key: Mapped[str] = mapped_column(String(64), unique=True, index=True)
label: Mapped[str] = mapped_column(String(64), default="")
title: Mapped[str] = mapped_column(String(256), default="")
lead: Mapped[str] = mapped_column(Text, default="")
image: Mapped[str] = mapped_column(Text, default="")
cta_label: Mapped[str] = mapped_column(String(128), default="")
cta_href: Mapped[str] = mapped_column(String(128), default="")
class PageHeroContent(Base):
"""هیرو بالای هر صفحه — key: about | services | projects | investment | blog | faq | contact."""
__tablename__ = "page_heroes"
id: Mapped[int] = mapped_column(primary_key=True)
key: Mapped[str] = mapped_column(String(32), unique=True, index=True)
title: Mapped[str] = mapped_column(String(256), default="")
lead: Mapped[str] = mapped_column(Text, default="")
image: Mapped[str] = mapped_column(Text, default="")
class InvestmentModel(Base):
__tablename__ = "investment_models"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(256))
desc: Mapped[str] = mapped_column(Text, default="")
points: Mapped[list] = mapped_column(JSON, default=list)
class ProcessStep(Base):
__tablename__ = "process_steps"
id: Mapped[int] = mapped_column(primary_key=True)
step: Mapped[str] = mapped_column(String(16), default="")
title: Mapped[str] = mapped_column(String(256))
desc: Mapped[str] = mapped_column(Text, default="")
class Office(Base):
__tablename__ = "offices"
id: Mapped[int] = mapped_column(primary_key=True)
city: Mapped[str] = mapped_column(String(64))
address: Mapped[str] = mapped_column(Text, default="")
hours: Mapped[str] = mapped_column(String(128), default="")
class GalleryImage(Base):
__tablename__ = "gallery_images"
id: Mapped[int] = mapped_column(primary_key=True)
src: Mapped[str] = mapped_column(Text, default="")
alt: Mapped[str] = mapped_column(Text, default="")
class StatusLabel(Base):
"""برچسب فارسی وضعیت پروژه — key: done | progress."""
__tablename__ = "status_labels"
id: Mapped[int] = mapped_column(primary_key=True)
key: Mapped[str] = mapped_column(String(16), unique=True, index=True)
label: Mapped[str] = mapped_column(String(64))
class ContactSectionContent(Base):
"""بخش فرم تماس — رکورد یگانه."""
__tablename__ = "contact_section"
id: Mapped[int] = mapped_column(primary_key=True)
label: Mapped[str] = mapped_column(String(64), default="")
title: Mapped[str] = mapped_column(String(256), default="")
lead: Mapped[str] = mapped_column(Text, default="")
address_label: Mapped[str] = mapped_column(String(64), default="")
phone_label: Mapped[str] = mapped_column(String(64), default="")
email_label: Mapped[str] = mapped_column(String(64), default="")
name_label: Mapped[str] = mapped_column(String(64), default="")
name_placeholder: Mapped[str] = mapped_column(String(128), default="")
phone_field_label: Mapped[str] = mapped_column(String(64), default="")
phone_placeholder: Mapped[str] = mapped_column(String(64), default="")
note_label: Mapped[str] = mapped_column(String(64), default="")
note_optional_label: Mapped[str] = mapped_column(String(64), default="")
note_placeholder: Mapped[str] = mapped_column(String(256), default="")
submit_label: Mapped[str] = mapped_column(String(128), default="")
sending_label: Mapped[str] = mapped_column(String(64), default="")
success_title: Mapped[str] = mapped_column(String(256), default="")
success_text: Mapped[str] = mapped_column(Text, default="")
success_button_label: Mapped[str] = mapped_column(String(128), default="")
class NewsletterSectionContent(Base):
"""بخش خبرنامه — رکورد یگانه."""
__tablename__ = "newsletter"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(256), default="")
lead: Mapped[str] = mapped_column(Text, default="")
placeholder: Mapped[str] = mapped_column(String(128), default="")
button_label: Mapped[str] = mapped_column(String(64), default="")
sending_label: Mapped[str] = mapped_column(String(64), default="")
success_text: Mapped[str] = mapped_column(String(256), default="")
class FooterContent(Base):
"""متن‌های فوتر — رکورد یگانه."""
__tablename__ = "footer"
id: Mapped[int] = mapped_column(primary_key=True)
about_text: Mapped[str] = mapped_column(Text, default="")
pages_heading: Mapped[str] = mapped_column(String(64), default="")
services_heading: Mapped[str] = mapped_column(String(64), default="")
contact_heading: Mapped[str] = mapped_column(String(64), default="")
copyright: Mapped[str] = mapped_column(String(256), default="")
tagline: Mapped[str] = mapped_column(String(256), default="")
socials: Mapped[list] = mapped_column(JSON, default=list) # [{label, href, icon}]
class ContactMessage(Base):
__tablename__ = "contact_messages"
+249
View File
@@ -112,6 +112,137 @@ class BeforeAfterOut(ORMModel):
after_label: str
class NavItemOut(ORMModel):
id: int
label: str
href: str
class HeroOut(ORMModel):
id: int
kicker: str
title: str
title_highlight: str
subtitle: str
badge_value: str
badge_label: str
cta_primary_label: str
cta_primary_href: str
cta_secondary_label: str
cta_secondary_href: str
scroll_label: str
class StoryOut(ORMModel):
id: int
title: str
paragraphs: list[str]
quote: str
quote_author: str
stat1_value: str
stat1_label: str
stat2_value: str
stat2_label: str
link_label: str
link_href: str
bento: list[dict]
class SectionHeadingOut(ORMModel):
id: int
key: str
label: str
title: str
lead: str
image: str
cta_label: str
cta_href: str
class PageHeroOut(ORMModel):
id: int
key: str
title: str
lead: str
image: str
class InvestmentModelOut(ORMModel):
id: int
title: str
desc: str
points: list[str]
class ProcessStepOut(ORMModel):
id: int
step: str
title: str
desc: str
class OfficeOut(ORMModel):
id: int
city: str
address: str
hours: str
class GalleryImageOut(ORMModel):
id: int
src: str
alt: str
class StatusLabelOut(ORMModel):
id: int
key: str
label: str
class ContactSectionOut(ORMModel):
id: int
label: str
title: str
lead: str
address_label: str
phone_label: str
email_label: str
name_label: str
name_placeholder: str
phone_field_label: str
phone_placeholder: str
note_label: str
note_optional_label: str
note_placeholder: str
submit_label: str
sending_label: str
success_title: str
success_text: str
success_button_label: str
class NewsletterOut(ORMModel):
id: int
title: str
lead: str
placeholder: str
button_label: str
sending_label: str
success_text: str
class FooterOut(ORMModel):
id: int
about_text: str
pages_heading: str
services_heading: str
contact_heading: str
copyright: str
tagline: str
socials: list[dict]
class ContactMessageOut(ORMModel):
id: int
name: str
@@ -242,5 +373,123 @@ class BeforeAfterIn(BaseModel):
after_label: str = ""
class NavItemIn(BaseModel):
label: str = Field(min_length=1, max_length=64)
href: str = Field(min_length=1, max_length=128)
class HeroIn(BaseModel):
kicker: str = ""
title: str = ""
title_highlight: str = ""
subtitle: str = ""
badge_value: str = ""
badge_label: str = ""
cta_primary_label: str = ""
cta_primary_href: str = ""
cta_secondary_label: str = ""
cta_secondary_href: str = ""
scroll_label: str = ""
class StoryIn(BaseModel):
title: str = ""
paragraphs: list[str] = []
quote: str = ""
quote_author: str = ""
stat1_value: str = ""
stat1_label: str = ""
stat2_value: str = ""
stat2_label: str = ""
link_label: str = ""
link_href: str = ""
bento: list[dict] = []
class SectionHeadingIn(BaseModel):
key: str = Field(min_length=1, max_length=64)
label: str = ""
title: str = ""
lead: str = ""
image: str = ""
cta_label: str = ""
cta_href: str = ""
class PageHeroIn(BaseModel):
key: str = Field(min_length=1, max_length=32)
title: str = ""
lead: str = ""
image: str = ""
class InvestmentModelIn(BaseModel):
title: str = Field(min_length=1, max_length=256)
desc: str = ""
points: list[str] = []
class ProcessStepIn(BaseModel):
step: str = ""
title: str = Field(min_length=1, max_length=256)
desc: str = ""
class OfficeIn(BaseModel):
city: str = Field(min_length=1, max_length=64)
address: str = ""
hours: str = ""
class GalleryImageIn(BaseModel):
src: str = ""
alt: str = ""
class StatusLabelIn(BaseModel):
key: str = Field(min_length=1, max_length=16)
label: str = Field(min_length=1, max_length=64)
class ContactSectionIn(BaseModel):
label: str = ""
title: str = ""
lead: str = ""
address_label: str = ""
phone_label: str = ""
email_label: str = ""
name_label: str = ""
name_placeholder: str = ""
phone_field_label: str = ""
phone_placeholder: str = ""
note_label: str = ""
note_optional_label: str = ""
note_placeholder: str = ""
submit_label: str = ""
sending_label: str = ""
success_title: str = ""
success_text: str = ""
success_button_label: str = ""
class NewsletterSectionIn(BaseModel):
title: str = ""
lead: str = ""
placeholder: str = ""
button_label: str = ""
sending_label: str = ""
success_text: str = ""
class FooterIn(BaseModel):
about_text: str = ""
pages_heading: str = ""
services_heading: str = ""
contact_heading: str = ""
copyright: str = ""
tagline: str = ""
socials: list[dict] = []
class MessageReadIn(BaseModel):
is_read: bool = True
+40 -39
View File
@@ -1,17 +1,49 @@
"""اسکریپت seed دیتابیس.
اجرا:
python -m app.seed # فقط اگر دیتابیس خالی باشد
python -m app.seed # فقط جدول‌های خالی پر می‌شوند
python -m app.seed --force # حذف کامل داده‌ها و ساخت مجدد
پر کردن هر جدول مستقل است: روی یک دیتابیس موجود، فقط جدول‌های جدید یا خالی
seed می‌شوند و داده‌های قبلی دست‌نخورده می‌مانند.
"""
import argparse
from typing import Any
from . import models, seed_data
from .auth import hash_password
from .config import settings
from .database import Base, SessionLocal, engine
# (مدل، داده‌های اولیه، برچسب گزارش)
CONTENT: list[tuple[Any, list[dict], str]] = [
(models.Brand, [seed_data.BRAND], "برند"),
(models.Hero, [seed_data.HERO], "هیرو صفحه اصلی"),
(models.StorySection, [seed_data.STORY], "داستان ما"),
(models.SectionHeadingContent, seed_data.SECTION_HEADINGS, "تیتر بخش‌ها"),
(models.PageHeroContent, seed_data.PAGE_HEROES, "هیرو صفحات"),
(models.NavItem, seed_data.NAV_ITEMS, "منوی ناوبری"),
(models.InvestmentModel, seed_data.INVESTMENT_MODELS, "مدل‌های سرمایه‌گذاری"),
(models.ProcessStep, seed_data.PROCESS_STEPS, "مراحل فرآیند کار"),
(models.Office, seed_data.OFFICES, "دفاتر"),
(models.GalleryImage, seed_data.GALLERY_IMAGES, "گالری تصاویر"),
(models.StatusLabel, seed_data.STATUS_LABELS, "برچسب وضعیت پروژه"),
(models.ContactSectionContent, [seed_data.CONTACT_SECTION], "بخش تماس"),
(models.NewsletterSectionContent, [seed_data.NEWSLETTER], "خبرنامه"),
(models.FooterContent, [seed_data.FOOTER], "فوتر"),
(models.Service, seed_data.SERVICES, "خدمات"),
(models.Stat, seed_data.STATS, "آمار"),
(models.Project, seed_data.PROJECTS, "پروژه‌ها"),
(models.Article, seed_data.ARTICLES, "مقالات"),
(models.Testimonial, seed_data.TESTIMONIALS, "نظرات"),
(models.Faq, seed_data.FAQS, "سوالات متداول"),
(models.TeamMember, seed_data.TEAM, "تیم"),
(models.TimelineItem, seed_data.TIMELINE, "تایم‌لاین"),
(models.WhyUsItem, seed_data.WHY, "چرا آرامش"),
(models.BeforeAfter, [seed_data.BEFORE_AFTER], "قبل/بعد"),
]
def seed(force: bool = False) -> None:
db = SessionLocal()
@@ -22,31 +54,11 @@ def seed(force: bool = False) -> None:
Base.metadata.create_all(bind=engine)
if not force and db.query(models.Service).count() > 0:
print("دیتابیس قبلاً seed شده است. برای بازنشانی کامل: python -m app.seed --force")
return
db.add(models.Brand(**seed_data.BRAND))
for item in seed_data.SERVICES:
db.add(models.Service(**item))
for item in seed_data.STATS:
db.add(models.Stat(**item))
for item in seed_data.PROJECTS:
db.add(models.Project(**item))
for item in seed_data.ARTICLES:
db.add(models.Article(**item))
for item in seed_data.TESTIMONIALS:
db.add(models.Testimonial(**item))
for item in seed_data.FAQS:
db.add(models.Faq(**item))
for item in seed_data.TEAM:
db.add(models.TeamMember(**item))
for item in seed_data.TIMELINE:
db.add(models.TimelineItem(**item))
for item in seed_data.WHY:
db.add(models.WhyUsItem(**item))
db.add(models.BeforeAfter(**seed_data.BEFORE_AFTER))
for model, rows, label in CONTENT:
if db.query(model).count() > 0:
continue
for row in rows:
db.add(model(**row))
if db.query(models.AdminUser).count() == 0:
db.add(
@@ -59,19 +71,8 @@ def seed(force: bool = False) -> None:
db.commit()
counts = {
"خدمات": db.query(models.Service).count(),
"پروژه‌ها": db.query(models.Project).count(),
"مقالات": db.query(models.Article).count(),
"نظرات": db.query(models.Testimonial).count(),
"سوالات متداول": db.query(models.Faq).count(),
"تیم": db.query(models.TeamMember).count(),
"تایم‌لاین": db.query(models.TimelineItem).count(),
"آمار": db.query(models.Stat).count(),
"چرا آرامش": db.query(models.WhyUsItem).count(),
}
for label, count in counts.items():
print(f" {label}: {count} رکورد")
for model, _, label in CONTENT:
print(f" {label}: {db.query(model).count()} رکورد")
print("✔ seed با موفقیت انجام شد.")
finally:
db.close()
+162
View File
@@ -289,3 +289,165 @@ FAQS = [
{"question": "خدمات پس از تحویل شامل چه مواردی می‌شود؟", "answer": "پنج سال پشتیبانی فنی تأسیسات مکانیکی و برقی، بازدید دوره‌ای سالانه و تضمین اجرای نما و عایق‌کاری."},
{"question": "پروژه‌ها در چه شهرهایی اجرا می‌شوند؟", "answer": "در حال حاضر تهران، اصفهان و سواحل شمالی کشور مناطق اصلی فعالیت ما هستند و پروژه‌ای نیز در جزیره کیش در دست ساخت است."},
]
# ---------- محتوای قبلاً هاردکد‌شده در فرانت‌اند (الان در دیتابیس) ----------
NAV_ITEMS = [
{"label": "صفحه اصلی", "href": "/"},
{"label": "درباره ما", "href": "/about"},
{"label": "خدمات", "href": "/services"},
{"label": "پروژه‌ها", "href": "/projects"},
{"label": "سرمایه‌گذاری", "href": "/investment"},
{"label": "وبلاگ", "href": "/blog"},
{"label": "سوالات متداول", "href": "/faq"},
{"label": "تماس با ما", "href": "/contact"},
]
HERO = {
"kicker": "از سال ۱۳۸۳ — تهران، اصفهان، سواحل شمال",
"title": "ساختمانی که آرامش را معماری می‌کند",
"title_highlight": "معماری",
"subtitle": "طراحی، ساخت و سرمایه‌گذاری در پروژه‌های مسکونی و تجاری لوکس؛ با استانداردی که در جزئیات دیده می‌شود و در سال‌ها دوام می‌آورد.",
"badge_value": "۴۷",
"badge_label": "پروژه تحویل‌شده در بیست‌ویک سال فعالیت حرفه‌ای",
"cta_primary_label": "مشاهده پروژه‌ها",
"cta_primary_href": "/projects",
"cta_secondary_label": "مشاوره سرمایه‌گذاری",
"cta_secondary_href": "/contact",
"scroll_label": "اسکرول کنید",
}
STORY = {
"title": "درباره ی ما",
"paragraphs": [
"گروه ساختمانی آرامش با یک دفتر کوچک معماری آغاز شد و امروز یکی از مجموعه‌های شناخته‌شده ساخت‌وساز لوکس در ایران است. در تمام این سال‌ها یک اصل تغییر نکرده است: ساختمان باید برای زندگی ساخته شود، نه برای فروش سریع.",
"هر پروژه با مطالعه زمین، نور، اقلیم و الگوی زندگی ساکنان آغاز می‌شود؛ سپس تیم طراحی، سازه و اجرا در یک فرآیند واحد آن را تا تحویل کلید پیش می‌برند.",
],
"quote": "«ساختمان برای ما فقط آجر و بتن نیست؛ بلکه تعهدی است به کیفیت زندگی ساکنان. ما فضاهایی می‌سازیم که در گذر زمان ارزش و هویت خود را حفظ کنند.»",
"quote_author": "کامران آرامش — مدیرعامل شرکت ساختمانی آرامش",
"stat1_value": "۹۴٪",
"stat1_label": "تحویل به‌موقع پروژه‌ها",
"stat2_value": "۵ سال",
"stat2_label": "پشتیبانی پس از تحویل",
"link_label": "داستان کامل ما",
"link_href": "/about",
"bento": [
{"src": IMAGES[0], "alt": "نمای برج مسکونی آرامش در تهران"},
{"src": IMAGES[1], "alt": "آتریوم مرکزی مجتمع تجاری پارسه"},
{"src": IMAGES[2], "alt": "ویلاهای ساخته‌شده در سواحل شمال"},
{"src": IMAGES[3], "alt": "نمای شبانه برج دوقلوی نیاوران"},
],
}
SECTION_HEADINGS = [
{"key": "services", "title": "خدمات", "lead": "از نخستین خط روی کاغذ تا تحویل کلید، مدیریت سرمایه و طراحی محوطه؛ همه مراحل در یک مجموعه و زیر یک نظام کنترل کیفیت."},
{"key": "featured_projects", "title": "پروژه‌های شاخص", "lead": "کارهایی که بیشترین چیز را درباره استاندارد ما می‌گویند؛ چه آن‌هایی که تحویل شده‌اند و چه آن‌هایی که همین امروز در حال ساخت‌اند.", "cta_label": "همه پروژه‌ها", "cta_href": "/projects"},
{"key": "gallery", "title": "گالری تصاویر", "lead": "قاب‌هایی از کارگاه‌ها و پروژه‌های تحویل‌شده گروه ساختمانی آرامش."},
{"key": "timeline", "title": "مسیر ما", "lead": "از یک دفتر چهارنفره تا ۴۷ پروژه. خط زمان پروژه‌های ما را ببینید."},
{"key": "why_us", "title": "چرا گروه ساختمانی آرامش", "lead": "شش دلیلی که کارفرمایان و سرمایه‌گذاران بار دوم هم به ما برمی‌گردند.", "image": IMAGES[3]},
{"key": "testimonials", "title": "نظرات مشتریان"},
{"key": "team", "title": "تیم ما", "lead": "کسانی که تصمیم‌های اصلی هر پروژه را می‌گیرند و پاسخ‌گوی آن هستند."},
{"key": "articles", "label": "وبلاگ", "title": "آخرین مقالات", "lead": "یادداشت‌های تیم معماری، اجرا و سرمایه‌گذاری گروه ساختمانی آرامش درباره آنچه در کارگاه و بازار می‌گذرد.", "cta_label": "همه مقالات", "cta_href": "/blog"},
{"key": "faq", "title": "سوالات متداول", "lead": "پاسخ کوتاه به پرتکرارترین پرسش‌های سرمایه‌گذاران و خریداران."},
{"key": "investment_models", "title": "مدل‌های همکاری", "lead": "بسته به میزان سرمایه، افق زمانی و سطح ریسکی که می‌پذیرید، یکی از این سه مسیر مناسب شماست."},
{"key": "process", "title": "فرآیند کار ما", "lead": "چهار مرحله شفاف، از نخستین جلسه تا تحویل کلید."},
]
PAGE_HEROES = [
{"key": "about", "title": "درباره ما", "lead": "گروه ساختمانی آرامش مجموعه‌ای از معماران، مهندسان سازه و مدیران پروژه است که از سال ۱۳۸۳ در حوزه ساخت‌وساز لوکس فعالیت می‌کند.", "image": IMAGES[3]},
{"key": "services", "title": "خدمات", "lead": "هشت حوزه تخصصی که چرخه کامل یک پروژه ساختمانی را از ایده تا بهره‌برداری پوشش می‌دهند.", "image": IMAGES[1]},
{"key": "projects", "title": "پروژه‌ها", "lead": "مجموعه‌ای از برج‌های مسکونی، مراکز تجاری و ویلاهای اختصاصی؛ از پروژه‌های تحویل‌شده تا کارگاه‌های فعال امروز.", "image": IMAGES[0]},
{"key": "investment", "title": "سرمایه‌گذاری", "lead": "سه مسیر روشن برای ورود به بازار ساخت‌وساز لوکس، با ساختار مالی شفاف و گزارش‌دهی منظم.", "image": IMAGES[4]},
{"key": "blog", "title": "وبلاگ", "lead": "یادداشت‌های تیم معماری، اجرا و سرمایه‌گذاری گروه ساختمانی آرامش درباره آنچه در کارگاه و بازار می‌گذرد.", "image": IMAGES[2]},
{"key": "faq", "title": "سوالات متداول", "lead": "پیش از تماس، شاید پاسخ پرسش شما همین‌جا باشد. اگر نبود، خوشحال می‌شویم مستقیم صحبت کنیم.", "image": IMAGES[1]},
{"key": "contact", "title": "تماس با ما", "lead": "برای مشاوره ساخت، سرمایه‌گذاری یا بازدید از پروژه‌ها، تیم ما در سه شهر آماده پاسخ‌گویی است.", "image": IMAGES[1]},
]
INVESTMENT_MODELS = [
{
"title": "مشارکت در ساخت",
"desc": "مالک زمین و گروه ساختمانی آرامش در سود پروژه شریک می‌شوند؛ اجرا، مجوزها و فروش بر عهده ماست.",
"points": ["ارزیابی رایگان زمین", "قرارداد شفاف با ضمانت اجرا", "گزارش ماهانه پیشرفت"],
},
{
"title": "صندوق پروژه‌محور",
"desc": "سرمایه‌گذاری روی یک پروژه مشخص با واحدهای قابل خرید و بازدهی محاسبه‌شده در پایان دوره.",
"points": ["حداقل دوره ۲۴ ماه", "گزارش ارزش‌گذاری فصلی", "امکان انتقال واحد سرمایه"],
},
{
"title": "پیش‌خرید واحد",
"desc": "خرید واحد مسکونی یا تجاری در مراحل ابتدایی ساخت با قیمت پیش از تکمیل و پرداخت مرحله‌ای.",
"points": ["پرداخت مرحله‌ای متناسب با پیشرفت", "امکان شخصی‌سازی پلان", "تضمین زمان تحویل"],
},
]
PROCESS_STEPS = [
{"step": "۰۱", "title": "مطالعه و امکان‌سنجی", "desc": "بررسی زمین، ضوابط شهری و تحلیل بازدهی اقتصادی پروژه."},
{"step": "۰۲", "title": "طراحی و فاز اجرایی", "desc": "کانسپت معماری، طراحی سازه و تأسیسات و تهیه نقشه‌های اجرایی."},
{"step": "۰۳", "title": "اجرا و کنترل کیفیت", "desc": "ساخت با نظارت مستمر، گزارش ماهانه پیشرفت و کنترل هزینه."},
{"step": "۰۴", "title": "تحویل و پشتیبانی", "desc": "تحویل کلید، آموزش بهره‌برداری و پنج سال پشتیبانی فنی."},
]
OFFICES = [
{"city": "تهران", "address": "خیابان ولیعصر، برج آرامش، طبقه هجدهم", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۸"},
{"city": "اصفهان", "address": "خیابان چهارباغ بالا، مجتمع پارسه، طبقه ششم", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۷"},
{"city": "نوشهر", "address": "بلوار دریا، دفتر پروژه دریاکنار", "hours": "شنبه تا پنجشنبه، ۱۰ تا ۱۶"},
]
GALLERY_IMAGES = [
{"src": IMAGES[0], "alt": "تصویری از پروژه‌های گروه ساختمانی آرامش، شماره ۱"},
{"src": IMAGES[1], "alt": "تصویری از پروژه‌های گروه ساختمانی آرامش، شماره ۲"},
{"src": IMAGES[2], "alt": "تصویری از پروژه‌های گروه ساختمانی آرامش، شماره ۳"},
{"src": IMAGES[3], "alt": "تصویری از پروژه‌های گروه ساختمانی آرامش، شماره ۴"},
{"src": IMAGES[4], "alt": "تصویری از پروژه‌های گروه ساختمانی آرامش، شماره ۵"},
{"src": IMAGES[1], "alt": "تصویری از پروژه‌های گروه ساختمانی آرامش، شماره ۶"},
]
STATUS_LABELS = [
{"key": "done", "label": "انجام شده"},
{"key": "progress", "label": "در حال انجام"},
]
CONTACT_SECTION = {
"label": "تماس با ما",
"title": "پروژه‌تان را با ما در میان بگذارید",
"lead": "شماره‌تان را بگذارید؛ کارشناس ما در کمتر از یک روز کاری برای یک جلسه مشاوره رایگان تماس می‌گیرد.",
"address_label": "دفتر مرکزی",
"phone_label": "تلفن",
"email_label": "ایمیل",
"name_label": "نام",
"name_placeholder": "نام و نام خانوادگی",
"phone_field_label": "شماره تلفن",
"phone_placeholder": "09xx xxx xxxx",
"note_label": "توضیحات",
"note_optional_label": "(اختیاری)",
"note_placeholder": "درباره پروژه یا درخواست خود بنویسید…",
"submit_label": "ارسال درخواست مشاوره",
"sending_label": "در حال ارسال…",
"success_title": "درخواست شما ثبت شد",
"success_text": "از اعتماد شما سپاسگزاریم. کارشناسان ما به‌زودی با شما تماس می‌گیرند.",
"success_button_label": "ثبت درخواست جدید",
}
NEWSLETTER = {
"title": "خبرنامه گروه ساختمانی آرامش",
"lead": "هر ماه یک ایمیل: پروژه‌های جدید، فرصت‌های سرمایه‌گذاری و یادداشت‌های تیم معماری.",
"placeholder": "نشانی ایمیل شما",
"button_label": "عضویت",
"sending_label": "در حال ارسال…",
"success_text": "عضویت شما ثبت شد.",
}
FOOTER = {
"about_text": "گروه ساختمانی آرامش از سال ۱۳۸۳ ساختمان‌هایی می‌سازد که قرار است چند نسل در آن‌ها زندگی کنند؛ معماری آرام، اجرای دقیق و سرمایه‌گذاری شفاف.",
"pages_heading": "صفحات",
"services_heading": "خدمات",
"contact_heading": "ارتباط با ما",
"copyright": "© ۱۴۰۳ گروه ساختمانی آرامش — تمامی حقوق محفوظ است.",
"tagline": "طراحی و ساخت با دقتِ معماری",
"socials": [
{"label": "اینستاگرام", "href": "#", "icon": "instagram"},
{"label": "لینکدین", "href": "#", "icon": "linkedin"},
{"label": "تلگرام", "href": "#", "icon": "telegram"},
],
}