diff --git a/backend/Dockerfile b/backend/Dockerfile
index 3b4dee2..e867e6a 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -7,7 +7,11 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
+# PyPI برای IP ایران بلاک است (connection timeout) — آینهٔ Tsinghua پیشفرض است و
+# Aliyun بهعنوان منبع دوم؛ با --build-arg قابل بازنویسی است (مثلاً روی CI خارج ایران)
+ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
+ARG PIP_EXTRA_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
+RUN pip install --no-cache-dir -i "$PIP_INDEX_URL" --extra-index-url "$PIP_EXTRA_INDEX_URL" -r requirements.txt
# FFmpeg — نرمالسازی رسانه به WebP برای ورودیهایی که Pillow نمیخواند
# (ویدیو → WebP متحرک، فرمتهای تصویری خاص). مسیر Pillow به آن وابسته نیست.
diff --git a/backend/app/api/public.py b/backend/app/api/public.py
index 5a30799..82a1cb1 100644
--- a/backend/app/api/public.py
+++ b/backend/app/api/public.py
@@ -153,9 +153,11 @@ def get_before_after(db: Session = Depends(get_db)):
return item
-@router.get("/site")
-def get_site(db: Session = Depends(get_db)):
- """همه محتوای سایت در یک درخواست — مناسب برای بارگذاری اولیه فرانتاند."""
+def build_site_payload(db: Session) -> dict:
+ """همه محتوای سایت در یک دیکشنری — هم برای GET /api/site و هم برای
+ تزریق مستقیم داخل HTML (window.__SITE__) در app/seo_pages.py.
+ تزریق داخل HTML یعنی فرانتاند بدون انتظار برای XHR رندر میشود —
+ خزندههای رندرکننده (گوگل) هیچوقت صفحهٔ نیمهخالی نمیبینند."""
return {
"brand": db.query(models.Brand).first(),
"nav": db.query(models.NavItem).order_by(models.NavItem.position, models.NavItem.id).all(),
@@ -187,3 +189,9 @@ def get_site(db: Session = Depends(get_db)):
"footer": db.query(models.FooterContent).first(),
"seo_meta": db.query(models.SeoMeta).order_by(models.SeoMeta.id).all(),
}
+
+
+@router.get("/site")
+def get_site(db: Session = Depends(get_db)):
+ """همه محتوای سایت در یک درخواست — مناسب برای بارگذاری اولیه فرانتاند."""
+ return build_site_payload(db)
diff --git a/backend/app/seed_data.py b/backend/app/seed_data.py
index 803bf8d..a1b1f6c 100644
--- a/backend/app/seed_data.py
+++ b/backend/app/seed_data.py
@@ -178,11 +178,8 @@ WHY = [
{"title": "پایداری و انرژی", "desc": "طراحی پوسته و تأسیسات با هدف کاهش مصرف انرژی تا ۳۵ درصد."},
]
-TESTIMONIALS = [
- {"name": "مهندس سارا کیانی", "role": "سرمایهگذار پروژه پارسه", "quote": "گزارشهای ماهانه گروه ساختمانی آرامش آنقدر دقیق بود که هیچوقت نیاز نشد نگران سرمایهام باشم. پروژه دو ماه زودتر از موعد به بهرهبرداری رسید."},
- {"name": "دکتر امیرحسین راد", "role": "ساکن برج الهیه", "quote": "کیفیت اجرا در جزئیاتی دیده میشود که معمولاً کسی به آنها فکر نمیکند؛ از عایق صوتی پنجرهها تا نور غیرمستقیم راهروها."},
- {"name": "مریم دلاور", "role": "مدیر مجموعه ویلاهای دریاکنار", "quote": "همکاری با تیم طراحی، تجربهای واقعاً حرفهای بود. ایدههای ما را شنیدند و به زبان معماری ترجمه کردند."},
-]
+# معرفی مشتریان — خالی. (سه معرفی قبلی افراد ساختگی دمو بودند؛ با دادهٔ واقعی تیم پر میشود.)
+TESTIMONIALS = []
TEAM = [
{"name": "کامران آرامش", "role": "بنیانگذار و مدیرعامل", "bio": "بیش از سه دهه تجربه در ساخت بلندمرتبه."},
@@ -388,10 +385,9 @@ PROCESS_STEPS = [
{"step": "۰۴", "title": "تحویل و پشتیبانی", "desc": "تحویل کلید، آموزش بهرهبرداری و پنج سال پشتیبانی فنی."},
]
+# دفتر مرکزی — تنها دفتر واقعی. (سه دفتر تهران/اصفهان/نوشهرِ قبلی دادهٔ دمو بودند و حذف شدند.)
OFFICES = [
- {"city": "تهران", "address": "خیابان ولیعصر، برج آرامش، طبقه هجدهم", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۸"},
- {"city": "اصفهان", "address": "خیابان چهارباغ بالا، مجتمع پارسه، طبقه ششم", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۷"},
- {"city": "نوشهر", "address": "بلوار دریا، دفتر پروژه دریاکنار", "hours": "شنبه تا پنجشنبه، ۱۰ تا ۱۶"},
+ {"city": "اصفهان", "address": "خیابان ملاصدرا شمالی، کوچه ۶، پلاک ۳۶", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۷"},
]
GALLERY_IMAGES = [
diff --git a/backend/app/seed_data_en.py b/backend/app/seed_data_en.py
index 9da7a97..83eb169 100644
--- a/backend/app/seed_data_en.py
+++ b/backend/app/seed_data_en.py
@@ -213,11 +213,8 @@ WHY = [
{"title": "پایداری و انرژی", "title_en": "Sustainability & Energy", "desc": "طراحی پوسته و تأسیسات با هدف کاهش مصرف انرژی تا ۳۵ درصد.", "desc_en": "Envelope and building systems designed to cut energy consumption by up to 35%."},
]
-TESTIMONIALS = [
- {"name": "مهندس سارا کیانی", "name_en": "Sara Kiani", "role": "سرمایهگذار پروژه پارسه", "role_en": "Investor, Parseh Mall project", "quote": "گزارشهای ماهانه گروه ساختمانی آرامش آنقدر دقیق بود که هیچوقت نیاز نشد نگران سرمایهام باشم. پروژه دو ماه زودتر از موعد به بهرهبرداری رسید.", "quote_en": "Aramesh's monthly reports were so precise that I never had to worry about my investment. The project opened two months ahead of schedule."},
- {"name": "دکتر امیرحسین راد", "name_en": "Dr. Amirhossein Rad", "role": "ساکن برج الهیه", "role_en": "Resident, Elahieh Tower", "quote": "کیفیت اجرا در جزئیاتی دیده میشود که معمولاً کسی به آنها فکر نمیکند؛ از عایق صوتی پنجرهها تا نور غیرمستقیم راهروها.", "quote_en": "The build quality shows in details nobody usually thinks about — from the acoustic insulation of the windows to the indirect lighting in the corridors."},
- {"name": "مریم دلاور", "name_en": "Maryam Delavar", "role": "مدیر مجموعه ویلاهای دریاکنار", "role_en": "Manager, Daryakenar Villas", "quote": "همکاری با تیم طراحی، تجربهای واقعاً حرفهای بود. ایدههای ما را شنیدند و به زبان معماری ترجمه کردند.", "quote_en": "Working with the design team was a truly professional experience. They listened to our ideas and translated them into architecture."},
-]
+# Customer testimonials — empty. (The previous three were fictional demo people.)
+TESTIMONIALS = []
TEAM = [
{"name": "کامران آرامش", "name_en": "Kamran Aramesh", "role": "بنیانگذار و مدیرعامل", "role_en": "Founder & CEO", "bio": "بیش از سه دهه تجربه در ساخت بلندمرتبه.", "bio_en": "Three decades of high-rise construction experience."},
@@ -476,10 +473,9 @@ PROCESS_STEPS = [
{"step": "۰۴", "step_en": "04", "title": "تحویل و پشتیبانی", "title_en": "Handover & Support", "desc": "تحویل کلید، آموزش بهرهبرداری و پنج سال پشتیبانی فنی.", "desc_en": "Key handover, operating walkthrough and five years of technical support."},
]
+# Head office — the only real office. (The previous Tehran/Isfahan/Nowshahr entries were demo data.)
OFFICES = [
- {"city": "تهران", "city_en": "Tehran", "address": "خیابان ولیعصر، برج آرامش، طبقه هجدهم", "address_en": "Valiasr St., Aramesh Tower, 18th Floor", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۸", "hours_en": "Saturday–Wednesday, 9 AM–6 PM"},
- {"city": "اصفهان", "city_en": "Isfahan", "address": "خیابان چهارباغ بالا، مجتمع پارسه، طبقه ششم", "address_en": "Chaharbagh Bala St., Parseh Complex, 6th Floor", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۷", "hours_en": "Saturday–Wednesday, 9 AM–5 PM"},
- {"city": "نوشهر", "city_en": "Nowshahr", "address": "بلوار دریا، دفتر پروژه دریاکنار", "address_en": "Darya Blvd., Daryakenar Project Office", "hours": "شنبه تا پنجشنبه، ۱۰ تا ۱۶", "hours_en": "Saturday–Thursday, 10 AM–4 PM"},
+ {"city": "اصفهان", "city_en": "Isfahan", "address": "خیابان ملاصدرا شمالی، کوچه ۶، پلاک ۳۶", "address_en": "Mollasadra Shomali St., Alley 6, No. 36, Isfahan", "hours": "شنبه تا چهارشنبه، ۹ تا ۱۷", "hours_en": "Saturday–Wednesday, 9 AM–5 PM"},
]
GALLERY_IMAGES = [
diff --git a/backend/app/seo_pages.py b/backend/app/seo_pages.py
index 977db83..6ec12bc 100644
--- a/backend/app/seo_pages.py
+++ b/backend/app/seo_pages.py
@@ -24,12 +24,15 @@ import re
from datetime import date, datetime
from functools import lru_cache
from pathlib import Path
+from urllib.parse import quote
from fastapi import APIRouter, Depends, Response
+from fastapi.encoders import jsonable_encoder
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from . import models
+from .api.public import build_site_payload
from .config import settings
from .database import get_db
@@ -43,6 +46,9 @@ FALLBACK_EN_BRAND = "Aramesh Construction Group"
# تصویر پیشفرض شبکههای اجتماعی — در frontend/public بیلد میشود
DEFAULT_OG_IMAGE = "/og-default.jpg"
DEFAULT_OG_SIZE = (1200, 630)
+# کلید IndexNow — اثبات مالکیت برای اعلام فوری نشانیهای سایت به Bing/Copilot و
+# موتورهای مشارکتکننده در پروتکل (فایل در /.well-known/indexnow-key.txt سرو میشود)
+INDEXNOW_KEY = "daa4488a8e2905312b5efd02ad5fc614"
# خزندههای هوش مصنوعی جستوجو/ارجاع — سیاست صریح «مجاز» تا قالبهای پیشفرض
# «بستن همهچیز» اتفاقی آنها را نبندند (GPTBot آموزش است، OAI-SearchBot ارجاع چتجیپیتی)
AI_SEARCH_BOTS = [
@@ -79,6 +85,11 @@ def _fa_path(path: str) -> str:
return path[3:] if path.startswith("/en/") else path
+def _url_escape(url: str) -> str:
+ """درصدگذاری نویسههای غیر-ASCII (اسلاگهای فارسی) — استاندارد sitemap و IndexNow."""
+ return quote(url, safe=":/?#[]@!$&'()*+,;=")
+
+
def _clip(text: str, limit: int) -> str:
"""برش متن به طول مشخص برای توضیحهای تکخطی (llms.txt)."""
text = " ".join((text or "").split())
@@ -211,6 +222,16 @@ def _doc_text(doc: object, limit: int = 300) -> str:
return text[:limit] + ("…" if len(text) > limit else "")
+def _site_json(db: Session) -> str:
+ """محتوای کامل سایت بهصورت JSON برای تزریق داخل HTML (window.__SITE__).
+ فرانتاند با همین داده بدون انتظار برای XHR رندر میشود — خزندههای رندرکننده
+ (گوگل) هیچوقت در پنجرهٔ «در حال بارگذاری» صفحهٔ نیمهخالی نمیبینند."""
+ payload = jsonable_encoder(build_site_payload(db))
+ data = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
+ # امنسازی درج در HTML: توالی بستن اسکریپت و کامنت HTML نباید در داده ظاهر شود
+ return data.replace("", "<\\/").replace("
+
+
+
+
diff --git a/frontend/public/63730e04d66942f8b422d2d017f67fe4.txt b/frontend/public/63730e04d66942f8b422d2d017f67fe4.txt
new file mode 100644
index 0000000..ec0625a
--- /dev/null
+++ b/frontend/public/63730e04d66942f8b422d2d017f67fe4.txt
@@ -0,0 +1 @@
+63730e04d66942f8b422d2d017f67fe4
\ No newline at end of file
diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png
new file mode 100644
index 0000000..42e261d
Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ
diff --git a/frontend/public/f48526ce084d4b76a6e522283d3ad68f.txt b/frontend/public/f48526ce084d4b76a6e522283d3ad68f.txt
new file mode 100644
index 0000000..c469032
--- /dev/null
+++ b/frontend/public/f48526ce084d4b76a6e522283d3ad68f.txt
@@ -0,0 +1 @@
+f48526ce084d4b76a6e522283d3ad68f
\ No newline at end of file
diff --git a/frontend/public/favicon-32.png b/frontend/public/favicon-32.png
new file mode 100644
index 0000000..68cb885
Binary files /dev/null and b/frontend/public/favicon-32.png differ
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
new file mode 100644
index 0000000..8b5a37f
Binary files /dev/null and b/frontend/public/favicon.ico differ
diff --git a/frontend/public/icon-192.png b/frontend/public/icon-192.png
new file mode 100644
index 0000000..abd042f
Binary files /dev/null and b/frontend/public/icon-192.png differ
diff --git a/frontend/public/icon-512.png b/frontend/public/icon-512.png
new file mode 100644
index 0000000..e355b2c
Binary files /dev/null and b/frontend/public/icon-512.png differ
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 4a57907..ce44e20 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -149,7 +149,14 @@ function AppShell() {
{t('loading')}
+