diff --git a/frontend/src/components/LangToggle.tsx b/frontend/src/components/LangToggle.tsx
index a3c3301..c85a56d 100644
--- a/frontend/src/components/LangToggle.tsx
+++ b/frontend/src/components/LangToggle.tsx
@@ -1,18 +1,6 @@
import { useLocation, useNavigate } from 'react-router-dom'
+import { LanguagesIcon } from 'lucide-react'
import { counterpartPath, useLang, writeLangCookie } from '../i18n/lang'
-import { track } from '../analytics'
-
-/**
- * تنظیمات ثابت هر زبان.
- * ترتیب اینجا = ترتیب نمایش در کپسول (فارسی سمت چپ، English سمت راست).
- * بجای مقادیر پیکسلی، از translate-x-full استفاده شده تا دقیقاً به اندازه عرض خودش (50%) جابجا شود.
- */
-const LANGS = {
- fa: { label: 'فارسی', sliderTranslate: 'translate-x-0' },
- en: { label: 'English', sliderTranslate: 'translate-x-full' },
-} as const
-
-type LangKey = keyof typeof LANGS
/** دکمه تغییر زبان — بین نسخه فارسی و /en انگلیسی همان صفحه جابهجا میشود */
export function LangToggle({ className = '' }: { className?: string }) {
@@ -22,9 +10,7 @@ export function LangToggle({ className = '' }: { className?: string }) {
const target = counterpartPath(pathname, lang)
const switchLang = () => {
- const next = lang === 'fa' ? 'en' : 'fa'
- track('lang-switch', { to: next })
- writeLangCookie(next)
+ writeLangCookie(lang === 'fa' ? 'en' : 'fa')
// پرش خودکار بازدید بعدی با انتخاب دستی همینجا گره نمیخورد
sessionStorage.setItem('langAutoRedirect', '1')
navigate(target)
@@ -36,61 +22,10 @@ export function LangToggle({ className = '' }: { className?: string }) {
onClick={switchLang}
aria-label={t('switchLangAria')}
title={t('switchLangAria')}
- dir="ltr"
- className={[
- // ── Capsule shell ──────────────────────────────────────────────
- 'relative flex items-center',
- 'w-[152px] h-[42px] rounded-full p-1', // استفاده از p-1 (4px) به جای [4px]
- 'bg-[#1a1715]',
- // deep inset shadow → recessed track feel
- 'shadow-[inset_0_3px_7px_rgba(0,0,0,0.75),inset_0_-1px_2px_rgba(255,255,255,0.04)]',
- // interaction
- 'cursor-pointer select-none',
- 'active:scale-[0.975] transition-transform duration-150 ease-lux',
- className,
- ].join(' ')}
+ className={`group flex items-center gap-1.5 rounded-full border border-white/15 bg-white/5 px-3.5 py-2 text-xs font-bold text-ivory/80 backdrop-blur-lg shadow-[inset_0_1px_1px_rgba(255,255,255,0.15)] transition-all duration-300 ease-lux hover:border-copper/60 hover:bg-white/10 hover:text-copper active:scale-95 cursor-pointer ${className}`}
>
- {/* ── Inner Track ───────────────────────────────────────────────
- این دیو داخلی باعث میشود پدینگ دکمه اصلی روی محاسبات 50 درصدی تاثیر نگذارد
- */}
-
-
- {/* ── Slider pill ─────────────────────────────────────────────── */}
-
-
- {/* ── Labels ──────────────────────────────────────────────────── */}
-
- {(Object.keys(LANGS) as LangKey[]).map((key) => {
- const isActive = lang === key
- return (
-
- {LANGS[key].label}
-
- )
- })}
-
-
-
+
+ {t('switchLang')}
)
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/home/Faq.tsx b/frontend/src/components/home/Faq.tsx
index cf01a41..3dc02d1 100644
--- a/frontend/src/components/home/Faq.tsx
+++ b/frontend/src/components/home/Faq.tsx
@@ -18,22 +18,20 @@ export function Faq({ hideHeading = false }: FaqProps) {
return (
-
- {/* Header — centered, full width */}
+
+
{!hideHeading && (
-
+
)}
- {/* Accordion list — constrained width, direction-aware */}
-
+
{faqs.map((faq, i) => {
const isOpen = open === i
@@ -45,9 +43,9 @@ export function Faq({ hideHeading = false }: FaqProps) {
type="button"
onClick={() => setOpen(isOpen ? null : i)}
aria-expanded={isOpen}
- className="flex w-full items-center justify-between gap-6 py-6 text-start transition-colors duration-200 ease-lux hover:text-copper"
+ className="flex w-full items-center justify-between gap-6 py-6 text-right transition-colors duration-200 ease-lux hover:text-copper"
>
-
+
{faq.question}
- {faq.answer}
+ {faq.answer}
)}
diff --git a/frontend/src/pages/ProjectDetail.tsx b/frontend/src/pages/ProjectDetail.tsx
index 08e906e..5ace33b 100644
--- a/frontend/src/pages/ProjectDetail.tsx
+++ b/frontend/src/pages/ProjectDetail.tsx
@@ -1,115 +1,75 @@
-import { useEffect, useMemo, useState } from 'react'
+import React, { useState } from 'react'
import { Link, useParams } from 'react-router-dom'
-import { ChevronLeftIcon } from 'lucide-react'
+import { ArrowLeftIcon, ChevronLeftIcon } from 'lucide-react'
import { Reveal } from '../components/Reveal'
-import { PremiumButton } from '../components/PremiumButton'
+import { ButtonLink } from '../components/Button'
import { ProjectCard } from '../components/ProjectCard'
import { ContactSection } from '../components/ContactSection'
import { Lightbox } from '../components/Lightbox'
-import { LazyProjectsMapSingle } from '../map/LazyMaps';
-import { createPlaceholderProject, findMapProjectByName } from '../data/mapProjects'
+import { useEffect } from 'react';
import { useSite, useStatusLabel } from '../api';
import { useLang } from '../i18n/lang';
import { applySeo } from '../seo';
-import { projectsData } from '../data/projectsData';
import { SingletonPill } from '../admin/Editable';
export function ProjectDetail() {
const { slug } = useParams<{ slug: string }>()
const { projects, brand } = useSite()
const { t, href, lang } = useLang()
-
- // اولویت با رکورد دیتابیس است تا ویرایش پنل روی صفحهٔ جزئیات هم دیده شود؛
- // فایل استاتیک فقط برای اسلاگهای قدیمی که در دیتابیس نیستند به کار میرود.
- const dbProject = projects.find((p) => p.slug === slug)
- const staticProject = projectsData.find((p) => p.slug === slug)
- const currentProject = dbProject
- ? {
- id: String(dbProject.id),
- slug: dbProject.slug,
- name: { en: dbProject.title_en || dbProject.title, fa: dbProject.title },
- location: { en: dbProject.location_en || dbProject.location, fa: dbProject.location },
- area: { en: dbProject.area_en || dbProject.area, fa: dbProject.area },
- status: (dbProject.status === 'progress' ? 'progress' : 'done') as 'done' | 'progress',
- }
- : staticProject
-
+ const project = projects.find((p) => p.slug === slug)
+ const statusLabel = useStatusLabel(project?.status ?? '')
const [lightbox, setLightbox] = useState(null)
- if (!currentProject) {
- return Project not found
- }
-
- const backendProject = dbProject
-
- // Dynamic bilingual text from projectsData
- const displayTitle = currentProject.name[lang as 'en' | 'fa'] || currentProject.name.en;
- const displayLocation = currentProject.location[lang as 'en' | 'fa'] || currentProject.location.en;
- const displayArea = currentProject.area[lang as 'en' | 'fa'] || currentProject.area.en;
- const displayStatus = currentProject.status;
-
- const statusLabel = useStatusLabel(displayStatus)
-
- // Fallbacks for layout structure fields missing in projectsData
- const summary = backendProject?.summary || (lang === 'en' ? 'A modern and luxurious architectural project built with high-quality materials and smart design.' : 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ.');
- const body = backendProject?.body || (lang === 'en'
- ? ['Located in the heart of the city, this project offers unparalleled access to major urban hubs.', 'It features state-of-the-art facilities, green spaces, and a sustainable approach to modern living.']
- : ['لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است.', 'چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است.']);
- const gallery = backendProject?.gallery?.length ? backendProject.gallery : [
- '/seed-media/2026/09/1.png',
- '/seed-media/2026/09/2.png',
- '/seed-media/2026/09/3.png',
- '/seed-media/2026/09/4.png'
- ];
- const image = backendProject?.image || '/seed-media/2026/09/1.png';
- const year = backendProject?.year || (lang === 'en' ? '2024' : '1402');
- const floors = backendProject?.floors || '5';
- const units = backendProject?.units || '20';
- const client = backendProject?.client || (lang === 'en' ? 'Aramesh Group' : 'گروه آرامش');
-
- // تصاویر پاپآپ نقشه
- const mapImages = useMemo(
- () => [image, ...gallery].filter(Boolean),
- [image, gallery]
- )
-
- // مختصات همین پروژه
- const mapLocation = useMemo(
- () => findMapProjectByName(displayTitle) ?? createPlaceholderProject(displayTitle, displayLocation),
- [displayTitle, displayLocation]
- )
-
- // SEO
+ // عنوان تب، توضیح متا و OG از خود پروژه
useEffect(() => {
+ if (!project) return;
applySeo({
- title: `${displayTitle} | ${brand.name}`,
- description: summary,
- path: href(`/projects/${currentProject.slug}`),
- image: image,
+ title: `${project.title} | ${brand.name}`,
+ description: project.summary,
+ path: href(`/projects/${project.slug}`),
+ image: project.image,
lang,
});
- }, [currentProject.slug, lang, href, displayTitle, summary, image, brand.name]);
+ }, [project?.id, lang, href]);
- // پروژههای مرتبط — از دیتابیس، و اگر خالی بود از فهرست استاتیک قدیمی
- const related = (projects.length > 0 ? projects : projectsData)
- .filter((p) => p.slug !== currentProject.slug)
- .slice(0, 3)
+ if (!project) {
+ return (
+
+ {t('projectNotFound')}
+
+ {t('projectNotFoundText')}
+
+
+ {t('backToProjects')}
+
+
+
+ )
+ }
+
+ const related = projects.filter((p) => p.slug !== project.slug).slice(0, 3)
const facts = [
- { label: t('specLocation'), value: displayLocation },
- { label: t('specYear'), value: year },
- { label: t('specArea'), value: displayArea },
- { label: t('specFloors'), value: floors },
- { label: t('specUnits'), value: units },
- { label: t('specClient'), value: client },
+ { label: t('specLocation'), value: project.location },
+ { label: t('specYear'), value: project.year },
+ { label: t('specArea'), value: project.area },
+ { label: t('specFloors'), value: project.floors },
+ { label: t('specUnits'), value: project.units },
+ { label: t('specClient'), value: project.client },
]
return (
<>
-
-
-
-