feat(hero): نشان «پروژه فعال الیسیان» به صفحهٔ همان پروژه وصل شد (تطبیق دوزبانهٔ نام با نگاشت Elysian→الیسیان)
This commit is contained in:
@@ -10,9 +10,46 @@ import { InlineEdit } from '../admin/InlineEdit'
|
|||||||
import { HeroProjectBadge } from './HeroProjectBadge'
|
import { HeroProjectBadge } from './HeroProjectBadge'
|
||||||
import { SingletonPill } from '../../admin/Editable'
|
import { SingletonPill } from '../../admin/Editable'
|
||||||
|
|
||||||
|
/** واژههای عمومی برچسب نشان که در تطبیق نام پروژه نادیده گرفته میشوند */
|
||||||
|
const NOISE_WORDS = ['پروژه', 'فعال', 'آرامش', 'گروه', 'ساختمانی', 'Active', 'Project', 'Aramesh']
|
||||||
|
/** نام لاتین برچسب انگلیسی → معادل فارسی در عنوان پروژه (پروژههای بدون title_en) */
|
||||||
|
const LATIN_ALIAS: Record<string, string> = { elysian: 'الیسیان' }
|
||||||
|
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const { brand, hero } = useSite()
|
const { brand, hero, projects } = useSite()
|
||||||
const { t, href } = useLang()
|
const { t, href, lang } = useLang()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* پروژهٔ فعال هیرو → صفحهٔ همان پروژه.
|
||||||
|
* برچسب نشان مثل «پروژه فعال آرامش الیسیان» یا «Elysian Aramesh Active Project»
|
||||||
|
* است؛ واژههای عمومی حذف و نام باقیمانده با عنوان پروژهها تطبیق داده میشود.
|
||||||
|
*/
|
||||||
|
const activeProjectHref = (() => {
|
||||||
|
const label = hero.badge_label.trim()
|
||||||
|
if (!label) return undefined
|
||||||
|
|
||||||
|
const strip = (text: string) => {
|
||||||
|
let out = text
|
||||||
|
NOISE_WORDS.forEach((w) => {
|
||||||
|
out = out.split(w).join(' ')
|
||||||
|
})
|
||||||
|
return out.replace(/[\s\-—_]+/g, '').trim().toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = strip(label)
|
||||||
|
// برچسب انگلیسی ممکن است نام لاتین داشته باشد و پروژه فقط عنوان فارسی (Elysian → الیسیان)
|
||||||
|
if (key.length < 3 || LATIN_ALIAS[key]) key = LATIN_ALIAS[key] ?? key
|
||||||
|
if (key.length < 3) return undefined
|
||||||
|
|
||||||
|
const hit = projects.find((p) => {
|
||||||
|
const candidates = [p.title, p.title_en ?? ''].filter(Boolean)
|
||||||
|
return candidates.some((title) => {
|
||||||
|
const k = strip(title)
|
||||||
|
return k.length >= 3 && (k.includes(key) || key.includes(k))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return hit ? `/projects/${hit.slug}` : undefined
|
||||||
|
})()
|
||||||
const save = useFieldSaver()
|
const save = useFieldSaver()
|
||||||
const { scrollY } = useScroll()
|
const { scrollY } = useScroll()
|
||||||
const imageY = useTransform(scrollY, [0, 700], [0, 110])
|
const imageY = useTransform(scrollY, [0, 700], [0, 110])
|
||||||
@@ -141,7 +178,11 @@ export function Hero() {
|
|||||||
<span aria-hidden="true" className="absolute -top-20 -end-20 h-44 w-44 rounded-full bg-copper/10 blur-3xl transition-colors duration-700 group-hover:bg-copper/20" />
|
<span aria-hidden="true" className="absolute -top-20 -end-20 h-44 w-44 rounded-full bg-copper/10 blur-3xl transition-colors duration-700 group-hover:bg-copper/20" />
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<HeroProjectBadge value={String(hero.badge_value ?? '').trim()} label={hero.badge_label} />
|
<HeroProjectBadge
|
||||||
|
value={String(hero.badge_value ?? '').trim()}
|
||||||
|
label={hero.badge_label}
|
||||||
|
href={activeProjectHref ? href(activeProjectHref) : undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
import { Building2Icon } from 'lucide-react'
|
import { Building2Icon } from 'lucide-react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* نشان «پروژه فعال» هیرو — آیکون و متن کنار هم، با فاصلهٔ یکنواخت و
|
* نشان «پروژه فعال» هیرو — آیکون و متن کنار هم، با فاصلهٔ یکنواخت و
|
||||||
* چیدمان وسطچین. فلکسباکس ستون را از بین میبرد تا فاصلهٔ آیکون و متن
|
* چیدمان وسطچین. فلکسباکس ستون را از بین میبرد تا فاصلهٔ آیکون و متن
|
||||||
* یکدست بماند و آیکون درست با متن همتراز شود.
|
* یکدست بماند و آیکون درست با متن همتراز شود.
|
||||||
*/
|
*/
|
||||||
export function HeroProjectBadge({ value, label }: { value: string; label: string }) {
|
export function HeroProjectBadge({
|
||||||
return (
|
value,
|
||||||
|
label,
|
||||||
|
href,
|
||||||
|
}: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
/** نشانی صفحهٔ پروژهٔ فعال — نشان کلیکپذیر میشود و به همان پروژه میرود */
|
||||||
|
href?: string
|
||||||
|
}) {
|
||||||
|
const body = (
|
||||||
<div className="flex items-center gap-3.5 text-center">
|
<div className="flex items-center gap-3.5 text-center">
|
||||||
{/* آیکون در کادر مربعی با نشان زندهٔ مسی */}
|
{/* آیکون در کادر مربعی با نشان زندهٔ مسی */}
|
||||||
<span className="relative flex h-14 w-14 shrink-0 items-center justify-center rounded-xl border border-copper/40 bg-copper/10">
|
<span className="relative flex h-14 w-14 shrink-0 items-center justify-center rounded-xl border border-copper/40 bg-copper/10">
|
||||||
@@ -23,4 +33,16 @@ export function HeroProjectBadge({ value, label }: { value: string; label: strin
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// بدون نشانی همان ظرف ساده میماند؛ با نشانی، کل نشان یک پیوند به پروژه است
|
||||||
|
if (!href) return body
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={href}
|
||||||
|
className="block rounded-2xl transition-transform duration-300 ease-lux hover:-translate-y-0.5 focus:outline-none focus-visible:ring-2 focus-visible:ring-copper"
|
||||||
|
aria-label={label}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user