first project version mrDecor
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{"sessionId":"3a1cb1","location":"feature-243-gold-store.tsx","message":"Feature243GoldStore mounted","data":{"innerWidth":1920,"isMobile":false,"dir":"rtl"},"hypothesisId":"A","timestamp":1771973317022}
|
||||
{"sessionId":"3a1cb1","location":"feature-243-gold-store.tsx","message":"Feature243GoldStore mounted","data":{"innerWidth":1920,"isMobile":false,"dir":"rtl"},"hypothesisId":"A","timestamp":1771973385387}
|
||||
{"sessionId":"3a1cb1","location":"feature-243-gold-store.tsx","message":"Section vs cards wrapper layout","data":{"sectionWidth":1905,"wrapperWidth":1232,"leftGap":336.5,"rightGap":336.5,"viewportWidth":1920},"hypothesisId":"B","timestamp":1771973385465}
|
||||
@@ -0,0 +1 @@
|
||||
{"sessionId":"9e190c","location":"hero-91:secondary","message":"secondary clicked","data":{},"timestamp":1771929704065}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"sessionId":"b131a6","location":"hero-247.tsx:AnimatedButton","message":"hero button inner clicked","data":{"url":"#mozakerat"},"timestamp":1771775120502,"hypothesisId":"A"}
|
||||
{"sessionId":"b131a6","location":"hero-247.tsx:AnimatedButton","message":"hero anchor clicked","data":{"url":"#mozakerat","targetExists":true},"timestamp":1771775120503,"hypothesisId":"A_E"}
|
||||
{"sessionId":"b131a6","location":"hero-247.tsx:AnimatedButton","message":"hero button inner clicked","data":{"url":"#rahavarha"},"timestamp":1771775210149,"hypothesisId":"A"}
|
||||
{"sessionId":"b131a6","location":"hero-247.tsx:AnimatedButton","message":"hero anchor clicked","data":{"url":"#rahavarha","targetExists":true,"variant":"outline"},"timestamp":1771775210149,"hypothesisId":"A_E"}
|
||||
{"sessionId":"b131a6","location":"hero-91.tsx:Link","message":"hero91 link clicked","data":{"href":"#rahavarha","targetExists":true},"timestamp":1771775671865,"hypothesisId":"B_C"}
|
||||
@@ -0,0 +1,90 @@
|
||||
{"sessionId":"e841fd","timestamp":1771996173461,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996173462,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1771996183342,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771996183342,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996183537,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996183537,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771996486694,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996486696,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771996486705,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996486706,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771996487218,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1771996487219,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":0,"h":0},"vLineRect":{"w":2,"h":297}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996487221,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1771996502765,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996502767,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771996502780,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996502781,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771996503292,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1771996503293,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":0,"h":0},"vLineRect":{"w":2,"h":297}}}
|
||||
{"sessionId":"e841fd","timestamp":1771996503294,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195052,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195054,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195064,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195066,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195581,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195582,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":0,"h":0},"vLineRect":{"w":2,"h":297}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997195583,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587327,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587328,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587338,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587339,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587846,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587847,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":313.90625,"h":2},"vLineRect":{"w":2,"h":309.5}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997587847,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1771997602265,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":2133,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997602265,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":2133}}
|
||||
{"sessionId":"e841fd","timestamp":1771997616330,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997616330,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1771997707672,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997707675,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1771997708927,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1771997708928,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":313.90625,"h":2},"vLineRect":{"w":2,"h":309.5}}}
|
||||
{"sessionId":"e841fd","timestamp":1771997708929,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1772008672649,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008672649,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772008672660,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008672660,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772008673163,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1772008673164,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":313.90625,"h":2},"vLineRect":{"w":2,"h":309.5}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008673165,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1772008684861,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008684861,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772008688438,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008688439,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1772008693291,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008693292,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1772008693358,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008693359,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772008955109,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008955110,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1772008956960,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":357,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008956961,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":357}}
|
||||
{"sessionId":"e841fd","timestamp":1772008956988,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772008956989,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772015086456,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772015086458,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772015087518,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1772015087519,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":313.90625,"h":2},"vLineRect":{"w":2,"h":309.5}}}
|
||||
{"sessionId":"e841fd","timestamp":1772015087520,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1772015089592,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772015089592,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772015092826,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772015092826,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772015096183,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772015096184,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1772016039773,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772016039774,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772016040953,"hypothesisId":"H2","location":"feature-243-gold-store.tsx:overflow","message":"section/container overflow","data":{"sectionOverflow":"visible visible visible","containerOverflow":"visible"}}
|
||||
{"sessionId":"e841fd","timestamp":1772016040953,"hypothesisId":"H3","location":"feature-243-gold-store.tsx:rects","message":"line dimensions","data":{"hLineRect":{"w":313.90625,"h":2},"vLineRect":{"w":2,"h":309.5}}}
|
||||
{"sessionId":"e841fd","timestamp":1772016040954,"hypothesisId":"H4","location":"feature-243-gold-store.tsx:zindex","message":"z-index line vs card","data":{"lineZ":"auto","cardZ":"10"}}
|
||||
{"sessionId":"e841fd","timestamp":1772016096211,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772016096211,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
{"sessionId":"e841fd","timestamp":1772017219980,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":357,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772017219980,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":357}}
|
||||
{"sessionId":"e841fd","timestamp":1772017220300,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":430,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772017220301,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":430}}
|
||||
{"sessionId":"e841fd","timestamp":1772017225619,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":357,"breakpoint":"xs","horizontalVisible":{"index0":false,"index1":false,"index2":false,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772017225619,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":357}}
|
||||
{"sessionId":"e841fd","timestamp":1772017225656,"hypothesisId":"H1","location":"feature-243-gold-store.tsx:run","message":"viewport and line visibility","data":{"innerWidth":1920,"breakpoint":"2xl","horizontalVisible":{"index0":true,"index1":true,"index2":true,"index3":false}}}
|
||||
{"sessionId":"e841fd","timestamp":1772017225657,"hypothesisId":"H5","location":"feature-243-gold-store.tsx:run","message":"design: index3 always hidden, index2 only 2xl","data":{"innerWidth":1920}}
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
description: استانداردهای سازماندهی و مدیریت کامپوننتها در پروژه
|
||||
---
|
||||
|
||||
# استاندارد سازماندهی کامپوننتها
|
||||
|
||||
این پروژه از یک ساختار پوشهبندی استاندارد برای سازماندهی کامپوننتها استفاده میکند.
|
||||
|
||||
## ساختار پوشهبندی
|
||||
|
||||
کامپوننتها در پوشه `components/` بر اساس نوع و کاربردشان سازماندهی شدهاند:
|
||||
|
||||
### 1. `components/layout/`
|
||||
کامپوننتهای مرتبط با ساختار کلی صفحه:
|
||||
- `conditional-layout.tsx` - لایهبندی شرطی برای صفحات
|
||||
- `theme-provider.tsx` - ارائهدهنده تم
|
||||
- `top-progress-bar.tsx` - نوار پیشرفت بالا
|
||||
|
||||
### 2. `components/navigation/`
|
||||
کامپوننتهای مرتبط با ناوبری و هدر/فوتر:
|
||||
- `header.tsx` - هدر اصلی
|
||||
- `header-main.tsx` - هدر اصلی (نسخه tailark)
|
||||
- `footer.tsx` - فوتر
|
||||
- `logo.tsx` - لوگو
|
||||
- `logo-main.tsx` - لوگو اصلی (نسخه tailark)
|
||||
- `theme-toggle.tsx` - دکمه تغییر تم
|
||||
|
||||
### 3. `components/sections/`
|
||||
کامپوننتهای بخشهای مختلف صفحه که به زیرپوشهها تقسیم میشوند:
|
||||
|
||||
#### `sections/hero/`
|
||||
بخشهای Hero:
|
||||
- `hero-product.tsx` - Hero صفحه محصول
|
||||
- `hero-partners.tsx` - Hero صفحه همکاران
|
||||
- `hero-home.tsx` - Hero صفحه اصلی
|
||||
- `hero-section-main.tsx` - Hero اصلی (نسخه tailark)
|
||||
- `hero-alternative.tsx` - Hero جایگزین
|
||||
|
||||
#### `sections/feature/`
|
||||
بخشهای ویژگیها:
|
||||
- `feature-text-section.tsx` - بخش متن ویژگی
|
||||
- `feature-grid-with-icons.tsx` - شبکه ویژگی با آیکون
|
||||
- `feature-carousel-grid.tsx` - شبکه کاروسل ویژگی
|
||||
- `feature-list-section.tsx` - بخش لیست ویژگی
|
||||
- `feature-carousel.tsx` - کاروسل ویژگی
|
||||
- `features-card-grid.tsx` - شبکه کارت ویژگی
|
||||
- `features-icon-grid.tsx` - شبکه آیکون ویژگی
|
||||
- `features-primary.tsx` - ویژگیهای اصلی
|
||||
|
||||
#### `sections/cta/`
|
||||
بخشهای Call-to-Action:
|
||||
- `cta-product.tsx` - CTA صفحه محصول
|
||||
- `cta-home.tsx` - CTA صفحه اصلی
|
||||
- `cta-default.tsx` - CTA پیشفرض
|
||||
- `cta-alternative.tsx` - CTA جایگزین
|
||||
|
||||
#### `sections/other/`
|
||||
سایر بخشهای صفحه:
|
||||
- `timeline-section.tsx` - بخش تایملاین
|
||||
- `pricing-section.tsx` - بخش قیمتگذاری
|
||||
- `pricing-comparator.tsx` - مقایسهکننده قیمت
|
||||
- `faqs-section.tsx` - بخش سوالات متداول
|
||||
- `logo-cloud.tsx` - ابر لوگو
|
||||
- `solutions-showcase.tsx` - نمایش راهکارها
|
||||
- `apple-cards-carousel.tsx` - کاروسل کارتهای اپل
|
||||
- `content-section.tsx` - بخش محتوا
|
||||
|
||||
### 4. `components/blog/`
|
||||
کامپوننتهای مرتبط با وبلاگ:
|
||||
- `blog-blockquote.tsx` - نقل قول وبلاگ
|
||||
- `blog-card.tsx` - کارت وبلاگ
|
||||
- `blog-collapse-section.tsx` - بخش تاشو وبلاگ
|
||||
- `blog-post-card.tsx` - کارت پست وبلاگ
|
||||
- `blog-post-meta.tsx` - متادیتای پست وبلاگ
|
||||
- `blog-pullquote.tsx` - نقل قول کشیده وبلاگ
|
||||
- `blog-tip.tsx` - نکته وبلاگ
|
||||
- `blog-toc.tsx` - فهرست مطالب وبلاگ
|
||||
- `featured-posts-grid.tsx` - شبکه پستهای ویژه
|
||||
|
||||
### 5. `components/utilities/`
|
||||
کامپوننتهای کمکی:
|
||||
- `scroll-to-top.tsx` - اسکرول به بالا
|
||||
|
||||
### 6. `components/ui/`
|
||||
کامپوننتهای UI پایه (از shadcn/ui) - این پوشه دست نخورده باقی میماند.
|
||||
|
||||
### 7. `components/home-page/`
|
||||
کامپوننتهای اختصاصی صفحه اصلی - این پوشه دست نخورده باقی میماند.
|
||||
|
||||
### 8. `components/motion-primitives/`
|
||||
پریمییتیوهای انیمیشن - این پوشه دست نخورده باقی میماند.
|
||||
|
||||
## قوانین نامگذاری
|
||||
|
||||
### نام فایلها
|
||||
- از نامهای توصیفی و معنادار استفاده کنید
|
||||
- از camelCase برای نام فایلها استفاده کنید
|
||||
- نام فایل باید نوع کامپوننت را مشخص کند (مثلاً `hero-product.tsx` نه `hero47.tsx`)
|
||||
- برای کامپوننتهای مشابه، از پسوندهای واضح استفاده کنید (مثلاً `-home`, `-product`, `-alternative`)
|
||||
|
||||
### نام Export ها
|
||||
- نام export باید با نام فایل همخوانی داشته باشد و معنادار باشد
|
||||
- از PascalCase برای نام کامپوننتها استفاده کنید
|
||||
- از نامهای عددی یا غیرمعنادار (مثل Hero47، Feature55) خودداری کنید
|
||||
- نام export باید نوع و کاربرد کامپوننت را مشخص کند (مثلاً `HeroProduct` نه `Hero47`)
|
||||
|
||||
## قوانین Import
|
||||
|
||||
هنگام import کردن کامپوننتها، از مسیرهای کامل و نامهای معنادار استفاده کنید:
|
||||
|
||||
```typescript
|
||||
// ✅ درست
|
||||
import { HeroProduct } from '@/components/sections/hero/hero-product'
|
||||
import { FeatureTextSection } from '@/components/sections/feature/feature-text-section'
|
||||
import { FeatureCarousel } from '@/components/sections/feature/feature-carousel'
|
||||
import Footer5 from '@/components/navigation/footer'
|
||||
import CtaHome from '@/components/sections/cta/cta-home'
|
||||
|
||||
// ❌ اشتباه
|
||||
import { Hero47 } from '@/components/hero47'
|
||||
import { Feature55 } from '@/components/feature55'
|
||||
import { Feature240 } from '@/components/feature240'
|
||||
```
|
||||
|
||||
## افزودن کامپوننت جدید
|
||||
|
||||
هنگام افزودن کامپوننت جدید:
|
||||
|
||||
1. **تعیین نوع کامپوننت**: کامپوننت شما در کدام دسته قرار میگیرد؟
|
||||
- Layout → `components/layout/`
|
||||
- Navigation → `components/navigation/`
|
||||
- Hero Section → `components/sections/hero/`
|
||||
- Feature Section → `components/sections/feature/`
|
||||
- CTA Section → `components/sections/cta/`
|
||||
- Other Section → `components/sections/other/`
|
||||
- Blog Component → `components/blog/`
|
||||
- Utility → `components/utilities/`
|
||||
|
||||
2. **نامگذاری مناسب**: نام فایل باید توصیفی باشد و نوع کامپوننت را مشخص کند.
|
||||
|
||||
3. **بهروزرسانی این فایل**: اگر دسته جدیدی ایجاد کردید، این فایل را بهروزرسانی کنید.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **پوشههای موجود**: پوشههای `ui/`, `home-page/`, و `motion-primitives/` دست نخورده باقی میمانند.
|
||||
- **سازماندهی مداوم**: همیشه کامپوننتهای جدید را در پوشه مناسب قرار دهید.
|
||||
- **یکپارچگی**: از قرار دادن فایلها در root پوشه `components/` خودداری کنید (به جز پوشههای خاص).
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
description: استاندارد تایپوگرافی در همه صفحات – سایز، وزن و سلسلهمراتب
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# استاندارد تایپوگرافی
|
||||
|
||||
در همه صفحات از مقیاس تم و **سلسلهمراتب تایپوگرافی** استفاده کنید: یک سطح غالب (h1)، سپس سطوح پایینتر برای بخشها و زیربخشها.
|
||||
|
||||
## سلسلهمراتب (الزامی)
|
||||
|
||||
| سطح | تگ | کلاس سایز | وزن | کاربرد |
|
||||
|-----|-----|------------|-----|--------|
|
||||
| ۱ | h1 | `text-heading-1` | `font-bold` | **فقط یک بار در صفحه** – عنوان اصلی/ Hero |
|
||||
| ۲ | h2 | `text-heading-2` | `font-semibold` | عنوان هر **بخش** (Section) |
|
||||
| ۳ | h3 | `text-heading-3` | `font-semibold` | عنوان **زیربخش** یا کارت |
|
||||
| ۴ | h4 | `text-heading-4` | `font-medium` | عنوان ریز (آیتم، لیست) |
|
||||
| ۵ | h5 | `text-heading-5` | `font-medium` | عنوان خرد |
|
||||
| متن | p/span | `text-body` | `font-normal` | متن بدن |
|
||||
| فرعی | p/span | `text-body-sm` | `font-normal` | توضیح کوتاه، زیرمتن |
|
||||
| برچسب | span | `text-caption` | `font-medium` | زیرنویس، label |
|
||||
|
||||
قانون: **هر صفحه فقط یک h1**. بعد از آن فقط h2 برای بخشها، سپس h3 برای کارت/زیربخش، و h4 برای عنوانهای ریز. از پرش سطح (مثلاً h1 بعد h4) خودداری کنید.
|
||||
|
||||
## فونت
|
||||
|
||||
- **فونت اصلی**: IRANSansX (ارثبری از `body`). از فونتهای دیگر (مثل `font-playfair`, `font-calSans`) استفاده نکنید.
|
||||
|
||||
## نمونه درست (با رعایت سلسلهمراتب)
|
||||
|
||||
```tsx
|
||||
// ✅ درست – یک h1، سپس h2 برای بخش، h3 برای کارت
|
||||
<h1 className="text-heading-1 font-bold">عنوان صفحه</h1>
|
||||
<h2 className="text-heading-2 font-semibold">عنوان بخش</h2>
|
||||
<h3 className="text-heading-3 font-semibold">عنوان کارت</h3>
|
||||
<p className="text-body text-muted-foreground">متن توضیح.</p>
|
||||
<span className="text-caption font-medium">برچسب</span>
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ❌ اشتباه – چند h1 در صفحه، یا سایز دستی
|
||||
<h1>...</h1>
|
||||
<h1>...</h1> // فقط یک h1 مجاز
|
||||
<p className="text-2xl font-semibold">...</p> // از text-heading-* یا text-body استفاده کن
|
||||
```
|
||||
|
||||
## رنگ متن (الزامی)
|
||||
|
||||
- **متن اصلی**: `text-foreground`
|
||||
- **متن توضیح/فرعی**: `text-muted-foreground`
|
||||
- **تأکید (قیمت، لینک، CTA)**: `text-primary`
|
||||
- **متن روی پسزمینه تیره (overlay)**: `text-overlay-foreground` (نه `text-white`)
|
||||
- از رنگهای هاردکد (مثل `text-white`, `text-black`, `text-gray-*`) استفاده نکنید.
|
||||
|
||||
## نکات
|
||||
|
||||
- تگهای `h1`–`h6` در `globals.css` سایز/وزن پیشفرض دارند؛ در صورت نیاز فقط کلاسهای تم را اضافه کنید.
|
||||
- از `text-heading-1` تا `text-caption` استفاده کنید؛ از `text-4xl`, `text-2xl` و مانند آن برای عناوین/متن بدن استفاده نکنید.
|
||||
- مقیاس تایپوگرافی در `globals.css` با فونتهای کمی کوچکتر و فاصله خط مناسب برای حس لوکس تعریف شده است.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# Next.js
|
||||
.next
|
||||
out
|
||||
dist
|
||||
build
|
||||
|
||||
# Production
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Local env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# Vercel
|
||||
.vercel
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.dockerignore
|
||||
DOCKER.md
|
||||
@@ -0,0 +1,9 @@
|
||||
# ربات تلگرام برای دریافت درخواستهای مشاوره آنلاین
|
||||
# از @BotFather ربات بسازید و توکن را اینجا قرار دهید.
|
||||
|
||||
# آیدی چت (عدد) یا @username کانال/گروهی که پیامها ارسال شوند.
|
||||
# برای گرفتن chat_id یک پیام به ربات بفرستید و این آدرس را باز کنید:
|
||||
# https://api.telegram.org/bot<TOKEN>/getUpdates
|
||||
TELEGRAM_BOT_TOKEN=1925675433:uIq2vpidQoH8YJRldjGyiQP0haoeqGOj67Q
|
||||
TELEGRAM_CHAT_ID=5419302943
|
||||
TELEGRAM_URL=https://tapi.bale.ai
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
.yarn
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,120 @@
|
||||
# راهنمای استفاده از Docker
|
||||
|
||||
## نصب Docker
|
||||
|
||||
اگر Docker روی سیستم شما نصب نیست، میتوانید از یکی از روشهای زیر استفاده کنید:
|
||||
|
||||
### Windows
|
||||
|
||||
1. **Docker Desktop برای Windows** را دانلود و نصب کنید:
|
||||
- دانلود از: https://www.docker.com/products/docker-desktop/
|
||||
- پس از نصب، Docker Desktop را اجرا کنید
|
||||
- منتظر بمانید تا Docker شروع به کار کند
|
||||
|
||||
2. **بررسی نصب:**
|
||||
```powershell
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
|
||||
# اضافه کردن کاربر به گروه docker
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
1. **Docker Desktop برای Mac** را دانلود و نصب کنید:
|
||||
- دانلود از: https://www.docker.com/products/docker-desktop/
|
||||
|
||||
## ساخت و اجرای کانتینر
|
||||
|
||||
### استفاده از Docker Compose (پیشنهادی)
|
||||
|
||||
**نکته:** در نسخههای جدید Docker، از `docker compose` (با فاصله) استفاده میشود به جای `docker-compose` (با خط تیره).
|
||||
|
||||
```bash
|
||||
# ساخت و اجرای کانتینر
|
||||
docker compose up -d
|
||||
|
||||
# مشاهده لاگها
|
||||
docker compose logs -f
|
||||
|
||||
# توقف کانتینر
|
||||
docker compose down
|
||||
|
||||
# ساخت مجدد و اجرا
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
**اگر دستور بالا کار نکرد، از نسخه قدیمی استفاده کنید:**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### استفاده مستقیم از Docker
|
||||
|
||||
```bash
|
||||
# ساخت image
|
||||
docker build -t montazeran .
|
||||
|
||||
# اجرای کانتینر
|
||||
docker run -p 3000:3000 montazeran
|
||||
|
||||
# اجرا در حالت detached
|
||||
docker run -d -p 3000:3000 --name montazeran-app montazeran
|
||||
```
|
||||
|
||||
## دسترسی به اپلیکیشن
|
||||
|
||||
پس از اجرای کانتینر، اپلیکیشن در آدرس زیر در دسترس است:
|
||||
- http://localhost:3000
|
||||
|
||||
## عیبیابی
|
||||
|
||||
### مشکل: دستور docker شناسایی نمیشود
|
||||
|
||||
**راه حل:**
|
||||
1. مطمئن شوید Docker Desktop نصب و در حال اجرا است
|
||||
2. PowerShell را بسته و دوباره باز کنید
|
||||
3. یا از Command Prompt استفاده کنید
|
||||
4. بررسی کنید که Docker در PATH سیستم قرار دارد
|
||||
|
||||
### مشکل: Port 3000 در حال استفاده است
|
||||
|
||||
**راه حل:**
|
||||
```bash
|
||||
# تغییر پورت در docker-compose.yml
|
||||
ports:
|
||||
- "3001:3000" # حالا از پورت 3001 استفاده کنید
|
||||
```
|
||||
|
||||
یا کانتینر دیگری که از پورت 3000 استفاده میکند را متوقف کنید.
|
||||
|
||||
## متغیرهای محیطی
|
||||
|
||||
اگر نیاز به تنظیم متغیرهای محیطی دارید، میتوانید فایل `.env` را ایجاد کنید و در `docker-compose.yml` آن را اضافه کنید:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
next-app:
|
||||
env_file:
|
||||
- .env
|
||||
```
|
||||
|
||||
یا به صورت مستقیم:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
next-app:
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_API_URL=https://api.example.com
|
||||
```
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# Stage 1: Dependencies
|
||||
FROM node:20-alpine AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json package-lock.json* yarn.lock* ./
|
||||
|
||||
# Install dependencies
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn ; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
else npm i; \
|
||||
fi
|
||||
|
||||
# Stage 2: Builder
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Set environment variables for build
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Stage 3: Runner
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy necessary files from standalone build
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Copy standalone build files
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import type { Metadata } from 'next'
|
||||
import { getContentSync } from '@/lib/content'
|
||||
import { AboutCompanySection } from '@/components/sections/other/about-company-section'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const content = getContentSync()
|
||||
const meta = content.meta.pages.about
|
||||
return {
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
}
|
||||
}
|
||||
|
||||
export default function AboutPage() {
|
||||
const content = getContentSync()
|
||||
const about = content.pages.about as {
|
||||
heading: string
|
||||
description: string
|
||||
title: string
|
||||
subtitle: string
|
||||
bio: string
|
||||
vision: string
|
||||
philosophyHeading: string
|
||||
outro: string
|
||||
}
|
||||
|
||||
const imageLogo = content.images?.logo
|
||||
? { src: content.images.logo, alt: content.meta.siteName }
|
||||
: undefined
|
||||
const imageOffice = {
|
||||
src: '/assets/about-us/f97a15aa-cf15-4508-ae5d-d3e5211b433e.png',
|
||||
alt: 'نمای دفتر مستر دکور',
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<AboutCompanySection
|
||||
mainHeading={about.heading}
|
||||
description={about.description}
|
||||
title={about.title}
|
||||
subtitle={about.subtitle}
|
||||
bio={about.bio}
|
||||
vision={about.vision}
|
||||
philosophyHeading={about.philosophyHeading}
|
||||
outro={about.outro}
|
||||
imageLogo={imageLogo}
|
||||
imageOffice={imageOffice}
|
||||
/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Clock3, Paintbrush2, ShieldCheck, Wrench } from "lucide-react";
|
||||
import { MeteorTitleBackground } from "@/components/utilities/meteor-title-background";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "خدمات پس از فروش",
|
||||
description:
|
||||
"خدمات پس از فروش مستر دکور برای استند و ملزومات ویترین طلافروشی شامل تعویض روکش، تعمیرات تخصصی و پشتیبانی اجرایی است.",
|
||||
};
|
||||
|
||||
const serviceItems = [
|
||||
"تعویض روکش استندهای دکوری با هماهنگی رنگ و متریال جدید",
|
||||
"ترمیم خط و خش، لب پریدگی و بازسازی نقاط آسیب دیده بدنه",
|
||||
"تعمیر کشوهای ریلی، رگلاژ، تعویض ریل و رفع گیرکردن یا لقی",
|
||||
"تنظیم و تعمیر یراق آلات درب و محفظه های نمایشی",
|
||||
"بازدید دوره ای برای پیشگیری از خرابی و افزایش طول عمر تجهیزات",
|
||||
];
|
||||
|
||||
const supportStats = [
|
||||
{
|
||||
icon: Paintbrush2,
|
||||
title: "تعویض روکش",
|
||||
description: "بازسازی ظاهر استندها با حفظ هویت بصری فروشگاه",
|
||||
},
|
||||
{
|
||||
icon: Wrench,
|
||||
title: "تعمیر کشو",
|
||||
description: "رفع خرابی ریل و مکانیزم برای کارکرد روان روزانه",
|
||||
},
|
||||
{
|
||||
icon: Clock3,
|
||||
title: "زمان بندی منعطف",
|
||||
description: "هماهنگی مراجعه در ساعات کم تردد فروشگاه",
|
||||
},
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
title: "کیفیت اجرا",
|
||||
description: "تحویل نهایی پس از تست عملکرد و کنترل کیفی",
|
||||
},
|
||||
];
|
||||
|
||||
export default function AfterSalesPage() {
|
||||
return (
|
||||
<main className="min-h-screen pb-24 pt-16 md:pt-20">
|
||||
<section className="relative overflow-hidden bg-muted/60 py-20 md:py-24">
|
||||
<MeteorTitleBackground className="opacity-100" />
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6" dir="rtl">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<div className="relative z-10 rounded-2xl px-4 py-6 sm:px-8">
|
||||
<h1 className="text-heading-1 font-bold text-foreground">
|
||||
خدمات پس از فروش مستر دکور
|
||||
</h1>
|
||||
<p className="mt-5 text-body text-muted-foreground">
|
||||
کیفیت ویترین فقط به روز تحویل محدود نمی شود. خدمات پس از فروش مستر
|
||||
دکور کمک می کند استندهای شما همیشه حرفه ای، منظم و آماده نمایش موثر
|
||||
سرویس های طلا و جواهر باقی بمانند.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-14 md:py-16">
|
||||
<div className="mx-auto grid w-full max-w-7xl gap-10 px-4 sm:px-6 lg:grid-cols-3" dir="rtl">
|
||||
<div className="space-y-8 lg:col-span-2">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-heading-4 font-semibold text-foreground text-xl">
|
||||
تعویض روکش استندهای دکوری
|
||||
</h2>
|
||||
<p className="text-body text-muted-foreground">
|
||||
روکش استندها در فضای پررفت وآمد طلافروشی به مرور زمان دچار افت
|
||||
کیفیت ظاهری می شود. تیم ما با بررسی دقیق نوع متریال، شرایط نور و
|
||||
سبک چیدمان فروشگاه، روکش جدید را انتخاب و اجرا می کند تا ظاهر
|
||||
استندها دوباره یکدست، تمیز و هماهنگ با برند شما شود.
|
||||
</p>
|
||||
<p className="text-body text-muted-foreground">
|
||||
در فرآیند تعویض روکش، تلاش می کنیم کمترین اختلال در فعالیت روزانه
|
||||
فروشگاه ایجاد شود و نتیجه نهایی علاوه بر زیبایی، مقاومت مناسب در
|
||||
برابر سایش و نظافت مداوم داشته باشد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-heading-4 font-semibold text-foreground text-xl">
|
||||
تعمیر کشو و مکانیزم حرکتی
|
||||
</h2>
|
||||
<p className="text-body text-muted-foreground">
|
||||
کشوهای استند در استفاده مداوم ممکن است دچار گیرکردن، صدای اضافی،
|
||||
لقی یا بسته نشدن کامل شوند. خدمات تعمیر کشو شامل رگلاژ تخصصی،
|
||||
تعمیر یا تعویض ریل، بررسی اتصالات داخلی و تنظیم مجدد حرکات کشو است
|
||||
تا عملکرد روان و قابل اعتماد به سیستم برگردد.
|
||||
</p>
|
||||
<p className="text-body text-muted-foreground">
|
||||
پس از پایان تعمیر، همه بخش ها تست می شوند تا هنگام استفاده سریع
|
||||
در محیط فروش، کشو بدون تنش و با ایمنی کامل باز و بسته شود.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-heading-5 font-semibold text-foreground text-lg">
|
||||
خدمات قابل ارائه در یک نگاه
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{serviceItems.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="rounded-xl border border-border bg-card px-4 py-3"
|
||||
>
|
||||
<p className="text-body text-foreground">{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="lg:col-span-1">
|
||||
<div className="rounded-2xl border border-border bg-muted/50 p-5 lg:sticky lg:top-24">
|
||||
<h3 className="text-heading-5 font-semibold text-foreground text-lg">
|
||||
مزیت خدمات ما
|
||||
</h3>
|
||||
<div className="mt-5 space-y-4">
|
||||
{supportStats.map((stat) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<div key={stat.title} className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-9 w-9 items-center justify-center rounded-lg bg-card">
|
||||
<Icon className="h-4 w-4 text-primary" />
|
||||
</span>
|
||||
<div className="space-y-1">
|
||||
<p className="text-body-sm font-medium text-foreground">
|
||||
{stat.title}
|
||||
</p>
|
||||
<p className="text-caption text-muted-foreground">
|
||||
{stat.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
'use server'
|
||||
|
||||
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN
|
||||
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID
|
||||
const TELEGRAM_URL = process.env.TELEGRAM_URL
|
||||
|
||||
export type ConsultationFormData = {
|
||||
name: string
|
||||
phone: string
|
||||
email?: string
|
||||
subject?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
function buildTelegramMessage(data: ConsultationFormData): string {
|
||||
const lines = [
|
||||
'📩 *درخواست مشاوره آنلاین*',
|
||||
'',
|
||||
`👤 *نام:* ${data.name}`,
|
||||
`📱 *تلفن:* ${data.phone}`,
|
||||
]
|
||||
if (data.email?.trim()) {
|
||||
lines.push(`📧 *ایمیل:* ${data.email.trim()}`)
|
||||
}
|
||||
if (data.subject?.trim()) {
|
||||
lines.push(`📌 *موضوع:* ${data.subject.trim()}`)
|
||||
}
|
||||
if (data.message?.trim()) {
|
||||
lines.push('', '*توضیحات:*', data.message.trim())
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export async function submitConsultation(
|
||||
data: ConsultationFormData
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const name = data.name?.trim()
|
||||
const phone = data.phone?.trim().replace(/\s/g, '')
|
||||
|
||||
if (!name || name.length < 2) {
|
||||
return { success: false, error: 'نام و نام خانوادگی را وارد کنید.' }
|
||||
}
|
||||
if (!phone || phone.length < 10) {
|
||||
return { success: false, error: 'شماره تماس معتبر وارد کنید.' }
|
||||
}
|
||||
|
||||
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||
console.error('TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set')
|
||||
return {
|
||||
success: false,
|
||||
error: 'پیکربندی ارسال پیام انجام نشده است. لطفاً با پشتیبانی تماس بگیرید.',
|
||||
}
|
||||
}
|
||||
|
||||
const text = buildTelegramMessage({
|
||||
name,
|
||||
phone,
|
||||
email: data.email,
|
||||
subject: data.subject,
|
||||
message: data.message,
|
||||
})
|
||||
|
||||
const url = `${TELEGRAM_URL}/bot${TELEGRAM_BOT_TOKEN}/sendMessage`
|
||||
const body = {
|
||||
chat_id: TELEGRAM_CHAT_ID,
|
||||
text,
|
||||
parse_mode: 'Markdown',
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
const result = await res.json()
|
||||
|
||||
if (!res.ok || !result.ok) {
|
||||
console.error('Telegram API error:', result)
|
||||
return {
|
||||
success: false,
|
||||
error: 'ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.',
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
console.error('Failed to send to Telegram', e)
|
||||
return {
|
||||
success: false,
|
||||
error: 'ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.',
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import type { Metadata } from 'next'
|
||||
import { ConsultationForm } from '@/components/sections/other/consultation-form'
|
||||
import { getContentSync } from '@/lib/content'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const content = getContentSync()
|
||||
const meta = content.meta.pages.consultation
|
||||
return {
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
}
|
||||
}
|
||||
|
||||
export default function ConsultationPage() {
|
||||
const content = getContentSync()
|
||||
const { consultation } = content.pages
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<div className="mx-auto max-w-7xl px-6 py-24 md:py-32 lg:py-40">
|
||||
<div className="space-y-10 text-center">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-heading-1 font-bold">
|
||||
{consultation.heading}
|
||||
</h1>
|
||||
<p className="text-body text-muted-foreground mx-auto max-w-2xl">
|
||||
{consultation.description}
|
||||
</p>
|
||||
</div>
|
||||
<ConsultationForm />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
'use server'
|
||||
|
||||
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN
|
||||
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID
|
||||
const TELEGRAM_URL = process.env.TELEGRAM_URL ?? 'https://api.telegram.org'
|
||||
|
||||
export type ContactFormData = {
|
||||
name: string
|
||||
phone: string
|
||||
email?: string
|
||||
subject?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
function buildTelegramMessage(data: ContactFormData): string {
|
||||
const lines = [
|
||||
'📞 *تماس با ما*',
|
||||
'',
|
||||
`👤 *نام:* ${data.name}`,
|
||||
`📱 *تلفن:* ${data.phone}`,
|
||||
]
|
||||
if (data.email?.trim()) {
|
||||
lines.push(`📧 *ایمیل:* ${data.email.trim()}`)
|
||||
}
|
||||
if (data.subject?.trim()) {
|
||||
lines.push(`📌 *موضوع:* ${data.subject.trim()}`)
|
||||
}
|
||||
if (data.message?.trim()) {
|
||||
lines.push('', '*پیام:*', data.message.trim())
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export async function submitContact(
|
||||
data: ContactFormData
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const name = data.name?.trim()
|
||||
const phone = data.phone?.trim().replace(/\s/g, '')
|
||||
|
||||
if (!name || name.length < 2) {
|
||||
return { success: false, error: 'نام و نام خانوادگی را وارد کنید.' }
|
||||
}
|
||||
if (!phone || phone.length < 10) {
|
||||
return { success: false, error: 'شماره تماس معتبر وارد کنید.' }
|
||||
}
|
||||
|
||||
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||
console.error('TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set')
|
||||
return {
|
||||
success: false,
|
||||
error: 'پیکربندی ارسال پیام انجام نشده است. لطفاً با پشتیبانی تماس بگیرید.',
|
||||
}
|
||||
}
|
||||
|
||||
const text = buildTelegramMessage({
|
||||
name,
|
||||
phone,
|
||||
email: data.email,
|
||||
subject: data.subject,
|
||||
message: data.message,
|
||||
})
|
||||
|
||||
const url = `${TELEGRAM_URL}/bot${TELEGRAM_BOT_TOKEN}/sendMessage`
|
||||
const body = {
|
||||
chat_id: TELEGRAM_CHAT_ID,
|
||||
text,
|
||||
parse_mode: 'Markdown',
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
const result = await res.json()
|
||||
|
||||
if (!res.ok || !result.ok) {
|
||||
console.error('Telegram API error:', result)
|
||||
return {
|
||||
success: false,
|
||||
error: 'ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.',
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
console.error('Failed to send to Telegram', e)
|
||||
return {
|
||||
success: false,
|
||||
error: 'ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.',
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from 'react'
|
||||
import type { Metadata } from 'next'
|
||||
import { ContactInfoGrid } from '@/components/sections/other/contact-info-grid'
|
||||
import { ContactForm } from '@/components/sections/other/contact-form'
|
||||
import { getContentSync } from '@/lib/content'
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
try {
|
||||
const content = getContentSync()
|
||||
const meta = content.meta?.pages?.contact
|
||||
if (meta?.title) {
|
||||
return { title: meta.title, description: meta.description ?? undefined }
|
||||
}
|
||||
} catch {
|
||||
// fallback if content fails
|
||||
}
|
||||
return { title: 'تماس با ما', description: 'برای ارتباط با مستر دکور با ما تماس بگیرید.' }
|
||||
}
|
||||
|
||||
export default function ContactPage() {
|
||||
const content = getContentSync()
|
||||
const contact = content.pages?.contact
|
||||
// const siteName = content.meta?.siteName ?? 'مستر دکور'
|
||||
const heading = contact?.heading ?? 'تماس با ما'
|
||||
const description =
|
||||
contact?.description ?? 'برای دریافت مشاوره، استعلام قیمت و انتخاب بهترین دکور ویترین طلافروشی با ما در ارتباط باشید.'
|
||||
const contactItems = [
|
||||
{
|
||||
label: 'ایمیل',
|
||||
description: 'برای دریافت پیش فاکتور، کاتالوگ یا پاسخ تخصصی، درخواست خود را ایمیل کنید.',
|
||||
value: 'info@mrdecorr.com',
|
||||
href: 'mailto:info@mrdecorr.com',
|
||||
icon: 'mail' as const,
|
||||
},
|
||||
{
|
||||
label: 'دفتر',
|
||||
description: 'برای هماهنگی جلسه حضوری و بررسی دقیق نیاز فروشگاه، به دفتر ما مراجعه کنید.',
|
||||
value: 'مشاهده موقعیت روی نقشه',
|
||||
href: 'https://maps.google.com',
|
||||
icon: 'map' as const,
|
||||
},
|
||||
{
|
||||
label: 'تلفن',
|
||||
description: 'برای مشاوره سریع و ثبت سفارش، از شنبه تا پنجشنبه پاسخگوی شما هستیم.',
|
||||
value: '۰۹۱۲ ۹۴ ۹۷ ۳۱۵',
|
||||
href: 'tel:09129497315',
|
||||
icon: 'phone' as const,
|
||||
},
|
||||
{
|
||||
label: 'پشتیبانی واتساپ',
|
||||
description: 'برای ارسال عکس ویترین و دریافت پیشنهاد اولیه، در واتساپ پیام دهید.',
|
||||
value: 'شروع گفتگو',
|
||||
href: 'https://wa.me/989129497315',
|
||||
icon: 'chat' as const,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<ContactInfoGrid title={heading} description={description} items={contactItems} />
|
||||
<section
|
||||
id="contact-form"
|
||||
className="flex min-h-[50vh] flex-col items-center justify-center px-4 py-24 md:px-6 md:py-32 lg:py-40"
|
||||
aria-labelledby="contact-form-heading"
|
||||
>
|
||||
<div className="mx-auto w-full max-w-7xl">
|
||||
<div className="flex flex-col items-center space-y-10 text-center">
|
||||
<div className="space-y-4">
|
||||
<h2
|
||||
id="contact-form-heading"
|
||||
className="text-heading-2 font-semibold text-foreground"
|
||||
>
|
||||
تکمیل فرم
|
||||
</h2>
|
||||
<p className="text-body mx-auto max-w-2xl text-muted-foreground">
|
||||
اطلاعات فروشگاه و نیاز خود را ثبت کنید تا کارشناسان مستر دکور در کوتاه ترین زمان با شما تماس بگیرند.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full max-w-2xl">
|
||||
<ContactForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
'use server'
|
||||
|
||||
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN
|
||||
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID
|
||||
const TELEGRAM_URL = process.env.TELEGRAM_URL ?? 'https://api.telegram.org'
|
||||
|
||||
export type DemoRequestFormData = {
|
||||
name: string
|
||||
phone: string
|
||||
product: string
|
||||
email?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
function buildTelegramMessage(data: DemoRequestFormData): string {
|
||||
const lines = [
|
||||
'🎬 *درخواست دمو*',
|
||||
'',
|
||||
`👤 *نام:* ${data.name}`,
|
||||
`📱 *تلفن:* ${data.phone}`,
|
||||
`📦 *نرمافزار مورد نظر:* ${data.product}`,
|
||||
]
|
||||
if (data.email?.trim()) {
|
||||
lines.push(`📧 *ایمیل:* ${data.email.trim()}`)
|
||||
}
|
||||
if (data.message?.trim()) {
|
||||
lines.push('', '*توضیحات:*', data.message.trim())
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export async function submitDemoRequest(
|
||||
data: DemoRequestFormData
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const name = data.name?.trim()
|
||||
const phone = data.phone?.trim().replace(/\s/g, '')
|
||||
|
||||
if (!name || name.length < 2) {
|
||||
return { success: false, error: 'نام و نام خانوادگی را وارد کنید.' }
|
||||
}
|
||||
if (!phone || phone.length < 10) {
|
||||
return { success: false, error: 'شماره تماس معتبر وارد کنید.' }
|
||||
}
|
||||
|
||||
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
|
||||
console.error('TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID is not set')
|
||||
return {
|
||||
success: false,
|
||||
error: 'پیکربندی ارسال پیام انجام نشده است. لطفاً با پشتیبانی تماس بگیرید.',
|
||||
}
|
||||
}
|
||||
|
||||
const product = data.product?.trim()
|
||||
if (!product || product === '__empty__') {
|
||||
return { success: false, error: 'نرمافزار مورد نظر را انتخاب کنید.' }
|
||||
}
|
||||
|
||||
const text = buildTelegramMessage({
|
||||
name,
|
||||
phone,
|
||||
product,
|
||||
email: data.email,
|
||||
message: data.message,
|
||||
})
|
||||
|
||||
const url = `${TELEGRAM_URL}/bot${TELEGRAM_BOT_TOKEN}/sendMessage`
|
||||
const body = {
|
||||
chat_id: TELEGRAM_CHAT_ID,
|
||||
text,
|
||||
parse_mode: 'Markdown',
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
const result = await res.json()
|
||||
|
||||
if (!res.ok || !result.ok) {
|
||||
console.error('Telegram API error:', result)
|
||||
return {
|
||||
success: false,
|
||||
error: 'ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.',
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
console.error('Failed to send to Telegram', e)
|
||||
return {
|
||||
success: false,
|
||||
error: 'ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.',
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
import type { Metadata } from 'next'
|
||||
import { Hero204 } from '@/components/sections/hero/hero-204'
|
||||
import { DemoRequestForm } from '@/components/sections/other/demo-request-form'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'درخواست دمو',
|
||||
description:
|
||||
'درخواست دمو محصولات و خدمات مستر دکور. سامانههای تخصصی بازار طلا را از نزدیک ببینید؛ فرم را پر کنید تا برای هماهنگی دمو با شما تماس بگیریم.',
|
||||
}
|
||||
|
||||
export default function DemoRequestPage() {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<Hero204 />
|
||||
<div className="mx-auto max-w-7xl px-6 py-24 md:py-32 lg:py-40">
|
||||
<div className="space-y-10 text-center">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-heading-2 font-semibold text-foreground">
|
||||
تکمیل فرم
|
||||
</h2>
|
||||
<p className="text-body text-muted-foreground mx-auto max-w-2xl">
|
||||
اطلاعات خود را در فرم زیر وارد کنید تا برای هماهنگی جلسه دمو با شما تماس بگیریم.
|
||||
</p>
|
||||
</div>
|
||||
<DemoRequestForm />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { Faq16 } from "@/components/sections/other/faq-16";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "سوالات متداول",
|
||||
description: "پاسخ به سوالات متداول درباره طراحی، اجرا، سفارش و پشتیبانی خدمات مستر دکور.",
|
||||
};
|
||||
|
||||
export default function FaqPage() {
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<Faq16 />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+703
@@ -0,0 +1,703 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
/* IRANSansX Font Faces */
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-Thin.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-UltraLight.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-UltraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-Light.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-Medium.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-DemiBold.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-DemiBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-ExtraBold.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-ExtraBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-Black.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-Black.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-Bold.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSansX;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("/assets/irsansx/Woff/IRANSansXFaNum-Regular.woff") format("woff"),
|
||||
url("/assets/irsansx/Woff2/IRANSansXFaNum-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/*
|
||||
* تم واحد (بدون لایت/دارک) — پالت اصلاحشده
|
||||
* پایه: شرابی-مشکی گرم | کارت: بورگوندی کنترلشده (کمتر ماژنتا) | متن: کرم گرم (نه خاکستری آبی)
|
||||
* برند: طلایی #c99d4d | ثانویه: سرمهای | طلایی/زمردی برای گرادیان و border انیمیشن
|
||||
*/
|
||||
|
||||
@theme inline {
|
||||
/* Design tokens → Tailwind colors */
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-overlay: var(--overlay);
|
||||
--color-overlay-foreground: var(--overlay-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
/* Gold & Emerald scales for utility use */
|
||||
--color-gold-50: var(--gold-50);
|
||||
--color-gold-100: var(--gold-100);
|
||||
--color-gold-200: var(--gold-200);
|
||||
--color-gold-300: var(--gold-300);
|
||||
--color-gold-400: var(--gold-400);
|
||||
--color-gold-500: var(--gold-500);
|
||||
--color-gold-600: var(--gold-600);
|
||||
--color-gold-700: var(--gold-700);
|
||||
--color-gold-800: var(--gold-800);
|
||||
--color-gold-900: var(--gold-900);
|
||||
--color-gold-950: var(--gold-950);
|
||||
--color-emerald-50: var(--emerald-50);
|
||||
--color-emerald-100: var(--emerald-100);
|
||||
--color-emerald-200: var(--emerald-200);
|
||||
--color-emerald-300: var(--emerald-300);
|
||||
--color-emerald-400: var(--emerald-400);
|
||||
--color-emerald-500: var(--emerald-500);
|
||||
--color-emerald-600: var(--emerald-600);
|
||||
--color-emerald-700: var(--emerald-700);
|
||||
--color-emerald-800: var(--emerald-800);
|
||||
--color-emerald-900: var(--emerald-900);
|
||||
--color-emerald-950: var(--emerald-950);
|
||||
--font-sans: var(--font-iran-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-iran-sans: "IRANSansX", "system-ui", "Avenir", "Helvetica", "Arial",
|
||||
"sans-serif";
|
||||
--font-size-heading-1: clamp(2rem, 4vw + 1rem, 3.75rem);
|
||||
--font-size-heading-2: clamp(1.5rem, 3vw + 0.5rem, 2.25rem);
|
||||
--font-size-heading-3: clamp(1.25rem, 2vw + 0.5rem, 1.875rem);
|
||||
--font-size-heading-4: 1.25rem;
|
||||
--font-size-heading-5: 1.125rem;
|
||||
--font-size-body: 1rem;
|
||||
--font-size-body-sm: 0.875rem;
|
||||
--font-size-caption: 0.75rem;
|
||||
--font-weight-heading: 700;
|
||||
--font-weight-subheading: 600;
|
||||
--font-weight-body: 400;
|
||||
--font-weight-medium: 500;
|
||||
--line-height-heading: 1.25;
|
||||
--line-height-body: 1.6;
|
||||
--line-height-relaxed: 1.75;
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--animate-line-shadow: line-shadow 15s linear infinite;
|
||||
@keyframes line-shadow {
|
||||
0% { background-position: 0 0; }
|
||||
100% { background-position: 100% -100%; }
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
/* ----- Master palette from provided swatch ----- */
|
||||
--tone-1: #110507;
|
||||
--tone-2: #230a0d;
|
||||
--tone-3: #340e14;
|
||||
--tone-4: #46131a;
|
||||
--tone-5: #571821;
|
||||
--tone-6: #682f37;
|
||||
--tone-7: #895d64;
|
||||
--tone-8: #db8c90;
|
||||
--tone-9: #ddd1d3;
|
||||
|
||||
/* ----- طلایی (برند؛ همراستا با --primary) ----- */
|
||||
--gold-50: #fdf9f2;
|
||||
--gold-100: #f8eed9;
|
||||
--gold-200: #efd9b0;
|
||||
--gold-300: #e4c27e;
|
||||
--gold-400: #d4ad5a;
|
||||
--gold-500: #c99d4d;
|
||||
--gold-600: #a67e3c;
|
||||
--gold-700: #85632f;
|
||||
--gold-800: #5c4522;
|
||||
--gold-900: #3d2f18;
|
||||
--gold-950: #261c10;
|
||||
|
||||
/* ----- زمردی-فیروزه (تزئینات؛ کمی عمیقتر برای کنار بورگوندی) ----- */
|
||||
--emerald-50: #e6f4f1;
|
||||
--emerald-100: #c0e6df;
|
||||
--emerald-200: #8fd4c8;
|
||||
--emerald-300: #5cb9a9;
|
||||
--emerald-400: #3a9d8c;
|
||||
--emerald-500: #2a7f72;
|
||||
--emerald-600: #23695f;
|
||||
--emerald-700: #1d534c;
|
||||
--emerald-800: #163e39;
|
||||
--emerald-900: #0f2b28;
|
||||
--emerald-950: #081916;
|
||||
|
||||
--font-iran-sans: "IRANSansX", var(--font-geist-sans);
|
||||
--radius: 0.625rem;
|
||||
|
||||
/* ----- Semantic tokens based on exact provided palette ----- */
|
||||
--background: var(--tone-2);
|
||||
--foreground: var(--tone-9);
|
||||
|
||||
--card: var(--tone-4);
|
||||
--card-foreground: var(--tone-9);
|
||||
--popover: var(--tone-3);
|
||||
--popover-foreground: var(--tone-9);
|
||||
|
||||
--primary: var(--tone-8);
|
||||
--primary-foreground: var(--tone-1);
|
||||
|
||||
--secondary: var(--tone-6);
|
||||
--secondary-foreground: var(--tone-9);
|
||||
|
||||
--muted: var(--tone-3);
|
||||
--muted-foreground: var(--tone-7);
|
||||
|
||||
--accent: var(--tone-5);
|
||||
--accent-foreground: var(--tone-9);
|
||||
|
||||
--destructive: oklch(0.62 0.2 22);
|
||||
--destructive-foreground: oklch(0.99 0.01 25);
|
||||
|
||||
/* حاشیه: طلای ملایم روی پایهٔ تیرهٔ گرم */
|
||||
--border: color-mix(in srgb, var(--tone-8) 28%, var(--tone-2));
|
||||
--input: var(--tone-5);
|
||||
--ring: var(--tone-8);
|
||||
|
||||
/* متن/اسکرین روی تصویر و اورلی مودال */
|
||||
--overlay: var(--tone-1);
|
||||
--overlay-foreground: var(--tone-9);
|
||||
|
||||
--chart-1: var(--tone-8);
|
||||
--chart-2: var(--tone-6);
|
||||
--chart-3: var(--tone-7);
|
||||
--chart-4: var(--tone-5);
|
||||
--chart-5: var(--tone-4);
|
||||
|
||||
--sidebar: var(--tone-2);
|
||||
--sidebar-foreground: var(--tone-9);
|
||||
--sidebar-primary: var(--tone-8);
|
||||
--sidebar-primary-foreground: var(--tone-1);
|
||||
--sidebar-accent: var(--tone-5);
|
||||
--sidebar-accent-foreground: var(--tone-9);
|
||||
--sidebar-border: var(--tone-6);
|
||||
--sidebar-ring: var(--tone-8);
|
||||
|
||||
/* ----- دکمهٔ پریمیوم (طلایی + گرادیان آبی ثانویه) ----- */
|
||||
--btn-radius: 0.625rem;
|
||||
--btn-transition: 280ms ease-out;
|
||||
--btn-gold-from: #d8b056;
|
||||
--btn-gold-via: #c99d4d;
|
||||
--btn-gold-to: #b08a3f;
|
||||
--btn-gold-text: #fffaf0;
|
||||
--btn-gold-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 2px 8px rgba(0, 0, 0, 0.35),
|
||||
0 4px 16px rgba(224, 168, 48, 0.2);
|
||||
--btn-gold-shadow-hover: 0 1px 0 0 rgba(255, 255, 255, 0.3) inset, 0 6px 20px rgba(0, 0, 0, 0.3),
|
||||
0 0 10px rgba(242, 217, 122, 0.2);
|
||||
--btn-gold-shadow-active: 0 0 0 1px rgba(0, 0, 0, 0.15) inset, 0 2px 6px rgba(0, 0, 0, 0.4);
|
||||
--btn-gold-glow-hover: 0 0 10px rgba(242, 217, 122, 0.22);
|
||||
--btn-sapphire-from: #2a5ab8;
|
||||
--btn-sapphire-to: #153875;
|
||||
--btn-sapphire-shadow: 0 1px 0 0 rgba(255, 255, 255, 0.14) inset, 0 2px 8px rgba(0, 0, 0, 0.35),
|
||||
0 4px 16px rgba(30, 74, 180, 0.25);
|
||||
--btn-sapphire-shadow-hover: 0 1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 6px 20px rgba(0, 0, 0, 0.28),
|
||||
0 0 10px rgba(90, 140, 220, 0.22);
|
||||
--btn-sapphire-shadow-active: 0 0 0 1px rgba(0, 0, 0, 0.12) inset, 0 2px 6px rgba(0, 0, 0, 0.35);
|
||||
--btn-sapphire-glow-hover: 0 0 10px rgba(90, 140, 220, 0.28);
|
||||
}
|
||||
|
||||
@keyframes btn-shine {
|
||||
0% { transform: translateX(-100%) skewX(-12deg); }
|
||||
100% { transform: translateX(200%) skewX(-12deg); }
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
color-scheme: dark;
|
||||
}
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-family: "IRANSansX";
|
||||
letter-spacing: normal;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
/* Single subtle overlay for premium feel – no banding */
|
||||
background-image:
|
||||
radial-gradient(
|
||||
ellipse 95% 62% at 50% -10%,
|
||||
color-mix(in srgb, var(--tone-8) 18%, transparent) 0%,
|
||||
transparent 54%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 85% 48% at 50% 115%,
|
||||
color-mix(in srgb, var(--tone-7) 16%, transparent) 0%,
|
||||
transparent 56%
|
||||
);
|
||||
background-attachment: fixed;
|
||||
background-repeat: no-repeat;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
/* Standard typography – same sizes/weights across all pages */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: inherit;
|
||||
letter-spacing: normal;
|
||||
line-height: var(--line-height-heading);
|
||||
font-weight: var(--font-weight-heading);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
h1 {
|
||||
font-size: var(--font-size-heading-1);
|
||||
font-weight: 700;
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--font-size-heading-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--font-size-heading-3);
|
||||
font-weight: 600;
|
||||
}
|
||||
h4 {
|
||||
font-size: var(--font-size-heading-4);
|
||||
font-weight: 500;
|
||||
}
|
||||
h5 {
|
||||
font-size: var(--font-size-heading-5);
|
||||
font-weight: 500;
|
||||
}
|
||||
h6 {
|
||||
font-size: var(--font-size-body);
|
||||
font-weight: 500;
|
||||
}
|
||||
p {
|
||||
font-size: var(--font-size-body);
|
||||
font-weight: var(--font-weight-body);
|
||||
letter-spacing: normal;
|
||||
line-height: var(--line-height-body);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
a {
|
||||
transition: all 0.3s ease;
|
||||
color: var(--primary);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--primary);
|
||||
opacity: 0.9;
|
||||
}
|
||||
button {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
/* WordPress table block (figure.wp-block-table > table) */
|
||||
.wp-block-table {
|
||||
margin: 1.5rem 0;
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.wp-block-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9375rem;
|
||||
|
||||
}
|
||||
|
||||
.wp-block-table table.has-fixed-layout {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.wp-block-table th,
|
||||
.wp-block-table td {
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--border);
|
||||
text-align: right;
|
||||
min-width: 850px;
|
||||
}
|
||||
|
||||
.wp-block-table tbody tr:first-child th,
|
||||
.wp-block-table tbody tr:first-child td {
|
||||
background: var(--muted);
|
||||
font-weight: 600;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.wp-block-table tbody tr:not(:first-child):hover td {
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
.wp-block-table thead th {
|
||||
background: var(--muted);
|
||||
font-weight: 600;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.wp-block-button > .wp-element-button {
|
||||
@apply inline-flex bg-primary text-primary-foreground px-4 py-2 rounded-md hover:bg-primary/80 hover:text-primary-foreground/80 cursor-pointer no-underline;
|
||||
}
|
||||
|
||||
.wp-block-button {
|
||||
@apply inline-flex;
|
||||
}
|
||||
|
||||
.wp-block-button.is-style-outline > .wp-element-button {
|
||||
@apply bg-transparent text-foreground border border-2 border-primary hover:text-primary cursor-pointer no-underline;
|
||||
}
|
||||
|
||||
.cta-block {
|
||||
@apply flex flex-col gap-4 border rounded-md border-primary/50 p-4 items-center my-4 shadow-sm bg-primary/5 text-primary;
|
||||
}
|
||||
|
||||
.wp-block-image {
|
||||
@apply rounded-md overflow-hidden shadow-sm;
|
||||
}
|
||||
|
||||
/* ========== Animated Gradient Border ========== */
|
||||
|
||||
@property --bag-angle {
|
||||
syntax: '<angle>';
|
||||
initial-value: 0deg;
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
@keyframes bag-spin {
|
||||
to { --bag-angle: 360deg; }
|
||||
}
|
||||
|
||||
|
||||
.border-animated-gradient {
|
||||
--bag-width: 2px;
|
||||
--bag-speed: 5s;
|
||||
--bag-bg: var(--card);
|
||||
--bag-c1: var(--gold-300);
|
||||
--bag-c2: var(--emerald-500);
|
||||
--bag-c3: var(--gold-500);
|
||||
--bag-c4: var(--emerald-300);
|
||||
|
||||
border: var(--bag-width) solid transparent;
|
||||
background:
|
||||
var(--bag-bg) padding-box,
|
||||
conic-gradient(
|
||||
from var(--bag-angle),
|
||||
var(--bag-c1) 0%,
|
||||
var(--bag-c2) 25%,
|
||||
var(--bag-c3) 50%,
|
||||
var(--bag-c4) 75%,
|
||||
var(--bag-c1) 100%
|
||||
) border-box;
|
||||
animation: bag-spin var(--bag-speed) linear infinite;
|
||||
}
|
||||
|
||||
/* ── ورایانت رنگ: فقط طلایی ── */
|
||||
.border-animated-gradient.bag-gold {
|
||||
--bag-c1: var(--gold-200);
|
||||
--bag-c2: var(--gold-500);
|
||||
--bag-c3: var(--gold-300);
|
||||
--bag-c4: var(--gold-700);
|
||||
}
|
||||
|
||||
/* ── ورایانت رنگ: فقط زمردی ── */
|
||||
.border-animated-gradient.bag-emerald {
|
||||
--bag-c1: var(--emerald-200);
|
||||
--bag-c2: var(--emerald-500);
|
||||
--bag-c3: var(--emerald-300);
|
||||
--bag-c4: var(--emerald-700);
|
||||
}
|
||||
|
||||
/* ── ورایانت پسزمینه ── */
|
||||
.border-animated-gradient.bag-bg-base { --bag-bg: var(--background); }
|
||||
.border-animated-gradient.bag-bg-muted { --bag-bg: var(--muted); }
|
||||
|
||||
/* ── ورایانت سرعت ── */
|
||||
.border-animated-gradient.bag-slow { --bag-speed: 10s; }
|
||||
.border-animated-gradient.bag-fast { --bag-speed: 2s; }
|
||||
|
||||
/* ── ورایانت ضخامت ── */
|
||||
.border-animated-gradient.bag-thick { --bag-width: 3px; }
|
||||
.border-animated-gradient.bag-thin { --bag-width: 1px; }
|
||||
|
||||
/* ========== Unified section surfaces & blur separators ========== */
|
||||
.section-unified-block {
|
||||
border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
|
||||
background:
|
||||
linear-gradient(
|
||||
145deg,
|
||||
color-mix(in srgb, var(--tone-5) 62%, transparent) 0%,
|
||||
color-mix(in srgb, var(--tone-3) 74%, transparent) 100%
|
||||
);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
backdrop-filter: blur(14px);
|
||||
box-shadow: 0 10px 30px color-mix(in srgb, var(--tone-1) 55%, transparent);
|
||||
}
|
||||
|
||||
.frosted-panel {
|
||||
border: 1px solid color-mix(in srgb, var(--tone-8) 24%, transparent);
|
||||
background: color-mix(in srgb, var(--tone-4) 72%, transparent);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(115%);
|
||||
backdrop-filter: blur(16px) saturate(115%);
|
||||
}
|
||||
|
||||
.tone-gradient-line {
|
||||
height: 1px;
|
||||
width: 8rem;
|
||||
border-radius: 999px;
|
||||
margin-inline: auto;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--tone-3) 0%,
|
||||
var(--tone-6) 35%,
|
||||
var(--tone-8) 65%,
|
||||
var(--tone-9) 100%
|
||||
);
|
||||
filter: blur(0.2px);
|
||||
}
|
||||
|
||||
.section-blur-separator {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-blur-separator::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
width: min(78rem, 92vw);
|
||||
height: 1px;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
color-mix(in srgb, var(--tone-6) 65%, transparent) 20%,
|
||||
color-mix(in srgb, var(--tone-8) 78%, transparent) 50%,
|
||||
color-mix(in srgb, var(--tone-7) 65%, transparent) 80%,
|
||||
transparent 100%
|
||||
);
|
||||
filter: blur(1.6px);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
@keyframes meteor-title-fall {
|
||||
0% {
|
||||
transform: translate3d(64px, -120px, 0) rotate(300deg) scaleX(0.82);
|
||||
opacity: 0;
|
||||
}
|
||||
9% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
58% {
|
||||
opacity: 0.62;
|
||||
}
|
||||
82% {
|
||||
opacity: 0.28;
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-146px, var(--meteor-distance, 340px), 0) rotate(300deg) scaleX(0.96);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.meteor-title-streak {
|
||||
position: absolute;
|
||||
top: -18%;
|
||||
width: 56px;
|
||||
height: 1.2px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
color-mix(in srgb, var(--tone-9) 0%, transparent) 0%,
|
||||
color-mix(in srgb, var(--gold-200) 56%, transparent) 30%,
|
||||
color-mix(in srgb, var(--gold-400) 72%, transparent) 55%,
|
||||
color-mix(in srgb, var(--tone-9) 60%, transparent) 100%
|
||||
);
|
||||
box-shadow:
|
||||
0 0 10px color-mix(in srgb, var(--gold-400) 44%, transparent),
|
||||
0 0 20px color-mix(in srgb, var(--gold-200) 34%, transparent),
|
||||
0 0 28px color-mix(in srgb, var(--tone-9) 20%, transparent);
|
||||
transform-origin: right center;
|
||||
transform: translate3d(64px, -120px, 0) rotate(300deg) scaleX(0.82);
|
||||
animation-name: meteor-title-fall;
|
||||
animation-delay: -2s;
|
||||
animation-duration: 4.5s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
.meteor-title-streak-1 {
|
||||
left: 2%;
|
||||
--meteor-distance: 320px;
|
||||
animation-delay: -0.6s;
|
||||
animation-duration: 4.1s;
|
||||
}
|
||||
.meteor-title-streak-2 {
|
||||
left: 9%;
|
||||
--meteor-distance: 360px;
|
||||
animation-delay: -1.7s;
|
||||
animation-duration: 4.8s;
|
||||
}
|
||||
.meteor-title-streak-3 {
|
||||
left: 15%;
|
||||
--meteor-distance: 340px;
|
||||
animation-delay: -2.9s;
|
||||
animation-duration: 4.3s;
|
||||
}
|
||||
.meteor-title-streak-4 {
|
||||
left: 22%;
|
||||
--meteor-distance: 390px;
|
||||
animation-delay: -3.8s;
|
||||
animation-duration: 5.1s;
|
||||
}
|
||||
.meteor-title-streak-5 {
|
||||
left: 29%;
|
||||
--meteor-distance: 330px;
|
||||
animation-delay: -1.2s;
|
||||
animation-duration: 4.2s;
|
||||
}
|
||||
.meteor-title-streak-6 {
|
||||
left: 36%;
|
||||
--meteor-distance: 370px;
|
||||
animation-delay: -2.4s;
|
||||
animation-duration: 4.9s;
|
||||
}
|
||||
.meteor-title-streak-7 {
|
||||
left: 43%;
|
||||
--meteor-distance: 350px;
|
||||
animation-delay: -3.5s;
|
||||
animation-duration: 4.6s;
|
||||
}
|
||||
.meteor-title-streak-8 {
|
||||
left: 50%;
|
||||
--meteor-distance: 400px;
|
||||
animation-delay: -0.9s;
|
||||
animation-duration: 5.2s;
|
||||
}
|
||||
.meteor-title-streak-9 {
|
||||
left: 57%;
|
||||
--meteor-distance: 335px;
|
||||
animation-delay: -2.1s;
|
||||
animation-duration: 4.4s;
|
||||
}
|
||||
.meteor-title-streak-10 {
|
||||
left: 64%;
|
||||
--meteor-distance: 375px;
|
||||
animation-delay: -3.1s;
|
||||
animation-duration: 5s;
|
||||
}
|
||||
.meteor-title-streak-11 {
|
||||
left: 71%;
|
||||
--meteor-distance: 345px;
|
||||
animation-delay: -1.4s;
|
||||
animation-duration: 4.5s;
|
||||
}
|
||||
.meteor-title-streak-12 {
|
||||
left: 78%;
|
||||
--meteor-distance: 385px;
|
||||
animation-delay: -2.8s;
|
||||
animation-duration: 5.1s;
|
||||
}
|
||||
.meteor-title-streak-13 {
|
||||
left: 84%;
|
||||
--meteor-distance: 325px;
|
||||
animation-delay: -3.7s;
|
||||
animation-duration: 4.2s;
|
||||
}
|
||||
.meteor-title-streak-14 {
|
||||
left: 90%;
|
||||
--meteor-distance: 365px;
|
||||
animation-delay: -1.9s;
|
||||
animation-duration: 4.8s;
|
||||
}
|
||||
.meteor-title-streak-15 {
|
||||
left: 95%;
|
||||
--meteor-distance: 340px;
|
||||
animation-delay: -2.6s;
|
||||
animation-duration: 4.6s;
|
||||
}
|
||||
.meteor-title-streak-16 {
|
||||
left: 98%;
|
||||
--meteor-distance: 380px;
|
||||
animation-delay: -3.3s;
|
||||
animation-duration: 5.2s;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import { ConditionalLayout } from "@/components/layout/conditional-layout";
|
||||
import { ContentProvider } from "@/components/layout/content-provider";
|
||||
import { ThemeProvider } from "@/components/layout/theme-provider";
|
||||
import { TopProgressBar } from "@/components/layout/top-progress-bar";
|
||||
import { getContent } from "@/lib/content";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "مستر دکور",
|
||||
template: "%s | مستر دکور",
|
||||
},
|
||||
description: "مستر دکور — راهکارهای نرمافزاری برای کسبوکار",
|
||||
icons: {
|
||||
icon: "/assets/mainLogo_montzar.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const content = await getContent();
|
||||
return (
|
||||
<html lang="fa" dir="rtl" suppressHydrationWarning>
|
||||
<body className="antialiased">
|
||||
<ContentProvider initialContent={content}>
|
||||
<ThemeProvider>
|
||||
<TopProgressBar />
|
||||
<ConditionalLayout>{children}</ConditionalLayout>
|
||||
</ThemeProvider>
|
||||
</ContentProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { Hero174 } from "@/components/sections/hero/hero-174";
|
||||
import { ProductCategories2 } from "@/components/sections/feature/product-categories-2";
|
||||
import { Logos21 } from "@/components/sections/other/logos-21";
|
||||
import { FeatureWorkspaceTabs } from "@/components/sections/feature/feature-workspace-tabs";
|
||||
import { BusinessStatsSection } from "@/components/sections/other/business-stats-section";
|
||||
import { Cta6 } from "@/components/sections/cta/cta-6";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "صفحه اصلی",
|
||||
description:
|
||||
"مستر دکور؛ تامین کننده تخصصی استند و ملزومات دکور ویترین طلافروشی برای نمایش حرفه ای سرویس های طلا و جواهر.",
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="relative min-h-screen overflow-x-hidden">
|
||||
<Hero174 />
|
||||
<ProductCategories2 />
|
||||
<Logos21 />
|
||||
<FeatureWorkspaceTabs />
|
||||
<BusinessStatsSection />
|
||||
<Cta6 />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { ProductDetailView } from "@/components/sections/feature/product-detail-view";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getAllProductIds, getProductById, getProductDetail } from "@/lib/catalog-products";
|
||||
|
||||
type PageParams = { productId: string };
|
||||
|
||||
export function generateStaticParams(): PageParams[] {
|
||||
return getAllProductIds().map((productId) => ({ productId }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<PageParams>;
|
||||
}): Promise<Metadata> {
|
||||
const { productId } = await params;
|
||||
const product = getProductById(productId);
|
||||
if (!product) {
|
||||
return { title: "محصول" };
|
||||
}
|
||||
return {
|
||||
title: product.name,
|
||||
description: `${product.name} — ${product.categoryLabel} | مستر دکور`,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<PageParams>;
|
||||
}) {
|
||||
const { productId } = await params;
|
||||
const detail = getProductDetail(productId);
|
||||
if (!detail) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pb-16">
|
||||
<div className="border-b border-border bg-muted/40" dir="rtl">
|
||||
<div className="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-3 px-4 py-3 sm:px-6">
|
||||
<Button variant="ghost" className="text-body-sm text-gold-300 hover:bg-gold-950/35 hover:text-gold-200" asChild>
|
||||
<Link href={`/product?category=${detail.category}`}>بازگشت به فهرست {detail.categoryLabel}</Link>
|
||||
</Button>
|
||||
<Button variant="ghost" className="text-body-sm text-gold-300 hover:bg-gold-950/35 hover:text-gold-200" asChild>
|
||||
<Link href="/product">همه محصولات</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ProductDetailView product={detail} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { ProductList10Catalog } from '@/components/sections/feature/product-list10-catalog'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "محصولات",
|
||||
description:
|
||||
"محصولات مستر دکور شامل استند و ملزومات دکور ویترین طلافروشی در دسته های طلا، خردسال، جواهر و مانکن های تخصصی.",
|
||||
}
|
||||
|
||||
type ProductSearchParams =
|
||||
| { category?: string | string[] }
|
||||
| Promise<{ category?: string | string[] }>
|
||||
|
||||
export default async function ProductsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams?: ProductSearchParams
|
||||
}) {
|
||||
const resolvedSearchParams =
|
||||
searchParams && typeof (searchParams as Promise<{ category?: string | string[] }>).then === 'function'
|
||||
? await (searchParams as Promise<{ category?: string | string[] }>)
|
||||
: (searchParams as { category?: string | string[] } | undefined)
|
||||
|
||||
const rawCategory = resolvedSearchParams?.category
|
||||
const initialCategory = Array.isArray(rawCategory) ? rawCategory[0] : rawCategory
|
||||
|
||||
return (
|
||||
<main className="min-h-screen">
|
||||
<ProductList10Catalog initialCategory={initialCategory} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import type { Metadata } from "next";
|
||||
import { BadgeCheck, Clock3, ShieldCheck, Wrench } from "lucide-react";
|
||||
import { MeteorTitleBackground } from "@/components/utilities/meteor-title-background";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "گارانتی",
|
||||
description:
|
||||
"گارانتی مستر دکور برای استند و ملزومات ویترین طلافروشی، شامل پوشش ایرادهای ساخت، یراق آلات اصلی و پشتیبانی فنی است.",
|
||||
};
|
||||
|
||||
const warrantyCoverage = [
|
||||
"پوشش عیوب ساخت و مونتاژ بدنه استند در بازه گارانتی",
|
||||
"بازبینی و رفع اشکال عملکردی کشو، ریل و یراق آلات اصلی",
|
||||
"تنظیم مجدد قطعات متحرک که در استفاده عادی دچار اختلال شده اند",
|
||||
"ارائه راهکار اصلاحی یا تعویض قطعه در صورت تایید کارشناس فنی",
|
||||
"پشتیبانی تلفنی و ثبت درخواست برای هماهنگی بازدید حضوری",
|
||||
];
|
||||
|
||||
const warrantyHighlights = [
|
||||
{
|
||||
icon: ShieldCheck,
|
||||
title: "پوشش فنی معتبر",
|
||||
description: "تعهد کتبی نسبت به کیفیت اجرای سازه و قطعات اصلی",
|
||||
},
|
||||
{
|
||||
icon: Wrench,
|
||||
title: "رسیدگی تخصصی",
|
||||
description: "عیب یابی و رفع مشکل توسط تیم آشنا با استندهای طلافروشی",
|
||||
},
|
||||
{
|
||||
icon: Clock3,
|
||||
title: "پاسخگویی زمان دار",
|
||||
description: "پیگیری درخواست ها بر اساس اولویت و زمان بندی هماهنگ شده",
|
||||
},
|
||||
{
|
||||
icon: BadgeCheck,
|
||||
title: "کنترل کیفی نهایی",
|
||||
description: "تحویل خدمت پس از تست عملکرد و تایید کارایی",
|
||||
},
|
||||
];
|
||||
|
||||
export default function WarrantyPage() {
|
||||
return (
|
||||
<main className="min-h-screen pb-24 pt-16 md:pt-20">
|
||||
<section className="relative overflow-hidden bg-muted/60 py-20 md:py-24">
|
||||
<MeteorTitleBackground className="opacity-100" />
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6" dir="rtl">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<div className="relative z-10 rounded-2xl px-4 py-6 sm:px-8">
|
||||
<h1 className="text-heading-1 font-bold text-foreground">
|
||||
گارانتی خدمات و محصولات
|
||||
</h1>
|
||||
<p className="mt-5 text-body text-muted-foreground">
|
||||
گارانتی مستر دکور برای حفظ کیفیت واقعی استندها طراحی شده است تا با
|
||||
اطمینان بیشتری ویترین خود را مدیریت کنید و عملکرد تجهیزات در فروش
|
||||
روزانه پایدار بماند.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-14 md:py-16">
|
||||
<div
|
||||
className="mx-auto grid w-full max-w-7xl gap-10 px-4 sm:px-6 lg:grid-cols-3"
|
||||
dir="rtl"
|
||||
>
|
||||
<div className="space-y-8 lg:col-span-2">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-heading-4 font-semibold text-foreground text-xl">
|
||||
دامنه پوشش گارانتی
|
||||
</h2>
|
||||
<p className="text-body text-muted-foreground">
|
||||
در دوره گارانتی، مشکلاتی که ناشی از ایراد ساخت، مونتاژ یا عملکرد
|
||||
قطعات اصلی باشند توسط تیم فنی بررسی و رفع می شوند. هدف این است که
|
||||
استندها در شرایط عادی فروشگاه، کارکرد پایدار و ظاهری حرفه ای خود
|
||||
را حفظ کنند.
|
||||
</p>
|
||||
<p className="text-body text-muted-foreground">
|
||||
جزئیات نهایی هر پروژه شامل مدت زمان، شرایط نگهداری و موارد خارج از
|
||||
پوشش، همزمان با تحویل پروژه و مستندات فنی در اختیار شما قرار
|
||||
می گیرد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-heading-4 font-semibold text-foreground text-xl">
|
||||
روند رسیدگی به درخواست گارانتی
|
||||
</h2>
|
||||
<p className="text-body text-muted-foreground">
|
||||
پس از ثبت درخواست، ابتدا وضعیت به صورت تلفنی یا تصویری ارزیابی
|
||||
می شود. در صورت نیاز به بازدید حضوری، زمان مناسب با مجموعه شما
|
||||
هماهنگ می گردد تا بدون ایجاد اختلال در فعالیت فروشگاه، اقدامات
|
||||
اصلاحی انجام شود.
|
||||
</p>
|
||||
<p className="text-body text-muted-foreground">
|
||||
در پایان، عملکرد قطعه یا بخش تعمیر شده مجدد تست می شود و تحویل
|
||||
نهایی پس از تایید کیفیت اجرا انجام می گیرد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-heading-5 font-semibold text-foreground text-lg">
|
||||
موارد تحت پوشش
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{warrantyCoverage.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="rounded-xl border border-border bg-card px-4 py-3"
|
||||
>
|
||||
<p className="text-body text-foreground">{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside className="lg:col-span-1">
|
||||
<div className="rounded-2xl border border-border bg-muted/50 p-5 lg:sticky lg:top-24">
|
||||
<h3 className="text-heading-5 font-semibold text-foreground text-lg">
|
||||
مزایای گارانتی
|
||||
</h3>
|
||||
<div className="mt-5 space-y-4">
|
||||
{warrantyHighlights.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<div key={item.title} className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-9 w-9 items-center justify-center rounded-lg bg-card">
|
||||
<Icon className="h-4 w-4 text-primary" />
|
||||
</span>
|
||||
<div className="space-y-1">
|
||||
<p className="text-body-sm font-medium text-foreground">
|
||||
{item.title}
|
||||
</p>
|
||||
<p className="text-caption text-muted-foreground">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {
|
||||
"@tailark": "https://tailark.com/r/{name}.json",
|
||||
"@shadcnblocks": {
|
||||
"url": "https://shadcnblocks.com/r/{name}",
|
||||
"headers": {
|
||||
"Authorization": "Bearer sk_live_2kofDNyO7SAxm0tk6pDChCUwbOXkSo-w"
|
||||
}
|
||||
},
|
||||
"@aceternity": "https://ui.aceternity.com/registry/{name}.json"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
const MouseEnterContext = createContext<
|
||||
[boolean, React.Dispatch<React.SetStateAction<boolean>>] | undefined
|
||||
>(undefined);
|
||||
|
||||
export const CardContainer = ({
|
||||
children,
|
||||
className,
|
||||
containerClassName,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isMouseEntered, setIsMouseEntered] = useState(false);
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!containerRef.current) return;
|
||||
const { left, top, width, height } =
|
||||
containerRef.current.getBoundingClientRect();
|
||||
const x = (e.clientX - left - width / 2) / 25;
|
||||
const y = (e.clientY - top - height / 2) / 25;
|
||||
containerRef.current.style.transform = `rotateY(${x}deg) rotateX(${y}deg)`;
|
||||
};
|
||||
|
||||
const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
setIsMouseEntered(true);
|
||||
if (!containerRef.current) return;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (!containerRef.current) return;
|
||||
setIsMouseEntered(false);
|
||||
containerRef.current.style.transform = `rotateY(0deg) rotateX(0deg)`;
|
||||
};
|
||||
return (
|
||||
<MouseEnterContext.Provider value={[isMouseEntered, setIsMouseEntered]}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center",
|
||||
containerClassName
|
||||
)}
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
className={cn(
|
||||
"relative flex items-center justify-center transition-all duration-200 ease-linear",
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</MouseEnterContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const CardBody = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-96 w-96 [transform-style:preserve-3d] [&>*]:[transform-style:preserve-3d]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CardItem = ({
|
||||
as: Tag = "div",
|
||||
children,
|
||||
className,
|
||||
translateX = 0,
|
||||
translateY = 0,
|
||||
translateZ = 0,
|
||||
rotateX = 0,
|
||||
rotateY = 0,
|
||||
rotateZ = 0,
|
||||
...rest
|
||||
}: {
|
||||
as?: React.ElementType;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
translateX?: number | string;
|
||||
translateY?: number | string;
|
||||
translateZ?: number | string;
|
||||
rotateX?: number | string;
|
||||
rotateY?: number | string;
|
||||
rotateZ?: number | string;
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [isMouseEntered] = useMouseEnter();
|
||||
|
||||
useEffect(() => {
|
||||
handleAnimations();
|
||||
}, [isMouseEntered]);
|
||||
|
||||
const handleAnimations = () => {
|
||||
if (!ref.current) return;
|
||||
if (isMouseEntered) {
|
||||
ref.current.style.transform = `translateX(${translateX}px) translateY(${translateY}px) translateZ(${translateZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`;
|
||||
} else {
|
||||
ref.current.style.transform = `translateX(0px) translateY(0px) translateZ(0px) rotateX(0deg) rotateY(0deg) rotateZ(0deg)`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
ref={ref}
|
||||
className={cn("w-fit transition duration-200 ease-linear", className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
// Create a hook to use the context
|
||||
export const useMouseEnter = () => {
|
||||
const context = useContext(MouseEnterContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useMouseEnter must be used within a MouseEnterProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
FaCog,
|
||||
FaDollarSign,
|
||||
FaPalette,
|
||||
FaRocket,
|
||||
FaShieldAlt,
|
||||
FaUsers,
|
||||
} from "react-icons/fa";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Feature278Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature278 = ({ className }: Feature278Props) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: "Fast Performance",
|
||||
description: "Lightning quick loading times and smooth interactions",
|
||||
icon: FaRocket,
|
||||
color: "text-primary",
|
||||
bgColor: "bg-primary/10",
|
||||
},
|
||||
{
|
||||
title: "Easy Setup",
|
||||
description: "Simple configuration with minimal code required",
|
||||
icon: FaCog,
|
||||
color: "text-secondary",
|
||||
bgColor: "bg-secondary/10",
|
||||
},
|
||||
{
|
||||
title: "Beautiful Design",
|
||||
description: "Modern aesthetics with stunning visual appeal",
|
||||
icon: FaPalette,
|
||||
color: "text-primary",
|
||||
bgColor: "bg-primary/10",
|
||||
},
|
||||
{
|
||||
title: "Great Value",
|
||||
description: "Affordable pricing with premium features included",
|
||||
icon: FaDollarSign,
|
||||
color: "text-secondary",
|
||||
bgColor: "bg-secondary/10",
|
||||
},
|
||||
{
|
||||
title: "Secure",
|
||||
description: "Enterprise-grade security with data protection",
|
||||
icon: FaShieldAlt,
|
||||
color: "text-primary",
|
||||
bgColor: "bg-primary/10",
|
||||
},
|
||||
{
|
||||
title: "Team Support",
|
||||
description: "Dedicated support team available around the clock",
|
||||
icon: FaUsers,
|
||||
color: "text-secondary",
|
||||
bgColor: "bg-secondary/10",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn("min-h-screen overflow-hidden bg-muted py-32", className)}
|
||||
>
|
||||
<div className="container flex w-full flex-col items-center justify-center px-4">
|
||||
<div className="max-w-5xl">
|
||||
<h2 className="relative z-20 py-2 text-center font-sans text-5xl font-semibold tracking-tighter md:py-7">
|
||||
Features That speak for <br className="hidden lg:block" />{" "}
|
||||
<span className="opacity-50">Themselves</span>
|
||||
</h2>
|
||||
|
||||
<div className="relative grid w-full max-w-3xl grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
||||
{items.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="group relative block h-full w-full p-2"
|
||||
onMouseEnter={() => setHoveredIndex(idx)}
|
||||
onMouseLeave={() => setHoveredIndex(null)}
|
||||
>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{hoveredIndex === idx && (
|
||||
<motion.span
|
||||
className={cn(
|
||||
"absolute inset-0 block h-full w-full rounded-2xl",
|
||||
item.bgColor,
|
||||
)}
|
||||
layoutId="hoverBackground"
|
||||
key={idx}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<Card
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
icon={item.icon}
|
||||
color={item.color}
|
||||
bgColor={item.bgColor}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature278 };
|
||||
|
||||
const Card = ({
|
||||
className,
|
||||
title,
|
||||
description,
|
||||
bgColor,
|
||||
icon: Icon,
|
||||
color,
|
||||
}: {
|
||||
className?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
bgColor: string;
|
||||
color: string;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-20 flex h-full flex-col items-start justify-start gap-2 rounded-3xl bg-background p-5",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"mb-12 flex size-15 items-center justify-center rounded-2xl bg-background",
|
||||
color,
|
||||
bgColor,
|
||||
)}
|
||||
>
|
||||
<Icon className="size-5" />
|
||||
</div>
|
||||
<h1 className="text-xl font-medium tracking-tight">{title}</h1>
|
||||
<p className="text-sm text-muted-foreground/50">{description}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
|
||||
|
||||
export default function Footer5() {
|
||||
return (
|
||||
<footer className="m-1 rounded-3xl border border-border dark:border-primary/20 bg-card">
|
||||
<div className="mx-auto max-w-5xl space-y-16 px-5 py-16">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 border-b pb-8">
|
||||
<div
|
||||
aria-label="go home"
|
||||
className="flex items-center cursor-pointer">
|
||||
<Image
|
||||
src="/assets/mainLogo_montzar.png"
|
||||
alt="مستر دکور"
|
||||
width={150}
|
||||
height={50}
|
||||
className="h-10 w-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Link
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Threads"
|
||||
className="text-muted-foreground hover:text-primary dark:hover:text-primary block">
|
||||
<svg
|
||||
className="size-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
d="M19.25 8.505c-1.577-5.867-7-5.5-7-5.5s-7.5-.5-7.5 8.995s7.5 8.996 7.5 8.996s4.458.296 6.5-3.918c.667-1.858.5-5.573-6-5.573c0 0-3 0-3 2.5c0 .976 1 2 2.5 2s3.171-1.027 3.5-3c1-6-4.5-6.5-6-4"
|
||||
color="currentColor"></path>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Instagram"
|
||||
className="text-muted-foreground hover:text-primary dark:hover:text-primary block">
|
||||
<svg
|
||||
className="size-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7.8 2h8.4C19.4 2 22 4.6 22 7.8v8.4a5.8 5.8 0 0 1-5.8 5.8H7.8C4.6 22 2 19.4 2 16.2V7.8A5.8 5.8 0 0 1 7.8 2m-.2 2A3.6 3.6 0 0 0 4 7.6v8.8C4 18.39 5.61 20 7.6 20h8.8a3.6 3.6 0 0 0 3.6-3.6V7.6C20 5.61 18.39 4 16.4 4zm9.65 1.5a1.25 1.25 0 0 1 1.25 1.25A1.25 1.25 0 0 1 17.25 8A1.25 1.25 0 0 1 16 6.75a1.25 1.25 0 0 1 1.25-1.25M12 7a5 5 0 0 1 5 5a5 5 0 0 1-5 5a5 5 0 0 1-5-5a5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3a3 3 0 0 0 3 3a3 3 0 0 0 3-3a3 3 0 0 0-3-3"></path>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="TikTok"
|
||||
className="text-muted-foreground hover:text-primary dark:hover:text-primary block">
|
||||
<svg
|
||||
className="size-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M16.6 5.82s.51.5 0 0A4.28 4.28 0 0 1 15.54 3h-3.09v12.4a2.59 2.59 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6c0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64c0 3.33 2.76 5.7 5.69 5.7c3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48"></path>
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted dark:bg-muted/50 mt-16 flex items-center justify-between rounded-md p-4 px-6 py-3 border border-border dark:border-primary/10">
|
||||
<span>© مستر دکور 1404</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import FooterSection from '@/components/navigation/footer'
|
||||
import { HeroHeader } from '@/components/navigation/header'
|
||||
import { HeroHeaderTailark } from '@/components/navigation/header-main'
|
||||
import Footer5 from '@/components/navigation/footer'
|
||||
|
||||
export function ConditionalLayout({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const isLoginPage = pathname === '/login'
|
||||
const isHomePage = pathname === '/'
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isLoginPage && <HeroHeaderTailark />}
|
||||
{children}
|
||||
{!isLoginPage && <Footer5 />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client'
|
||||
|
||||
import React, { createContext, useContext, useMemo } from 'react'
|
||||
import type { SiteContent } from '@/lib/content'
|
||||
|
||||
const ContentContext = createContext<SiteContent | null>(null)
|
||||
|
||||
export function ContentProvider({
|
||||
initialContent,
|
||||
children,
|
||||
}: {
|
||||
initialContent: SiteContent
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const value = useMemo(() => initialContent, [initialContent])
|
||||
return (
|
||||
<ContentContext.Provider value={value}>
|
||||
{children}
|
||||
</ContentContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useContent(): SiteContent {
|
||||
const ctx = useContext(ContentContext)
|
||||
if (!ctx) {
|
||||
throw new Error('useContent must be used within ContentProvider')
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
/**
|
||||
* استفاده در کامپوننتهایی که خارج از ContentProvider هستند (مثلاً سرور).
|
||||
* در آن صورت از getContentSync() یا getContent() استفاده کنید.
|
||||
*/
|
||||
export function useContentOptional(): SiteContent | null {
|
||||
return useContext(ContentContext)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
'use client'
|
||||
|
||||
/** تم ثابت؛ بدون next-themes و بدون سوئیچ لایت/دارک */
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
return <>{children}</>
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
const TOP_BAR_HEIGHT = 3;
|
||||
const MAX_PERCENT = 90;
|
||||
const COMPLETE_PERCENT = 100;
|
||||
const TICK_MS = 50;
|
||||
const INCREMENT = 2;
|
||||
const COMPLETE_DURATION = 200;
|
||||
|
||||
export function TopProgressBar() {
|
||||
const pathname = usePathname();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [percent, setPercent] = useState(0);
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const completeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const prevPathnameRef = useRef(pathname);
|
||||
|
||||
const clearTimers = useCallback(() => {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
if (completeTimeoutRef.current) {
|
||||
clearTimeout(completeTimeoutRef.current);
|
||||
completeTimeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const startProgress = useCallback(() => {
|
||||
clearTimers();
|
||||
setVisible(true);
|
||||
setPercent(0);
|
||||
timerRef.current = setInterval(() => {
|
||||
setPercent((p) => {
|
||||
if (p >= MAX_PERCENT) {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
return MAX_PERCENT;
|
||||
}
|
||||
return Math.min(p + INCREMENT, MAX_PERCENT);
|
||||
});
|
||||
}, TICK_MS);
|
||||
}, [clearTimers]);
|
||||
|
||||
const completeProgress = useCallback(() => {
|
||||
clearTimers();
|
||||
setPercent(COMPLETE_PERCENT);
|
||||
completeTimeoutRef.current = setTimeout(() => {
|
||||
setVisible(false);
|
||||
setPercent(0);
|
||||
completeTimeoutRef.current = null;
|
||||
}, COMPLETE_DURATION);
|
||||
}, [clearTimers]);
|
||||
|
||||
// Start bar when user clicks an internal link
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const target = (e.target as Element).closest("a");
|
||||
if (!target?.href) return;
|
||||
try {
|
||||
const url = new URL(target.href);
|
||||
if (url.origin === window.location.origin && url.pathname !== pathname) {
|
||||
startProgress();
|
||||
}
|
||||
} catch {
|
||||
// ignore invalid URLs
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", handleClick, true);
|
||||
return () => document.removeEventListener("click", handleClick, true);
|
||||
}, [pathname, startProgress]);
|
||||
|
||||
// Complete bar when pathname changes (route loaded)
|
||||
useEffect(() => {
|
||||
if (prevPathnameRef.current !== pathname) {
|
||||
prevPathnameRef.current = pathname;
|
||||
if (visible) completeProgress();
|
||||
}
|
||||
}, [pathname, visible, completeProgress]);
|
||||
|
||||
useEffect(() => {
|
||||
return clearTimers;
|
||||
}, [clearTimers]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed left-0 right-0 z-[9999] overflow-hidden bg-transparent"
|
||||
style={{ height: TOP_BAR_HEIGHT }}
|
||||
aria-hidden
|
||||
>
|
||||
<div
|
||||
className="h-full bg-primary transition-[width] duration-150 ease-out"
|
||||
style={{ width: `${percent}%` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMotionValue, animate, motion } from 'motion/react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import useMeasure from 'react-use-measure';
|
||||
|
||||
export type InfiniteSliderProps = {
|
||||
children: React.ReactNode;
|
||||
gap?: number;
|
||||
speed?: number;
|
||||
speedOnHover?: number;
|
||||
direction?: 'horizontal' | 'vertical';
|
||||
reverse?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function InfiniteSlider({
|
||||
children,
|
||||
gap = 16,
|
||||
speed = 100,
|
||||
speedOnHover,
|
||||
direction = 'horizontal',
|
||||
reverse = false,
|
||||
className,
|
||||
}: InfiniteSliderProps) {
|
||||
const [currentSpeed, setCurrentSpeed] = useState(speed);
|
||||
const [ref, { width, height }] = useMeasure();
|
||||
const translation = useMotionValue(0);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let controls;
|
||||
const size = direction === 'horizontal' ? width : height;
|
||||
const contentSize = size + gap;
|
||||
const from = reverse ? -contentSize / 2 : 0;
|
||||
const to = reverse ? 0 : -contentSize / 2;
|
||||
|
||||
const distanceToTravel = Math.abs(to - from);
|
||||
const duration = distanceToTravel / currentSpeed;
|
||||
|
||||
if (isTransitioning) {
|
||||
const remainingDistance = Math.abs(translation.get() - to);
|
||||
const transitionDuration = remainingDistance / currentSpeed;
|
||||
|
||||
controls = animate(translation, [translation.get(), to], {
|
||||
ease: 'linear',
|
||||
duration: transitionDuration,
|
||||
onComplete: () => {
|
||||
setIsTransitioning(false);
|
||||
setKey((prevKey) => prevKey + 1);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
controls = animate(translation, [from, to], {
|
||||
ease: 'linear',
|
||||
duration: duration,
|
||||
repeat: Infinity,
|
||||
repeatType: 'loop',
|
||||
repeatDelay: 0,
|
||||
onRepeat: () => {
|
||||
translation.set(from);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return controls?.stop;
|
||||
}, [
|
||||
key,
|
||||
translation,
|
||||
currentSpeed,
|
||||
width,
|
||||
height,
|
||||
gap,
|
||||
isTransitioning,
|
||||
direction,
|
||||
reverse,
|
||||
]);
|
||||
|
||||
const hoverProps = speedOnHover
|
||||
? {
|
||||
onHoverStart: () => {
|
||||
setIsTransitioning(true);
|
||||
setCurrentSpeed(speedOnHover);
|
||||
},
|
||||
onHoverEnd: () => {
|
||||
setIsTransitioning(true);
|
||||
setCurrentSpeed(speed);
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
<div className={cn('overflow-hidden', className)}>
|
||||
<motion.div
|
||||
className='flex w-max'
|
||||
style={{
|
||||
...(direction === 'horizontal'
|
||||
? { x: translation }
|
||||
: { y: translation }),
|
||||
gap: `${gap}px`,
|
||||
flexDirection: direction === 'horizontal' ? 'row' : 'column',
|
||||
}}
|
||||
ref={ref}
|
||||
{...hoverProps}
|
||||
>
|
||||
{children}
|
||||
{children}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { HTMLMotionProps, motion } from 'motion/react';
|
||||
|
||||
export const GRADIENT_ANGLES = {
|
||||
top: 0,
|
||||
right: 90,
|
||||
bottom: 180,
|
||||
left: 270,
|
||||
};
|
||||
|
||||
export type ProgressiveBlurProps = {
|
||||
direction?: keyof typeof GRADIENT_ANGLES;
|
||||
blurLayers?: number;
|
||||
className?: string;
|
||||
blurIntensity?: number;
|
||||
} & HTMLMotionProps<'div'>;
|
||||
|
||||
export function ProgressiveBlur({
|
||||
direction = 'bottom',
|
||||
blurLayers = 8,
|
||||
className,
|
||||
blurIntensity = 0.25,
|
||||
...props
|
||||
}: ProgressiveBlurProps) {
|
||||
const layers = Math.max(blurLayers, 2);
|
||||
const segmentSize = 1 / (blurLayers + 1);
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
{Array.from({ length: layers }).map((_, index) => {
|
||||
const angle = GRADIENT_ANGLES[direction];
|
||||
const gradientStops = [
|
||||
index * segmentSize,
|
||||
(index + 1) * segmentSize,
|
||||
(index + 2) * segmentSize,
|
||||
(index + 3) * segmentSize,
|
||||
].map(
|
||||
(pos, posIndex) =>
|
||||
`rgba(255, 255, 255, ${posIndex === 1 || posIndex === 2 ? 1 : 0}) ${pos * 100}%`
|
||||
);
|
||||
|
||||
const gradient = `linear-gradient(${angle}deg, ${gradientStops.join(
|
||||
', '
|
||||
)})`;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={index}
|
||||
className='pointer-events-none absolute inset-0 rounded-[inherit]'
|
||||
style={{
|
||||
maskImage: gradient,
|
||||
WebkitMaskImage: gradient,
|
||||
backdropFilter: `blur(${index * blurIntensity}px)`,
|
||||
WebkitBackdropFilter: `blur(${index * blurIntensity}px)`,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { Instagram, MessageCircle, Linkedin } from 'lucide-react'
|
||||
import { useContent } from '@/components/layout/content-provider'
|
||||
|
||||
export default function Footer5() {
|
||||
const content = useContent()
|
||||
const { footer } = content.navigation
|
||||
const logoAlt = footer.logoAlt
|
||||
const logoUrl = content.images.logo
|
||||
|
||||
return (
|
||||
<footer className="m-1 rounded-3xl border border-border dark:border-primary/20 bg-card">
|
||||
<div className="mx-auto max-w-5xl px-5 py-8">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 border-b pb-6">
|
||||
<Link
|
||||
href="/"
|
||||
aria-label="go home"
|
||||
className="flex items-center cursor-pointer">
|
||||
<Image
|
||||
src={logoUrl}
|
||||
alt={logoAlt}
|
||||
width={150}
|
||||
height={50}
|
||||
className="mx-auto h-10 w-auto object-contain object-center md:h-12"
|
||||
/>
|
||||
</Link>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-muted-foreground" dir="rtl">{footer.socialLabel}</span>
|
||||
<Link
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Instagram"
|
||||
className="text-muted-foreground transition-colors hover:text-primary dark:hover:text-primary">
|
||||
<Instagram className="size-5" />
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Telegram"
|
||||
className="text-muted-foreground transition-colors hover:text-primary dark:hover:text-primary">
|
||||
<MessageCircle className="size-5" />
|
||||
</Link>
|
||||
<Link
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="LinkedIn"
|
||||
className="text-muted-foreground transition-colors hover:text-primary dark:hover:text-primary">
|
||||
<Linkedin className="size-5" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-wrap items-center justify-center gap-6">
|
||||
{footer.links.map((link, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
href={link.href}
|
||||
className="text-sm text-muted-foreground hover:text-primary transition-colors duration-150">
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col items-center justify-center gap-2 text-center sm:flex-row sm:gap-4">
|
||||
{footer.phoneNumber && footer.phoneHref && (
|
||||
<a
|
||||
href={footer.phoneHref}
|
||||
className="text-sm text-muted-foreground hover:text-primary transition-colors duration-150"
|
||||
dir="ltr">
|
||||
{footer.phoneLabel && <span className="ms-1">{footer.phoneLabel}</span>}
|
||||
{footer.phoneNumber}
|
||||
</a>
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{footer.copyright}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { BadgeCheck, BriefcaseBusiness, Gauge, Info, Menu, PhoneCall, ShieldCheck, Sparkles, Store, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useScroll } from 'motion/react'
|
||||
import { useContent } from '@/components/layout/content-provider'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
} from '@/components/ui/navigation-menu'
|
||||
|
||||
function isRouteActive(pathname: string, href: string): boolean {
|
||||
if (href === '/') return pathname === '/'
|
||||
return pathname === href || pathname.startsWith(`${href}/`)
|
||||
}
|
||||
|
||||
const HOME_HREF = '/'
|
||||
|
||||
export const HeroHeaderTailark = () => {
|
||||
const [menuState, setMenuState] = React.useState(false)
|
||||
const [scrolled, setScrolled] = React.useState(false)
|
||||
const pathname = usePathname() ?? ''
|
||||
const content = useContent()
|
||||
const { header } = content.navigation
|
||||
const { images } = content
|
||||
const menuItems = header.menuItems
|
||||
const productItems = header.productItems
|
||||
const wordmark = header.wordmark ?? content.meta.siteName
|
||||
const homeItem = menuItems.find((i) => i.href === HOME_HREF)
|
||||
const trailItems = menuItems.filter((i) => i.href !== HOME_HREF)
|
||||
const productMenuItems = productItems.map((item, index) => ({
|
||||
...item,
|
||||
icon: [Sparkles, Gauge, ShieldCheck][index] ?? BadgeCheck,
|
||||
}))
|
||||
const solutionMenuItems = [
|
||||
{
|
||||
name: 'درباره ما',
|
||||
href: '/about',
|
||||
description: 'آشنایی با چشم انداز، تجربه و مسیر توسعه مستر دکور',
|
||||
icon: Info,
|
||||
},
|
||||
{
|
||||
name: 'تماس با ما',
|
||||
href: '/contact',
|
||||
description: 'ارتباط مستقیم با تیم برای دریافت پاسخ سریع',
|
||||
icon: PhoneCall,
|
||||
},
|
||||
{
|
||||
name: 'مشاوره و قیمت',
|
||||
href: '/consultation',
|
||||
description: 'بررسی دقیق نیاز شما و پیشنهاد مسیر اجرایی مناسب',
|
||||
icon: BriefcaseBusiness,
|
||||
},
|
||||
]
|
||||
|
||||
const { scrollYProgress } = useScroll()
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = scrollYProgress.on('change', (latest) => {
|
||||
setScrolled(latest > 0.05)
|
||||
})
|
||||
return () => unsubscribe()
|
||||
}, [scrollYProgress])
|
||||
|
||||
return (
|
||||
<header className="pt-[env(safe-area-inset-top)]">
|
||||
<nav
|
||||
data-state={menuState && 'active'}
|
||||
className={cn(
|
||||
'fixed z-[100] w-full border-b transition-[background-color,box-shadow] duration-200',
|
||||
scrolled
|
||||
? 'border-border/80 bg-background/90 shadow-sm shadow-black/5 backdrop-blur-xl'
|
||||
: 'border-transparent bg-background/70 backdrop-blur-md'
|
||||
)}>
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6">
|
||||
<div className="relative flex min-h-[3.5rem] flex-wrap items-center justify-between gap-3 py-2.5 lg:min-h-0 lg:flex-nowrap lg:py-3">
|
||||
<div className="flex w-full min-w-0 flex-1 items-center gap-2 lg:w-auto lg:flex-initial">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMenuState(!menuState)}
|
||||
aria-label={menuState ? 'بستن منو' : 'باز کردن منو'}
|
||||
className="relative z-20 -m-1.5 flex size-10 shrink-0 items-center justify-center rounded-full border border-transparent hover:bg-accent/60 lg:hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
{menuState ? (
|
||||
<motion.span
|
||||
key="close"
|
||||
initial={{ rotate: -90, opacity: 0 }}
|
||||
animate={{ rotate: 0, opacity: 1 }}
|
||||
exit={{ rotate: 90, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="inline-flex">
|
||||
<X className="size-5" />
|
||||
</motion.span>
|
||||
) : (
|
||||
<motion.span
|
||||
key="menu"
|
||||
initial={{ rotate: 90, opacity: 0 }}
|
||||
animate={{ rotate: 0, opacity: 1 }}
|
||||
exit={{ rotate: -90, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="inline-flex">
|
||||
<Menu className="size-5" />
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href="/"
|
||||
className="flex min-w-0 shrink items-center gap-2.5 rounded-xl py-1 pe-1 ps-0 transition-opacity hover:opacity-90"
|
||||
aria-label={wordmark}>
|
||||
<Image
|
||||
src={images.logo}
|
||||
alt={header.logoAlt}
|
||||
width={180}
|
||||
height={60}
|
||||
className="mx-auto h-9 w-auto object-contain object-center sm:h-10"
|
||||
priority
|
||||
/>
|
||||
<span className="hidden truncate text-start text-base font-semibold tracking-tight text-foreground sm:inline md:text-lg">
|
||||
{wordmark}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="order-last hidden w-full justify-center lg:order-none lg:flex lg:w-auto lg:flex-1 lg:justify-center">
|
||||
<NavigationMenu
|
||||
dir="rtl"
|
||||
className="mx-auto"
|
||||
>
|
||||
<NavigationMenuList className="gap-3">
|
||||
{homeItem ? (
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={cn(
|
||||
'inline-flex h-8 w-max items-center justify-center rounded-md px-4 py-1 text-body-sm font-medium transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1',
|
||||
isRouteActive(pathname, homeItem.href)
|
||||
? 'bg-foreground/5 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-foreground/5 hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Link href={homeItem.href}>{homeItem.name}</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
) : null}
|
||||
|
||||
<NavigationMenuItem >
|
||||
<NavigationMenuTrigger
|
||||
className={cn(
|
||||
'inline-flex h-8 w-max items-center justify-center rounded-md px-4 py-1 text-body-sm font-medium transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1',
|
||||
pathname.startsWith('/product')
|
||||
? 'bg-foreground/5 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-foreground/5 hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{header.productsLabel}
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent className="p-3" dir="rtl">
|
||||
<div className="grid gap-3 lg:grid-cols-3 w-[75rem] max-w-[calc(100vw-2rem)]">
|
||||
<div className="space-y-2 lg:col-span-2">
|
||||
<div className="px-2">
|
||||
<p className="text-caption font-medium text-muted-foreground">محصولات</p>
|
||||
</div>
|
||||
<ul className="grid gap-1 sm:grid-cols-2">
|
||||
{productMenuItems.map((product) => {
|
||||
const Icon = product.icon
|
||||
return (
|
||||
<li key={`${product.name}-${product.href}`}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={product.href}
|
||||
className="flex items-start gap-3 rounded-xl border border-transparent px-3 py-3 text-start transition-colors hover:border-border/80 hover:bg-accent/60"
|
||||
>
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted">
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
<span className="space-y-0.5">
|
||||
<span className="block text-body-sm font-medium text-foreground">{product.name}</span>
|
||||
<span className="block text-caption leading-snug text-muted-foreground">{product.description}</span>
|
||||
</span>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/product"
|
||||
className="group relative hidden overflow-hidden rounded-2xl border border-border bg-card lg:block"
|
||||
>
|
||||
<Image
|
||||
src="/assets/main-pic/product/sampleghaleb.png"
|
||||
alt="نمای کلی محصولات"
|
||||
width={600}
|
||||
height={760}
|
||||
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]"
|
||||
/>
|
||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-background/90 to-transparent p-4">
|
||||
<p className="text-body-sm font-medium text-foreground">اکوسیستم یکپارچه محصولات</p>
|
||||
<p className="mt-1 text-caption text-muted-foreground">برای مشاهده جزئیات کامل همه محصولات کلیک کنید</p>
|
||||
</div>
|
||||
<span className="absolute start-3 top-3 inline-flex items-center rounded-full bg-background/90 px-2 py-1 text-caption font-medium text-foreground">
|
||||
<Store className="me-1 h-3.5 w-3.5" />
|
||||
{productMenuItems.length} محصول
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={cn(
|
||||
'inline-flex h-8 w-max items-center justify-center rounded-md px-4 py-1 text-body-sm font-medium transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1',
|
||||
isRouteActive(pathname, '/after-sales')
|
||||
? 'bg-foreground/5 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-foreground/5 hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Link href="/after-sales">خدمات پس از فروش</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={cn(
|
||||
'inline-flex h-8 w-max items-center justify-center rounded-md px-4 py-1 text-body-sm font-medium transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1',
|
||||
isRouteActive(pathname, '/warranty')
|
||||
? 'bg-foreground/5 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-foreground/5 hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Link href="/warranty">گارانتی</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={cn(
|
||||
'inline-flex h-8 w-max items-center justify-center rounded-md px-4 py-1 text-body-sm font-medium transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1',
|
||||
isRouteActive(pathname, '/faq')
|
||||
? 'bg-foreground/5 text-foreground'
|
||||
: 'text-muted-foreground hover:bg-foreground/5 hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Link href="/faq">سوالات متداول</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
|
||||
<NavigationMenuItem >
|
||||
<NavigationMenuTrigger className="inline-flex h-8 w-max items-center justify-center rounded-md px-4 py-1 text-body-sm font-medium text-muted-foreground transition-[color,box-shadow] hover:bg-foreground/5 hover:text-foreground focus-visible:ring-[3px] focus-visible:outline-1">
|
||||
پیوند ها
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent className="p-3" dir="rtl">
|
||||
<div className="grid gap-3 lg:grid-cols-3 w-[75rem] max-w-[calc(100vw-2rem)]">
|
||||
<div className="space-y-2 lg:col-span-2">
|
||||
<div className="px-2">
|
||||
<p className="text-caption font-medium text-muted-foreground">منوی اصلی</p>
|
||||
</div>
|
||||
<ul className="grid gap-1 sm:grid-cols-2">
|
||||
{solutionMenuItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = isRouteActive(pathname, item.href)
|
||||
return (
|
||||
<li key={item.href}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'flex items-start gap-3 rounded-xl border border-transparent px-3 py-3 text-start transition-colors hover:border-border/80 hover:bg-accent/60',
|
||||
active && 'border-border/80 bg-accent/60'
|
||||
)}
|
||||
>
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-muted">
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
<span className="space-y-0.5">
|
||||
<span className="block text-body-sm font-medium text-foreground">{item.name}</span>
|
||||
<span className="block text-caption leading-snug text-muted-foreground">{item.description}</span>
|
||||
</span>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/consultation"
|
||||
className="group relative hidden overflow-hidden rounded-2xl border border-border bg-card lg:block"
|
||||
>
|
||||
<Image
|
||||
src="/assets/main-pic/feature/feature1.webp"
|
||||
alt="راهکارهای مشاوره و پیاده سازی"
|
||||
width={600}
|
||||
height={760}
|
||||
className="h-full max-h-[22rem] w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]"
|
||||
/>
|
||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-background/90 to-transparent p-4">
|
||||
<p className="text-body-sm font-medium text-foreground">همراهی تا استقرار نهایی</p>
|
||||
<p className="mt-1 text-caption text-muted-foreground">از مشاوره اولیه تا اجرای عملیاتی کنار شما هستیم</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-2 sm:gap-3">
|
||||
<Button asChild size="sm" variant="primary" className="hidden sm:inline-flex">
|
||||
<Link href="/consultation">{header.demoButton}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:hidden">
|
||||
<AnimatePresence>
|
||||
{menuState && (
|
||||
<>
|
||||
<motion.div
|
||||
aria-hidden
|
||||
onClick={() => setMenuState(false)}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed inset-0 z-[99] bg-background/50 backdrop-blur-[2px]"
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -8 }}
|
||||
transition={{ duration: 0.22, ease: [0.32, 0.72, 0, 1] }}
|
||||
className="relative z-[100] -mx-1 mb-3 overflow-hidden rounded-2xl border border-border/60 bg-card/95 shadow-lg shadow-black/10 backdrop-blur-xl">
|
||||
<nav className="max-h-[min(70vh,28rem)] overflow-y-auto overscroll-contain px-3 py-3" aria-label="منوی موبایل">
|
||||
{homeItem ? (
|
||||
<>
|
||||
<p className="text-caption mb-2 px-2 font-medium uppercase tracking-wider text-muted-foreground">
|
||||
منو
|
||||
</p>
|
||||
<ul className="space-y-1">
|
||||
<li>
|
||||
<Link
|
||||
href={homeItem.href}
|
||||
onClick={() => setMenuState(false)}
|
||||
className={cn(
|
||||
'flex items-center rounded-xl px-3 py-2.5 text-body-sm transition-colors',
|
||||
isRouteActive(pathname, homeItem.href)
|
||||
? 'bg-primary/12 font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
||||
)}>
|
||||
{homeItem.name}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="my-3 border-t border-border/60" />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<p className="text-caption mb-2 px-2 font-medium uppercase tracking-wider text-muted-foreground">
|
||||
{header.productsLabel}
|
||||
</p>
|
||||
<ul className="grid gap-2 sm:grid-cols-2">
|
||||
{productMenuItems.map((product) => {
|
||||
const Icon = product.icon
|
||||
return (
|
||||
<li key={`${product.name}-${product.href}`}>
|
||||
<Link
|
||||
href={product.href}
|
||||
onClick={() => setMenuState(false)}
|
||||
className="flex items-start gap-3 rounded-xl border border-border/60 bg-card/40 px-3 py-3 transition-colors hover:bg-muted/40">
|
||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted">
|
||||
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||
</span>
|
||||
<span className="space-y-0.5">
|
||||
<span className="block text-body-sm font-medium text-foreground">{product.name}</span>
|
||||
<span className="block text-caption leading-snug text-muted-foreground">{product.description}</span>
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)})}
|
||||
</ul>
|
||||
|
||||
{trailItems.length > 0 ? (
|
||||
<>
|
||||
<div className="my-3 border-t border-border/60" />
|
||||
<ul className="space-y-1">
|
||||
{trailItems.map((item, index) => {
|
||||
const active = isRouteActive(pathname, item.href)
|
||||
return (
|
||||
<li key={index}>
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={() => setMenuState(false)}
|
||||
className={cn(
|
||||
'flex items-center rounded-xl px-3 py-2.5 text-body-sm transition-colors',
|
||||
active
|
||||
? 'bg-primary/12 font-medium text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
||||
)}>
|
||||
{item.name}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<div className="mt-4 flex flex-col gap-2 border-t border-border/60 pt-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Button asChild variant="primary" className="w-full sm:w-auto">
|
||||
<Link href="/consultation" onClick={() => setMenuState(false)}>
|
||||
{header.demoButton}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
'use client'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Logo } from '@/components/navigation/logo'
|
||||
import { Menu, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const menuItems = [
|
||||
{ name: 'صفحه اصلی', href: '/' },
|
||||
{ name: 'محصولات', href: '/product' },
|
||||
{ name: 'تماس با ما', href: '/contact' },
|
||||
]
|
||||
|
||||
|
||||
|
||||
function isLinkActive(pathname: string, href: string): boolean {
|
||||
if (href === '/') return pathname === '/'
|
||||
return pathname === href || pathname.startsWith(href + '/')
|
||||
}
|
||||
|
||||
export const HeroHeader = () => {
|
||||
const pathname = usePathname()
|
||||
const [menuState, setMenuState] = React.useState(false)
|
||||
const [isScrolled, setIsScrolled] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 50)
|
||||
}
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
return (
|
||||
<header>
|
||||
<nav
|
||||
data-state={menuState && 'active'}
|
||||
className="fixed z-50 w-full px-2">
|
||||
<div className={cn('mx-auto mt-2 max-w-6xl px-2 transition-all duration-300 lg:px-2 bg-background/30 backdrop-blur-md rounded-md border border-border/10', isScrolled && 'bg-background/20 max-w-4xl shadow-lg lg:px-5')}>
|
||||
<div className="relative flex flex-wrap items-center justify-between gap-2 lg:gap-0">
|
||||
<div className="flex w-full justify-start gap-2 lg:w-auto">
|
||||
<button
|
||||
onClick={() => setMenuState(!menuState)}
|
||||
aria-label={menuState == true ? 'بستن منو' : 'باز کردن منو'}
|
||||
className="relative z-20 -m-2.5 -mr-4 block cursor-pointer p-2.5 lg:hidden">
|
||||
<Menu className="in-data-[state=active]:rotate-180 in-data-[state=active]:scale-0 in-data-[state=active]:opacity-0 m-auto size-4 duration-200" />
|
||||
<X className="in-data-[state=active]:rotate-0 in-data-[state=active]:scale-100 in-data-[state=active]:opacity-100 absolute inset-0 m-auto size-4 -rotate-180 scale-0 opacity-0 duration-200" />
|
||||
</button>
|
||||
<Logo />
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 m-auto hidden size-fit lg:flex items-center">
|
||||
<ul className="flex gap-2 text-sm">
|
||||
{menuItems.map((item, index) => {
|
||||
const active = isLinkActive(pathname ?? '', item.href)
|
||||
return (
|
||||
<li key={index}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'text-muted-foreground hover:text-foreground hover:bg-accent/20 transition-all duration-300 relative group p-2 px-4 rounded-md',
|
||||
active && 'text-foreground bg-accent/40'
|
||||
)}>
|
||||
<span className="relative z-10">{item.name}</span>
|
||||
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="in-data-[state=active]:block lg:in-data-[state=active]:flex mb-3 hidden w-full flex-wrap items-center justify-end space-y-8 md:flex-nowrap lg:m-0 lg:flex lg:w-fit lg:gap-6 lg:space-y-0 lg:border-transparent lg:bg-transparent lg:p-0 lg:shadow-none dark:shadow-none dark:lg:bg-transparent">
|
||||
<div className="lg:hidden w-full">
|
||||
|
||||
<ul className="text-base flex flex-col items-stretch">
|
||||
{menuItems.map((item, index) => {
|
||||
const active = isLinkActive(pathname ?? '', item.href)
|
||||
return (
|
||||
<li key={index}>
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={() => setMenuState(false)}
|
||||
className={cn(
|
||||
'text-muted-foreground block w-full hover:text-foreground hover:bg-accent/20 transition-all duration-300 relative group p-2 px-4 rounded-md',
|
||||
active && 'text-foreground bg-accent/40'
|
||||
)}>
|
||||
<span className="relative z-10">{item.name}</span>
|
||||
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className={`flex justify-end max-md:absolute top-0.5 left-8 in-data-[state=active]:block`} />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export const LogoTailark = ({ className, uniColor }: { className?: string; uniColor?: boolean }) => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 78 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn('text-foreground h-5 w-auto', className)}>
|
||||
<path
|
||||
d="M3 0H5V18H3V0ZM13 0H15V18H13V0ZM18 3V5H0V3H18ZM0 15V13H18V15H0Z"
|
||||
fill={uniColor ? 'currentColor' : 'url(#logo-gradient)'}
|
||||
/>
|
||||
<path
|
||||
d="M27.06 7.054V12.239C27.06 12.5903 27.1393 12.8453 27.298 13.004C27.468 13.1513 27.7513 13.225 28.148 13.225H29.338V14.84H27.808C26.9353 14.84 26.2667 14.636 25.802 14.228C25.3373 13.82 25.105 13.157 25.105 12.239V7.054H24V5.473H25.105V3.144H27.06V5.473H29.338V7.054H27.06ZM30.4782 10.114C30.4782 9.17333 30.6709 8.34033 31.0562 7.615C31.4529 6.88967 31.9855 6.32867 32.6542 5.932C33.3342 5.524 34.0822 5.32 34.8982 5.32C35.6349 5.32 36.2752 5.46733 36.8192 5.762C37.3745 6.04533 37.8165 6.40233 38.1452 6.833V5.473H40.1002V14.84H38.1452V13.446C37.8165 13.888 37.3689 14.2563 36.8022 14.551C36.2355 14.8457 35.5895 14.993 34.8642 14.993C34.0595 14.993 33.3229 14.789 32.6542 14.381C31.9855 13.9617 31.4529 13.3837 31.0562 12.647C30.6709 11.899 30.4782 11.0547 30.4782 10.114ZM38.1452 10.148C38.1452 9.502 38.0092 8.941 37.7372 8.465C37.4765 7.989 37.1309 7.62633 36.7002 7.377C36.2695 7.12767 35.8049 7.003 35.3062 7.003C34.8075 7.003 34.3429 7.12767 33.9122 7.377C33.4815 7.615 33.1302 7.972 32.8582 8.448C32.5975 8.91267 32.4672 9.468 32.4672 10.114C32.4672 10.76 32.5975 11.3267 32.8582 11.814C33.1302 12.3013 33.4815 12.6753 33.9122 12.936C34.3542 13.1853 34.8189 13.31 35.3062 13.31C35.8049 13.31 36.2695 13.1853 36.7002 12.936C37.1309 12.6867 37.4765 12.324 37.7372 11.848C38.0092 11.3607 38.1452 10.794 38.1452 10.148ZM43.6317 4.232C43.2803 4.232 42.9857 4.113 42.7477 3.875C42.5097 3.637 42.3907 3.34233 42.3907 2.991C42.3907 2.63967 42.5097 2.345 42.7477 2.107C42.9857 1.869 43.2803 1.75 43.6317 1.75C43.9717 1.75 44.2607 1.869 44.4987 2.107C44.7367 2.345 44.8557 2.63967 44.8557 2.991C44.8557 3.34233 44.7367 3.637 44.4987 3.875C44.2607 4.113 43.9717 4.232 43.6317 4.232ZM44.5837 5.473V14.84H42.6457V5.473H44.5837ZM49.0661 2.26V14.84H47.1281V2.26H49.0661ZM50.9645 10.114C50.9645 9.17333 51.1572 8.34033 51.5425 7.615C51.9392 6.88967 52.4719 6.32867 53.1405 5.932C53.8205 5.524 54.5685 5.32 55.3845 5.32C56.1212 5.32 56.7615 5.46733 57.3055 5.762C57.8609 6.04533 58.3029 6.40233 58.6315 6.833V5.473H60.5865V14.84H58.6315V13.446C58.3029 13.888 57.8552 14.2563 57.2885 14.551C56.7219 14.8457 56.0759 14.993 55.3505 14.993C54.5459 14.993 53.8092 14.789 53.1405 14.381C52.4719 13.9617 51.9392 13.3837 51.5425 12.647C51.1572 11.899 50.9645 11.0547 50.9645 10.114ZM58.6315 10.148C58.6315 9.502 58.4955 8.941 58.2235 8.465C57.9629 7.989 57.6172 7.62633 57.1865 7.377C56.7559 7.12767 56.2912 7.003 55.7925 7.003C55.2939 7.003 54.8292 7.12767 54.3985 7.377C53.9679 7.615 53.6165 7.972 53.3445 8.448C53.0839 8.91267 52.9535 9.468 52.9535 10.114C52.9535 10.76 53.0839 11.3267 53.3445 11.814C53.6165 12.3013 53.9679 12.6753 54.3985 12.936C54.8405 13.1853 55.3052 13.31 55.7925 13.31C56.2912 13.31 56.7559 13.1853 57.1865 12.936C57.6172 12.6867 57.9629 12.324 58.2235 11.848C58.4955 11.3607 58.6315 10.794 58.6315 10.148ZM65.07 6.833C65.3533 6.357 65.7273 5.98867 66.192 5.728C66.668 5.456 67.229 5.32 67.875 5.32V7.326H67.382C66.6227 7.326 66.0447 7.51867 65.648 7.904C65.2627 8.28933 65.07 8.958 65.07 9.91V14.84H63.132V5.473H65.07V6.833ZM73.3624 10.165L77.6804 14.84H75.0624L71.5944 10.811V14.84H69.6564V2.26H71.5944V9.57L74.9944 5.473H77.6804L73.3624 10.165Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="logo-gradient"
|
||||
x1="10"
|
||||
y1="0"
|
||||
x2="10"
|
||||
y2="20"
|
||||
gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#9B99FE" />
|
||||
<stop
|
||||
offset="1"
|
||||
stopColor="#2BC8B7"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Separator } from "./ui/separator";
|
||||
|
||||
export const Logo = ({
|
||||
className,
|
||||
uniColor,
|
||||
hasCaption
|
||||
}: {
|
||||
className?: string;
|
||||
uniColor?: boolean;
|
||||
hasCaption?: boolean;
|
||||
}) => {
|
||||
const _logoText = <span className="font-bold text-xl leading-tight">مستر دکور</span>
|
||||
return (
|
||||
<Link href="/" className={cn('flex items-center justify-center', hasCaption ? 'flex-col text-center' : 'gap-2', className)}>
|
||||
<LogoIcon uniColor={uniColor} />
|
||||
{hasCaption ?
|
||||
<span className="flex flex-col items-center">
|
||||
{_logoText}
|
||||
<span className="text-xs text-muted-foreground font-normal leading-tight">
|
||||
زیرساخت دیجیتال بازار طلا
|
||||
</span>
|
||||
<Separator className="mt-2 max-w-10" />
|
||||
</span>
|
||||
: _logoText}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogoIcon = ({
|
||||
className,
|
||||
uniColor,
|
||||
}: {
|
||||
className?: string;
|
||||
uniColor?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Image
|
||||
src="/assets/montazeranLogo.png"
|
||||
alt="Logo"
|
||||
width={50}
|
||||
height={50}
|
||||
className={cn("mx-auto h-10 w-auto object-contain object-center md:h-12 dark:invert", className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogoStroke = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
className={cn("size-7 w-7", className)}
|
||||
viewBox="0 0 71 25"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M61.25 1.625L70.75 1.5625C70.75 4.77083 70.25 7.79167 69.25 10.625C68.2917 13.4583 66.8958 15.9583 65.0625 18.125C63.2708 20.25 61.125 21.9375 58.625 23.1875C56.1667 24.3958 53.4583 25 50.5 25C46.875 25 43.6667 24.2708 40.875 22.8125C38.125 21.3542 35.125 19.2083 31.875 16.375C29.75 14.4167 27.7917 12.8958 26 11.8125C24.2083 10.7292 22.2708 10.1875 20.1875 10.1875C18.0625 10.1875 16.25 10.7083 14.75 11.75C13.25 12.75 12.0833 14.1875 11.25 16.0625C10.4583 17.9375 10.0625 20.1875 10.0625 22.8125L0 22.9375C0 19.6875 0.479167 16.6667 1.4375 13.875C2.4375 11.0833 3.83333 8.64583 5.625 6.5625C7.41667 4.47917 9.54167 2.875 12 1.75C14.5 0.583333 17.2292 0 20.1875 0C23.8542 0 27.1042 0.770833 29.9375 2.3125C32.8125 3.85417 35.7708 5.97917 38.8125 8.6875C41.1042 10.7708 43.1042 12.3333 44.8125 13.375C46.5625 14.375 48.4583 14.875 50.5 14.875C52.6667 14.875 54.5417 14.3125 56.125 13.1875C57.75 12.0625 59 10.5 59.875 8.5C60.7917 6.5 61.25 4.20833 61.25 1.625Z"
|
||||
fill="none"
|
||||
strokeWidth={0.5}
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<Sun className="size-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<Sun className="size-4 rotate-0 scale-100 transition-all duration-300 dark:-rotate-90 dark:scale-0" />
|
||||
<Moon className="absolute size-4 rotate-90 scale-0 transition-all duration-300 dark:rotate-0 dark:scale-100" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Cta10Props {
|
||||
heading: string;
|
||||
description: string;
|
||||
buttons?: {
|
||||
primary?: {
|
||||
text: string;
|
||||
url: string;
|
||||
};
|
||||
secondary?: {
|
||||
text: string;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Cta10 = ({
|
||||
heading = "Call to Action",
|
||||
description = "Build faster with our collection of pre-built blocks. Speed up your development and ship features in record time.",
|
||||
buttons = {
|
||||
primary: {
|
||||
text: "Buy Now",
|
||||
url: "https://www.shadcnblocks.com",
|
||||
},
|
||||
},
|
||||
className,
|
||||
}: Cta10Props) => {
|
||||
return (
|
||||
<section className={cn("pt-32 pb-40 md:pb-48 lg:pb-56", className)}>
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="flex w-full flex-col gap-16 overflow-hidden rounded-lg bg-accent p-8 md:rounded-xl lg:flex-row lg:items-center lg:p-12">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-heading-2 mb-3 font-semibold md:mb-4 lg:mb-6">
|
||||
{heading}
|
||||
</h2>
|
||||
<p className="text-body max-w-xl leading-relaxed text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 flex-col gap-2 sm:flex-row">
|
||||
{buttons.secondary && (
|
||||
<Button variant="secondary" asChild size="lg">
|
||||
<a href={buttons.secondary.url}>{buttons.secondary.text}</a>
|
||||
</Button>
|
||||
)}
|
||||
{buttons.primary && (
|
||||
<Button asChild variant="primary" size="lg">
|
||||
<a href={buttons.primary.url}>{buttons.primary.text}</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Cta10 };
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
interface Cta14Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Cta14 = ({ className }: Cta14Props) => {
|
||||
const content = useContent();
|
||||
const cta = content.pages.product.abeshdeHamkar.cta14;
|
||||
|
||||
return (
|
||||
<section className={cn("py-32", className)} dir="rtl">
|
||||
<div className="container mx-auto max-w-6xl px-6">
|
||||
<div className="flex min-h-[280px] items-center justify-center rounded-2xl border border-border bg-card shadow-sm">
|
||||
<div className="flex flex-col gap-8 p-4 text-center">
|
||||
<p className="text-body font-medium leading-[var(--line-height-body)] text-foreground">
|
||||
{cta.text}
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href="/contact">{cta.button}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Cta14 };
|
||||
@@ -0,0 +1,119 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
import Link from "next/link";
|
||||
|
||||
interface Cta17GoldStoreProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const CirclesPattern = () => (
|
||||
<svg
|
||||
className="absolute inset-0 size-full"
|
||||
viewBox="0 0 1984 1984"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width="1983"
|
||||
height="1983"
|
||||
rx="991.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="100.5"
|
||||
y="100.5"
|
||||
width="1783"
|
||||
height="1783"
|
||||
rx="891.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="200.5"
|
||||
y="200.5"
|
||||
width="1583"
|
||||
height="1583"
|
||||
rx="791.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="300.5"
|
||||
y="300.5"
|
||||
width="1383"
|
||||
height="1383"
|
||||
rx="691.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="400.25"
|
||||
y="400.25"
|
||||
width="1183.5"
|
||||
height="1183.5"
|
||||
rx="591.75"
|
||||
strokeWidth="0.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="500.25"
|
||||
y="500.25"
|
||||
width="983.5"
|
||||
height="983.5"
|
||||
rx="491.75"
|
||||
strokeWidth="0.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="600.25"
|
||||
y="600.25"
|
||||
width="783.5"
|
||||
height="783.5"
|
||||
rx="391.75"
|
||||
strokeWidth="0.5"
|
||||
className="fill-muted/50 stroke-border"
|
||||
/>
|
||||
<rect
|
||||
x="700.25"
|
||||
y="700.25"
|
||||
width="583.5"
|
||||
height="583.5"
|
||||
rx="291.75"
|
||||
strokeWidth="0.5"
|
||||
className="fill-muted/30 stroke-border"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const Cta17GoldStore = ({ className }: Cta17GoldStoreProps) => {
|
||||
const content = useContent();
|
||||
const cta = content.pages.product.forushgahSazTala.cta17;
|
||||
return (
|
||||
<section className={cn("pt-8 md:pt-12 lg:pt-16 pb-24 md:pb-32 lg:pb-40", className)}>
|
||||
<div className="relative flex items-center justify-center overflow-hidden border py-20 text-center md:p-20">
|
||||
<CirclesPattern />
|
||||
<div className="container relative mx-auto max-w-7xl px-6">
|
||||
<div className="mx-auto max-w-3xl" dir="rtl">
|
||||
<h2 className="text-heading-2 mb-4 font-semibold leading-snug text-balance">
|
||||
{cta.heading}
|
||||
</h2>
|
||||
<p className="text-body leading-relaxed text-muted-foreground">
|
||||
{cta.description}
|
||||
</p>
|
||||
<div className="mt-11 flex flex-col justify-center gap-2 sm:flex-row">
|
||||
<Button size="lg" variant="primary" asChild>
|
||||
<Link href="/contact">{cta.button}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Cta17GoldStore };
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Cta6Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Cta6 = ({ className }: Cta6Props) => {
|
||||
return (
|
||||
<section className={cn("section-blur-separator relative overflow-hidden py-20 md:py-24 lg:py-28", className)} dir="rtl">
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute left-1/2 top-1/2 -z-10 -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<div className="h-44 w-[24rem] rounded-full bg-primary/10 blur-3xl md:h-52 md:w-[34rem] md:bg-primary/12" />
|
||||
</div>
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mx-auto max-w-6xl p-6 shadow-sm md:rounded-3xl md:p-10 lg:p-12">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<p className="mb-3 text-caption font-medium text-primary">ویترین متفاوت، فروش متفاوت</p>
|
||||
<h2 className="text-heading-2 font-semibold text-foreground">
|
||||
برای انتخاب استند و دکور مناسب فروشگاهتان مشاوره بگیرید
|
||||
</h2>
|
||||
<p className="mx-auto mt-4 max-w-2xl text-body text-muted-foreground">
|
||||
کارشناسان مستر دکور با بررسی فضای فروشگاه، نوع محصولات و سبک برند شما، بهترین ترکیب استند و ملزومات ویترین را
|
||||
پیشنهاد می دهند تا مشتری در نگاه اول کیفیت شما را حس کند.
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-4">
|
||||
<Button asChild size="lg" variant="primary" className="w-full sm:w-auto">
|
||||
<Link href="/consultation">
|
||||
دریافت مشاوره و استعلام قیمت
|
||||
<ArrowRight className="ms-2 size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Cta6 };
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Mail, SendHorizonal } from 'lucide-react'
|
||||
|
||||
export default function CallToAction3() {
|
||||
return (
|
||||
<section className="py-16 md:py-24 lg:py-32">
|
||||
<div className="mx-auto max-w-5xl px-6">
|
||||
<div className="text-center">
|
||||
<h2 className="text-heading-2 text-balance font-semibold">Start Building</h2>
|
||||
<p className="mt-4">Libero sapiente aliquam quibusdam aspernatur.</p>
|
||||
|
||||
<form
|
||||
action=""
|
||||
className="mx-auto mt-10 max-w-sm lg:mt-12">
|
||||
<div className="bg-background has-[input:focus]:ring-muted relative grid grid-cols-[1fr_auto] items-center rounded-[calc(var(--radius)+0.75rem)] border border-border pr-3 shadow-sm has-[input:focus]:ring-2">
|
||||
<Mail className="text-caption pointer-events-none absolute inset-y-0 left-5 my-auto size-5" />
|
||||
|
||||
<input
|
||||
placeholder="Your mail address"
|
||||
className="h-14 w-full bg-transparent pl-12 focus:outline-none"
|
||||
type="email"
|
||||
/>
|
||||
|
||||
<div className="md:pr-1.5 lg:pr-0">
|
||||
<Button variant="primary" aria-label="submit">
|
||||
<span className="hidden md:block">Get Started</span>
|
||||
<SendHorizonal
|
||||
className="relative mx-auto size-5 md:hidden"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function CallToAction() {
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="mx-auto max-w-5xl rounded-3xl border px-6 py-12 md:py-20 lg:py-32">
|
||||
<div className="text-center">
|
||||
<h2 className="text-heading-2 text-balance font-semibold">Start Building</h2>
|
||||
<p className="mt-4">Libero sapiente aliquam quibusdam aspernatur.</p>
|
||||
|
||||
<div className="mt-12 flex flex-wrap justify-center gap-4">
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href="/">
|
||||
<span>Get Started</span>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="secondary">
|
||||
<Link href="/consultation">
|
||||
<span>درخواست مشاوره</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
import { useContent } from '@/components/layout/content-provider'
|
||||
|
||||
export default function CtaHome() {
|
||||
const content = useContent()
|
||||
const { ctaHome } = content.pages.home
|
||||
|
||||
return (
|
||||
<section id="mozakerat" className="py-12 md:py-16 lg:py-20 border-t border-border/50">
|
||||
<div className="mx-auto max-w-5xl rounded-3xl border border-border bg-card px-6 py-10 md:py-16 lg:py-20 dark:border-primary/20 dark:bg-card/50 hover:border-primary/30 dark:hover:border-primary/30 transition-colors">
|
||||
<div className="text-center">
|
||||
<h2 className="text-heading-2 text-balance font-semibold">{ctaHome.heading}</h2>
|
||||
<p className="text-body mt-4 leading-relaxed text-muted-foreground">{ctaHome.description}</p>
|
||||
|
||||
<div className="mt-12 flex flex-wrap justify-center gap-4">
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href="/consultation">
|
||||
<span>{ctaHome.primaryButton}</span>
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="secondary">
|
||||
<Link href="/consultation">
|
||||
<span>{ctaHome.secondaryButton}</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
|
||||
interface CtaProductFinalProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function CtaProductFinal({ className }: CtaProductFinalProps) {
|
||||
return (
|
||||
<section className={cn("py-16 md:py-24 lg:py-32", className)}>
|
||||
<div className="container mx-auto max-w-4xl px-6">
|
||||
<div className="flex flex-col items-center text-center space-y-8 p-8 rounded-2xl bg-card border border-border shadow-sm">
|
||||
<p className="text-body leading-relaxed text-muted-foreground">
|
||||
اگر حجم معاملات شما بالاست و به دنبال کاهش تماسهای غیرضروری و مدیریت دقیق حسابها هستید،
|
||||
برای دریافت معرفی کامل سامانه با ما در تماس باشید.
|
||||
</p>
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href="/consultation">
|
||||
درخواست جلسه تخصصی
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
interface CtaProductProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const CtaProduct = ({ className }: CtaProductProps) => {
|
||||
return (
|
||||
<section className={cn("py-32", className)}>
|
||||
<div className="container mx-auto">
|
||||
<div>
|
||||
<p className="text-center text-sm">Transform your business today.</p>
|
||||
<div className="mt-4 flex items-center justify-between gap-4">
|
||||
<Separator className="shrink" />
|
||||
<Button size="lg" variant="primary">Get Started</Button>
|
||||
<Separator className="shrink" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { CtaProduct };
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Handshake,
|
||||
Download,
|
||||
GraduationCap,
|
||||
Headphones,
|
||||
RefreshCw,
|
||||
BarChart3,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const cooperationSteps = [
|
||||
{
|
||||
title: "برگزاری جلسه معرفی و نیازسنجی",
|
||||
description: "شناسایی نیازها و تعیین اهداف همکاری",
|
||||
icon: Handshake,
|
||||
},
|
||||
{
|
||||
title: "نصب نسخه اختصاصی",
|
||||
description: "راهاندازی سامانه متناسب با کسبوکار شما",
|
||||
icon: Download,
|
||||
},
|
||||
{
|
||||
title: "آموزش و راهاندازی",
|
||||
description: "آموزش تیم و راهاندازی عملیاتی",
|
||||
icon: GraduationCap,
|
||||
},
|
||||
{
|
||||
title: "۶ ماه پشتیبانی رایگان",
|
||||
description: "پشتیبانی فنی در ماههای اول همکاری",
|
||||
icon: Headphones,
|
||||
},
|
||||
{
|
||||
title: "ادامه پشتیبانی و توسعه ماهانه",
|
||||
description: "پشتیبانی مستمر و بهروزرسانیهای دورهای",
|
||||
icon: RefreshCw,
|
||||
},
|
||||
{
|
||||
title: "ارزیابی و بهینهسازی مستمر",
|
||||
description: "بررسی عملکرد و پیشنهاد بهبود",
|
||||
icon: BarChart3,
|
||||
},
|
||||
];
|
||||
|
||||
interface Feature104Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature104 = ({ className }: Feature104Props) => {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"overflow-visible border-t border-border/50 bg-background py-10 sm:py-16 md:py-24 lg:py-32",
|
||||
className
|
||||
)}
|
||||
dir="rtl"
|
||||
>
|
||||
<div className="container mx-auto px-4 sm:px-6">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-3 sm:mb-4 border-primary/50 bg-background px-3 py-1 sm:px-4 sm:py-1.5 text-caption font-medium text-foreground"
|
||||
>
|
||||
مسیر همکاری
|
||||
</Badge>
|
||||
<h2 className="text-heading-2 font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
فرآیند همکاری
|
||||
</h2>
|
||||
<p className="mt-3 text-body text-muted-foreground sm:mt-4">
|
||||
از اولین تماس تا راهاندازی و پشتیبانی، در هر مرحله همراه شما
|
||||
هستیم تا همکاری با مستر دکور ساده و شفاف باشد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-8 max-w-5xl sm:mt-12">
|
||||
<div className="relative overflow-hidden rounded-lg border border-border bg-card sm:rounded-xl">
|
||||
<img
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/illustrations/tokyo-letters-and-arrows-flying-out-of-a-black-hole.svg"
|
||||
alt=""
|
||||
className="h-32 w-full scale-x-[-1] object-cover object-center opacity-60 sm:h-40 md:h-48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-10 grid max-w-6xl gap-4 sm:mt-16 sm:gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{cooperationSteps.map((step, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-3 rounded-xl border border-border bg-card p-4 shadow-sm transition-colors hover:bg-accent/50 sm:gap-4 sm:p-6"
|
||||
>
|
||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary sm:size-10">
|
||||
<step.icon className="size-5" />
|
||||
</div>
|
||||
<h3 className="text-heading-4 font-semibold leading-tight text-foreground">
|
||||
{step.title}
|
||||
</h3>
|
||||
<p className="text-body-sm text-muted-foreground">
|
||||
{step.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature104 };
|
||||
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Feature188Resource {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
imageSrc: string;
|
||||
}
|
||||
|
||||
interface Feature188Props {
|
||||
className?: string;
|
||||
title?: string;
|
||||
resources?: Feature188Resource[];
|
||||
}
|
||||
|
||||
const defaultResources: Feature188Resource[] = [
|
||||
{
|
||||
title: "نصب نسخه اختصاصی برای هر مجموعه",
|
||||
description:
|
||||
"هر سازمان نسخهٔ جداگانه و مستقل از سامانه را روی سرور خود دریافت میکند. دادهها کاملاً در اختیار شما میمانند و امنیت و کنترل کامل بر اطلاعات خود دارید.",
|
||||
href: "#",
|
||||
imageSrc:
|
||||
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-8-wide.svg",
|
||||
},
|
||||
{
|
||||
title: "امکان شخصیسازی کامل بر اساس مدل کاری",
|
||||
description:
|
||||
"ماژولها، فیلدها و گردشکارها متناسب با فرایندهای داخلی مجموعهٔ شما تنظیم میشوند. سامانه دقیقاً با نیاز و مدل کاری شما هماهنگ میشود.",
|
||||
href: "#",
|
||||
imageSrc:
|
||||
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-8-wide.svg",
|
||||
},
|
||||
{
|
||||
title: "پشتیبانی مستقیم و توسعه مستمر",
|
||||
description:
|
||||
"تیم فنی ما بهصورت مستقیم پاسخگوی سوالات و مشکلات شماست. امکانات جدید و بهروزرسانیهای امنیتی و عملکردی بهطور منظم ارائه میشود.",
|
||||
href: "#",
|
||||
imageSrc:
|
||||
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-8-wide.svg",
|
||||
},
|
||||
];
|
||||
|
||||
const Feature188 = ({
|
||||
className,
|
||||
title = "چرا این سامانه انتخاب مجموعههای حرفهای است؟",
|
||||
resources = defaultResources,
|
||||
}: Feature188Props) => {
|
||||
return (
|
||||
<section
|
||||
dir="rtl"
|
||||
className={cn(
|
||||
"relative w-full overflow-hidden border-t border-border/50 bg-background py-16 md:py-24 lg:py-32",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto max-w-5xl px-4 sm:px-6">
|
||||
<h2 className="text-center text-heading-2 font-semibold leading-[var(--line-height-heading)] text-foreground sm:text-heading-2 lg:text-heading-2">
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<div className="mx-auto mt-10 sm:mt-12 rounded-2xl border border-border bg-background overflow-hidden">
|
||||
{resources.map((resource, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={cn(
|
||||
index !== resources.length - 1 && "border-b border-border",
|
||||
)}
|
||||
>
|
||||
<a
|
||||
href={resource.href}
|
||||
className="flex flex-col gap-4 p-4 md:flex-row md:items-center md:justify-between md:gap-6 lg:p-6"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-start gap-4 sm:gap-5">
|
||||
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-muted p-2 lg:h-14 lg:w-14">
|
||||
<ArrowLeft className="h-5 w-5 text-muted-foreground sm:h-6 sm:w-6" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<h3 className="text-heading-4 font-semibold leading-[var(--line-height-heading)] text-foreground sm:text-heading-3">
|
||||
{resource.title}
|
||||
</h3>
|
||||
<p className="text-body-sm leading-[var(--line-height-body)] text-muted-foreground sm:text-body">
|
||||
{resource.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 w-full shrink-0 md:mt-0 md:w-52 lg:w-60">
|
||||
<div className="aspect-video w-full overflow-hidden rounded-lg bg-muted">
|
||||
<img
|
||||
src={resource.imageSrc}
|
||||
alt={resource.title}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature188 };
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Feature189AbeshdeHamkarProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature189AbeshdeHamkar = ({ className }: Feature189AbeshdeHamkarProps) => {
|
||||
const features = [
|
||||
{
|
||||
title: "بهرهوری بیشتر",
|
||||
description:
|
||||
"ثبت و پیگیری معاملات آبشده بدون تماسهای مکرر؛ دسترسی لحظهای به مانده و ریز تراکنشها تا روی کار اصلی تمرکز کنید.",
|
||||
},
|
||||
{
|
||||
title: "همکاری و شفافیت",
|
||||
description:
|
||||
"همه طرفین معامله بهصورت شفاف به دادهها دسترسی دارند؛ همکاری بدون ابهام و تسویه بر اساس اعداد واقعی.",
|
||||
},
|
||||
{
|
||||
title: "گزارش و تحلیل",
|
||||
description:
|
||||
"گزارشهای تفصیلی و ابزارهای تحلیلی برای ارزیابی عملکرد و تصمیمگیری بر اساس داده.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"bg-background py-16 sm:py-24 md:py-28 lg:py-32",
|
||||
className
|
||||
)}
|
||||
dir="rtl"
|
||||
>
|
||||
<div className="container mx-auto max-w-6xl px-4 sm:px-6">
|
||||
<div className="grid items-center gap-10 sm:gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
{/* تصاویر سکشن آبشده همکار */}
|
||||
<div className="relative mb-8 lg:mb-0">
|
||||
<div className="relative -top-8 z-10 mx-auto w-[55%] sm:-top-10 sm:w-[40%] lg:-top-12 lg:w-[35%]">
|
||||
<img
|
||||
alt="Professional Business Collaboration Meeting abshodeh-hamkar"
|
||||
src="/assets/abshode-hamkar/Professional Business Collaboration Meeting abshodeh-hamkar.jpg"
|
||||
className="h-[180px] rounded-xl object-cover shadow-lg sm:h-[260px] lg:h-[280px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute left-0 top-4 z-0 w-[50%] -rotate-12 sm:top-6 sm:w-[35%]">
|
||||
<img
|
||||
alt="Financial Management Productivity Workspace abshodeh-hamkar"
|
||||
src="/assets/abshode-hamkar/Financial Management Productivity Workspace abshodeh-hamkar.jpg"
|
||||
className="h-[160px] rounded-xl object-cover shadow-lg sm:h-[240px] lg:h-[260px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute right-0 top-4 z-20 w-[50%] rotate-12 sm:top-6 sm:w-[35%]">
|
||||
<img
|
||||
alt="Advanced Data Analysis Dashboard Office abshodeh-hamkar"
|
||||
src="/assets/abshode-hamkar/Advanced Data Analysis Dashboard Office abshodeh-hamkar.jpg"
|
||||
className="h-[160px] rounded-xl object-cover shadow-lg sm:h-[240px] lg:h-[260px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* محتوا */}
|
||||
<div className="max-w-xl lg:max-w-none lg:mr-auto">
|
||||
<h2 className="text-heading-2 font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground lg:max-w-[90%]">
|
||||
با ابزارهای حرفهای، کسبوکارتان را متحول کنید
|
||||
</h2>
|
||||
|
||||
<div className="mt-6 grid gap-6 sm:mt-10 sm:gap-8">
|
||||
{features.map((feature) => (
|
||||
<div key={feature.title} className="flex items-start gap-4">
|
||||
<div>
|
||||
<h3 className="text-heading-4 font-semibold leading-[var(--line-height-heading)] text-foreground">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-body mt-1.5 leading-[var(--line-height-body)] text-muted-foreground">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
asChild
|
||||
variant="secondary"
|
||||
className="mt-6 flex items-center gap-2 sm:mt-10"
|
||||
>
|
||||
<Link href="/contact">
|
||||
بیشتر بدانید
|
||||
<ArrowRight className="size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature189AbeshdeHamkar };
|
||||
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { Layout, UserPlus, Users, Zap } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
const iconClass = "size-10 stroke-white transition-all ease-in-out group-hover:scale-105";
|
||||
const stepIcons = [
|
||||
<UserPlus key="0" className={iconClass} aria-hidden />,
|
||||
<Layout key="1" className={iconClass} aria-hidden />,
|
||||
<Zap key="2" className={iconClass} aria-hidden />,
|
||||
<Users key="3" className={iconClass} aria-hidden />,
|
||||
];
|
||||
|
||||
interface Feature243GoldStoreProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature243GoldStore = ({ className }: Feature243GoldStoreProps) => {
|
||||
const content = useContent();
|
||||
const { heading, steps: stepsContent } = content.pages.product.forushgahSazTala.feature243;
|
||||
const steps = stepsContent.map((step, index) => ({
|
||||
title: step.title,
|
||||
description: step.description,
|
||||
icon: stepIcons[index] ?? stepIcons[0],
|
||||
}));
|
||||
|
||||
return (
|
||||
<section className={cn("overflow-visible bg-background", className)} dir="rtl">
|
||||
<div className="container mx-auto max-w-7xl px-4 py-16 sm:px-6 sm:py-24 md:py-28 lg:py-32">
|
||||
<header className="relative z-10 flex flex-col items-center justify-center gap-6 bg-background text-center">
|
||||
<h2 className="text-heading-2 font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
{heading}
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<div className="mt-44 flex flex-wrap items-stretch justify-center gap-6 sm:mt-48 md:mt-52 lg:gap-8">
|
||||
{steps.map((step, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative flex w-full max-w-[18rem] flex-col items-center overflow-visible sm:w-64 sm:max-w-none md:w-72"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute -top-[174px] left-1/2 h-0.5 w-[120%] -translate-x-1/2 rounded-full bg-muted",
|
||||
index === 3 ? "hidden" : "",
|
||||
index === 2 ? "hidden lg:block" : "",
|
||||
index === 1 ? "hidden lg:block" : "",
|
||||
index === 0 ? "hidden sm:block" : "",
|
||||
)}
|
||||
/>
|
||||
<div className="absolute -top-[174px] left-1/2 h-full w-0.5 -translate-x-1/2 rounded-full bg-muted" />
|
||||
<div className="relative z-10 flex h-8 w-5 flex-shrink-0 items-center justify-center rounded-full bg-background p-1 pb-5">
|
||||
<div className="relative z-10 size-3 rounded-full bg-foreground" />
|
||||
</div>
|
||||
|
||||
<Card className="group relative z-10 h-full w-full overflow-hidden rounded-4xl border-none bg-muted shadow-none">
|
||||
<CardContent className="flex h-full flex-col items-center gap-5 p-0 pt-10 pb-5">
|
||||
<div className="flex size-20 shrink-0 items-center justify-center rounded-3xl bg-foreground dark:bg-background">
|
||||
{step.icon}
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col items-center gap-4 px-4 pt-5 pb-3">
|
||||
<h3 className="text-heading-3 w-full text-center font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
{step.title}
|
||||
</h3>
|
||||
{step.description ? (
|
||||
<p className="text-body w-full max-w-sm text-center text-muted-foreground/70">
|
||||
{step.description}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature243GoldStore };
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import { BadgeCheck } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Feature246Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature246 = ({ className }: Feature246Props) => {
|
||||
const benefits = [
|
||||
"برگزاری جلسه معرفی و نیازسنجی",
|
||||
"نصب نسخه اختصاصی",
|
||||
"آموزش و راهاندازی",
|
||||
"۶ ماه پشتیبانی رایگان",
|
||||
"ادامه پشتیبانی و توسعه ماهانه",
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={cn("overflow-visible bg-background", className)} dir="rtl">
|
||||
<div className="container mx-auto flex flex-col items-center justify-between overflow-visible px-6 py-40 lg:flex-row">
|
||||
<div className="flex w-full max-w-4xl flex-col items-center gap-20 text-center lg:mr-2 lg:items-start lg:text-left">
|
||||
<h2 className="text-heading-2 font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
بخش فرآیند همکاری
|
||||
</h2>
|
||||
<div className="flex flex-col space-y-5 lg:items-start">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div key={index} className="flex items-center justify-center gap-4 lg:justify-start">
|
||||
<div className="flex h-7 w-7 items-center justify-center">
|
||||
<BadgeCheck className="h-20" />
|
||||
</div>
|
||||
<span className="text-heading-4 leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
{benefit}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex min-h-[400px] w-full items-center justify-center overflow-visible rounded-none py-12 lg:min-h-[500px] lg:py-20">
|
||||
<img
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/illustrations/tokyo-letters-and-arrows-flying-out-of-a-black-hole.svg"
|
||||
className="h-full w-full scale-x-[-1] object-contain object-center"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature246 };
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import { motion, MotionValue, useScroll, useTransform } from "motion/react";
|
||||
import React from "react";
|
||||
import { ComponentPropsWithoutRef, FC, ReactNode, useRef } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
const Feature247 = () => {
|
||||
const content = useContent();
|
||||
const { title, description } = content.pages.product.forushgahSazTala.feature247;
|
||||
return (
|
||||
<section className="py-32">
|
||||
<div className="container mx-auto flex flex-col items-center justify-center px-6">
|
||||
<div className="flex max-w-4xl justify-center">
|
||||
<TextReveal
|
||||
title={title}
|
||||
className="items-center justify-center text-center"
|
||||
>
|
||||
{description}
|
||||
</TextReveal>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature247 };
|
||||
|
||||
// Below is the modified component from Magic UI
|
||||
// Original source: https://magicui.design/docs/components/text-reveal
|
||||
// Modified to follow our coding standards and design system
|
||||
// We respect copyright and attribution to the original creators
|
||||
|
||||
interface TextRevealProps extends ComponentPropsWithoutRef<"div"> {
|
||||
children: string;
|
||||
title?: string;
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
const TextReveal: FC<TextRevealProps> = ({
|
||||
children,
|
||||
title,
|
||||
className,
|
||||
maxWidth,
|
||||
}) => {
|
||||
const targetRef = useRef<HTMLDivElement | null>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: targetRef,
|
||||
});
|
||||
|
||||
const words = children.split(" ");
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={targetRef}
|
||||
className={cn("relative z-0 h-[200vh]", className)}
|
||||
style={{ maxWidth: maxWidth || "56rem" }}
|
||||
>
|
||||
<div className="sticky top-0 mx-auto flex h-[50%] items-center bg-transparent px-[1rem] py-[5rem]">
|
||||
<div ref={targetRef} className="flex flex-col justify-center">
|
||||
<span className="text-heading-4 text-center font-medium leading-snug tracking-tight text-foreground">
|
||||
{title}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"flex flex-wrap justify-center p-5 text-2xl font-semibold leading-relaxed text-black/20 md:p-8 md:text-3xl lg:p-10 lg:text-4xl xl:text-5xl dark:text-white/20",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{words.map((word, i) => {
|
||||
const start = i / words.length;
|
||||
const end = start + 1 / words.length;
|
||||
return (
|
||||
<Word key={i} progress={scrollYProgress} range={[start, end]}>
|
||||
{word}
|
||||
</Word>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface WordProps {
|
||||
children: ReactNode;
|
||||
progress: MotionValue<number>;
|
||||
range: [number, number];
|
||||
}
|
||||
|
||||
const Word: FC<WordProps> = ({ children, progress, range }) => {
|
||||
const opacity = useTransform(progress, range, [0, 1]);
|
||||
return (
|
||||
<span className="relative mx-1 lg:mx-1.5">
|
||||
<span className="absolute opacity-30">{children}</span>
|
||||
<motion.span
|
||||
style={{ opacity: opacity }}
|
||||
className="text-black dark:text-white"
|
||||
>
|
||||
{children}
|
||||
</motion.span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
import { ArrowRight, Check } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import {
|
||||
CardBody,
|
||||
CardContainer,
|
||||
CardItem,
|
||||
} from "@/components/aceternity/3d-card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const defaultFeatures = [
|
||||
{ title: "Instant Implementation", description: "Get access to your blocks the moment you pay." },
|
||||
{ title: "One-Time Payment", description: "No subscriptions. Just pay once and own it forever." },
|
||||
{ title: "Developer Friendly", description: "Clean, copy-paste-ready code with full Tailwind + shadcn support." },
|
||||
{ title: "Fully Responsive", description: "Every block looks perfect on all screen sizes, out of the box." },
|
||||
{ title: "Production Ready", description: "Battle-tested components you can drop into real projects today." },
|
||||
];
|
||||
|
||||
interface Feature268Props {
|
||||
className?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showButtons?: boolean;
|
||||
listTitle?: string;
|
||||
features?: { title: string; description?: string }[];
|
||||
cardTitle?: string;
|
||||
cardWelcome?: string;
|
||||
cardImage?: string;
|
||||
showTryNow?: boolean;
|
||||
}
|
||||
|
||||
const Feature268 = ({
|
||||
className,
|
||||
title = "Drop-In Ready Blocks to Supercharge Your App",
|
||||
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad",
|
||||
showButtons = true,
|
||||
listTitle = "ShadcnBlocks Perks",
|
||||
features = defaultFeatures,
|
||||
cardTitle = "#1 Biggest Shadcn Blocks Collection",
|
||||
cardWelcome = "Welcome to SHADCN BLOCKS",
|
||||
cardImage,
|
||||
showTryNow = true,
|
||||
}: Feature268Props) => {
|
||||
const defaultCardImage = "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img27.jpeg";
|
||||
return (
|
||||
<section className={cn("h-full w-full max-w-full overflow-x-hidden overflow-y-hidden py-32", className)}>
|
||||
<div className="relative container mx-auto px-4 md:px-6">
|
||||
<div className="flex h-full flex-col items-center justify-center gap-10 lg:flex-row lg:justify-center lg:max-w-7xl lg:mx-auto">
|
||||
<div className="w-full space-y-5 lg:w-3/5">
|
||||
<h2 className="text-heading-2 mt-3 w-full max-w-2xl font-semibold tracking-tight">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-body max-w-xl leading-relaxed text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
{showButtons && (
|
||||
<div className="mt-10 mb-29 flex flex-wrap gap-4">
|
||||
<Button variant="primary">Get Started</Button>
|
||||
<Button variant="secondary">Learn More</Button>
|
||||
</div>
|
||||
)}
|
||||
<ul className="space-y-4">
|
||||
<li className="mb-3">
|
||||
<p className="text-caption font-medium text-muted-foreground">
|
||||
{listTitle}
|
||||
</p>
|
||||
</li>
|
||||
{features.map((feature) => (
|
||||
<li key={feature.title} className="flex gap-4 lg:items-center">
|
||||
<Check className="size-4 shrink-0" />
|
||||
<p className="text-body-sm font-normal tracking-tight">
|
||||
{feature.title}
|
||||
{feature.description != null && feature.description !== "" && (
|
||||
<span className="pl-2 opacity-30">{feature.description}</span>
|
||||
)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<CardContainer
|
||||
className="w-full"
|
||||
containerClassName="h-full w-full p-0 m-0 lg:w-2/5"
|
||||
>
|
||||
<CardBody className="group/card flex h-full !w-full flex-col items-center justify-center rounded-3xl bg-muted/70 px-5 py-15">
|
||||
{cardTitle != null && cardTitle !== "" && (
|
||||
<CardItem
|
||||
translateZ="60"
|
||||
className="text-heading-3 mb-2 max-w-xs text-center font-semibold leading-none tracking-tight"
|
||||
>
|
||||
{cardTitle}
|
||||
</CardItem>
|
||||
)}
|
||||
{cardWelcome != null && cardWelcome !== "" && (
|
||||
<CardItem
|
||||
as="p"
|
||||
translateZ="25"
|
||||
className="text-body-sm my-4 flex w-full max-w-sm items-center justify-center font-medium tracking-tight"
|
||||
>
|
||||
<span className="mr-2 opacity-50">Welcome to</span>
|
||||
<span className="font-semibold">SHADCN BLOCKS</span>
|
||||
</CardItem>
|
||||
)}
|
||||
<CardItem translateZ="60" className="mt-4 w-full max-w-[1000px]">
|
||||
<img
|
||||
src={cardImage ?? defaultCardImage}
|
||||
height="1000"
|
||||
width="1000"
|
||||
className="h-82 w-full rounded-3xl object-cover group-hover/card:shadow-xl lg:h-60"
|
||||
alt="thumbnail"
|
||||
/>
|
||||
</CardItem>
|
||||
|
||||
<div className="flex w-full flex-col items-center justify-center">
|
||||
{showTryNow && (
|
||||
<CardItem translateZ="100">
|
||||
<Button variant="ghost" className="my-5 opacity-50">
|
||||
Try now <ArrowRight />
|
||||
</Button>
|
||||
</CardItem>
|
||||
)}
|
||||
<img
|
||||
className="w-30"
|
||||
src="/images/block/logos/ph-daily.svg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature268 };
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Check } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import {
|
||||
CardBody,
|
||||
CardContainer,
|
||||
CardItem,
|
||||
} from "@/components/aceternity/3d-card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
interface Feature269GoldStoreProps {
|
||||
className?: string;
|
||||
cardImage?: string;
|
||||
}
|
||||
|
||||
const defaultCardImage =
|
||||
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img7.jpeg";
|
||||
|
||||
const Feature269GoldStore = ({ className, cardImage }: Feature269GoldStoreProps) => {
|
||||
const content = useContent();
|
||||
const { heading, description, featuresLabel, cardTitle1, cardTitle2, cardWelcome, cardWelcomeBrand, features } = content.pages.product.forushgahSazTala.feature269;
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"h-full w-full max-w-full overflow-x-hidden overflow-y-hidden py-16 sm:py-24 lg:py-32",
|
||||
className
|
||||
)}
|
||||
dir="rtl"
|
||||
>
|
||||
<div className="relative container mx-auto px-4 md:px-6">
|
||||
<div className="flex h-full flex-col items-center justify-center gap-10 lg:mx-auto lg:max-w-7xl lg:flex-row lg:justify-center">
|
||||
<div className="w-full space-y-5 lg:w-3/5">
|
||||
<h2 className="text-heading-2 mt-3 w-full max-w-2xl font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
{heading}
|
||||
</h2>
|
||||
<p className="text-body max-w-xl leading-[var(--line-height-body)] text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
<div className="mb-6 mt-10 flex flex-wrap gap-4">
|
||||
<Button asChild variant="primary" className="rounded-full">
|
||||
<Link href="/contact">شروع کنید</Link>
|
||||
</Button>
|
||||
<Button asChild variant="secondary" className="rounded-full">
|
||||
<Link href="/contact">بیشتر بدانید</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<ul className="space-y-4">
|
||||
<li className="mb-3">
|
||||
<p className="text-caption font-medium text-muted-foreground">{featuresLabel}</p>
|
||||
</li>
|
||||
{features.map((featureTitle) => (
|
||||
<li key={featureTitle} className="flex items-center gap-2">
|
||||
<Check className="size-4 shrink-0 text-primary" />
|
||||
<span className="text-body-sm font-medium text-foreground">
|
||||
{featureTitle}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<CardContainer
|
||||
className="w-full"
|
||||
containerClassName="h-full w-full p-0 m-0 lg:w-2/5"
|
||||
>
|
||||
<CardBody className="group/card flex h-full !w-full flex-col items-center justify-center rounded-3xl bg-muted/70 px-5 py-10 sm:py-15">
|
||||
<CardItem
|
||||
translateZ="60"
|
||||
className="text-heading-3 mb-2 max-w-xs text-center font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground"
|
||||
>
|
||||
<span className="block">{cardTitle1}</span>
|
||||
<span className="block">{cardTitle2}</span>
|
||||
</CardItem>
|
||||
<CardItem
|
||||
as="p"
|
||||
translateZ="25"
|
||||
className="my-4 flex w-full max-w-sm items-center justify-center gap-2 text-body-sm font-medium tracking-tight text-foreground"
|
||||
>
|
||||
<span className="opacity-50">{cardWelcome}</span>
|
||||
<span className="whitespace-nowrap font-semibold">{cardWelcomeBrand}</span>
|
||||
</CardItem>
|
||||
<CardItem translateZ="60" className="mt-4 w-full max-w-[1000px]">
|
||||
<img
|
||||
src={cardImage ?? defaultCardImage}
|
||||
height="1000"
|
||||
width="1000"
|
||||
className="h-70 w-full rounded-3xl object-cover group-hover/card:shadow-xl lg:h-60"
|
||||
alt="فروشگاه آنلاین طلا"
|
||||
/>
|
||||
</CardItem>
|
||||
<div className="flex w-full flex-col items-center justify-center">
|
||||
<img
|
||||
className="h-8 w-auto object-contain mt-4"
|
||||
src="/assets/mzLogo.png"
|
||||
alt="مستر دکور"
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature269GoldStore };
|
||||
@@ -0,0 +1,93 @@
|
||||
import { BadgeCheck } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import {
|
||||
CardBody,
|
||||
CardContainer,
|
||||
CardItem,
|
||||
} from "@/components/aceternity/3d-card";
|
||||
|
||||
interface Feature269Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature269 = ({ className }: Feature269Props) => {
|
||||
const features = [
|
||||
"Instant Implementation",
|
||||
"One-Time Payment",
|
||||
"Developer Friendly",
|
||||
"Fully Responsive",
|
||||
"Production Ready",
|
||||
"Premium Support",
|
||||
"Regular Updates",
|
||||
"Customizable Design",
|
||||
"Performance Optimized",
|
||||
"Accessibility Compliant",
|
||||
"Cross-Browser ",
|
||||
"Documentation Included",
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={cn("relative w-full overflow-hidden py-32", className)}>
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="relative flex h-full flex-col items-start justify-between gap-10 lg:flex-row">
|
||||
<div className="w-full space-y-5 lg:w-3/5">
|
||||
<h2 className="text-heading-2 mx-auto mb-15 w-full max-w-lg text-center font-semibold tracking-tight lg:mx-0 lg:text-left">
|
||||
Why Choose Shadcn Blocks?
|
||||
</h2>
|
||||
<div className="mb-18 flex items-center gap-4 px-5 lg:max-w-md">
|
||||
<span className="h-px w-full bg-muted-foreground/20" />
|
||||
<p className="text-caption font-medium text-muted-foreground/50">FEATURES</p>
|
||||
<span className="h-px w-full bg-muted-foreground/20" />
|
||||
</div>
|
||||
<ul className="grid grid-cols-2 gap-3 lg:max-w-md">
|
||||
{features.map((feature) => (
|
||||
<li key={feature} className="flex gap-2 lg:items-center">
|
||||
<BadgeCheck className="size-4 text-muted-foreground/80" />
|
||||
<p className="text-body-sm tracking-tight text-muted-foreground/80">
|
||||
{feature}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<CardContainer
|
||||
className="w-full"
|
||||
containerClassName="h-full w-full p-0 m-0 lg:w-2/5"
|
||||
>
|
||||
<CardBody className="group/card flex h-full !w-full flex-col items-center justify-center rounded-3xl bg-muted/70 p-5">
|
||||
<CardItem
|
||||
translateZ="60"
|
||||
className="text-heading-3 mt-10 mb-2 max-w-xs text-center font-semibold leading-none tracking-tight"
|
||||
>
|
||||
#1 Biggest Shadcn Blocks Collection
|
||||
</CardItem>
|
||||
<CardItem
|
||||
as="p"
|
||||
translateZ="25"
|
||||
className="text-body-sm my-4 flex w-full max-w-sm items-center justify-center font-medium tracking-tight"
|
||||
>
|
||||
<span className="mr-2 opacity-50">Welcome to</span>
|
||||
<span className="font-semibold">SHADCN BLOCKS</span>
|
||||
</CardItem>
|
||||
<CardItem translateZ="60" className="mt-4 w-full max-w-[1000px]">
|
||||
<img
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img7.jpeg"
|
||||
height="1000"
|
||||
width="1000"
|
||||
className="h-70 w-full rounded-3xl object-cover group-hover/card:shadow-xl"
|
||||
alt="thumbnail"
|
||||
/>
|
||||
</CardItem>
|
||||
</CardBody>
|
||||
</CardContainer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature269 };
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
"use client";
|
||||
import { motion } from "motion/react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const defaultFeatures = [
|
||||
{
|
||||
number: "01",
|
||||
title: "Instant Implementation",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et",
|
||||
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img11.jpeg",
|
||||
imageOrder: "order-1",
|
||||
contentOrder: "order-2",
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
title: "One time payment",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et",
|
||||
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img12.jpeg",
|
||||
imageOrder: "order-2",
|
||||
contentOrder: "order-1",
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
title: "Developer Friendly",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et",
|
||||
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/guri3/img14.jpeg",
|
||||
imageOrder: "order-1",
|
||||
contentOrder: "order-2",
|
||||
},
|
||||
];
|
||||
|
||||
interface Feature270Item {
|
||||
number: string;
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
imageOrder: string;
|
||||
contentOrder: string;
|
||||
}
|
||||
|
||||
interface Feature270Props {
|
||||
className?: string;
|
||||
heading?: string;
|
||||
features?: Feature270Item[];
|
||||
headingAlign?: "left" | "right" | "center";
|
||||
contentAlign?: "left" | "center" | "right";
|
||||
showNumbers?: boolean;
|
||||
/** وقتی false باشد، دکمه/لینک «Learn More» روی کارتها نمایش داده نمیشود. */
|
||||
showLearnMore?: boolean;
|
||||
}
|
||||
|
||||
const Feature270 = ({
|
||||
className,
|
||||
heading = "Why Choose Shadcn Blocks?",
|
||||
features = defaultFeatures,
|
||||
headingAlign = "left",
|
||||
contentAlign = "left",
|
||||
showNumbers = true,
|
||||
showLearnMore = true,
|
||||
}: Feature270Props) => {
|
||||
const headingClass = {
|
||||
left: "text-left",
|
||||
right: "text-right",
|
||||
center: "text-center",
|
||||
}[headingAlign];
|
||||
const contentClass = {
|
||||
left: "text-left",
|
||||
center: "text-center",
|
||||
right: "text-right",
|
||||
}[contentAlign];
|
||||
|
||||
return (
|
||||
<section className={cn("relative w-full py-32", className)}>
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className={cn("w-full space-y-5 lg:w-3/5 mb-12", headingAlign === "right" && "lg:mr-0 lg:ml-auto")}>
|
||||
<h2 className={cn("text-heading-2 mb-6 w-full max-w-lg font-semibold tracking-tight", headingClass)}>
|
||||
{heading}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="relative grid grid-cols-1 auto-rows-auto items-stretch gap-6 md:gap-3 md:min-h-[68rem] md:grid-cols-2 lg:min-h-[32rem] lg:grid-cols-3">
|
||||
{features.map((feature, index) => {
|
||||
const cardContent = (
|
||||
<div className="flex flex-col h-full min-h-[26rem]">
|
||||
<div className={feature.imageOrder}>
|
||||
<img
|
||||
src={feature.image}
|
||||
height="1000"
|
||||
width="1000"
|
||||
className="h-70 w-full rounded-3xl object-cover group-hover/card:shadow-xl"
|
||||
alt="thumbnail"
|
||||
/>
|
||||
</div>
|
||||
<div className={cn("mt-4 w-full p-3 flex-1 flex flex-col min-h-[8rem] gap-0", feature.contentOrder, contentClass)}>
|
||||
{showNumbers && (
|
||||
<p className="text-caption font-medium text-muted-foreground tracking-tighter">
|
||||
{feature.number}
|
||||
</p>
|
||||
)}
|
||||
<h3 className="text-heading-3 mt-3 mb-2 font-semibold leading-tight tracking-tight text-foreground">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-caption leading-relaxed text-muted-foreground flex-1">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const cardClassName = "w-full h-full min-h-[28rem] rounded-3xl !bg-muted/70 p-4 flex flex-col";
|
||||
return (
|
||||
<PinContainer
|
||||
key={index}
|
||||
title={showLearnMore ? "Learn More" : undefined}
|
||||
href={showLearnMore ? "https://www.shadcnblocks.com/blocks" : undefined}
|
||||
className={cardClassName}
|
||||
showPerspective={showLearnMore}
|
||||
>
|
||||
{cardContent}
|
||||
</PinContainer>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature270 };
|
||||
|
||||
// Below is the modified component from Aceternity UI
|
||||
// Original source: npx shadcn@latest add https://ui.aceternity.com/registry/3d-pin.json
|
||||
// Modified to follow our coding standards and design system
|
||||
// We respect copyright and attribution to the original creators
|
||||
|
||||
export const PinContainer = ({
|
||||
children,
|
||||
title,
|
||||
href,
|
||||
className,
|
||||
containerClassName,
|
||||
showPerspective = true,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
title?: string;
|
||||
href?: string;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
/** وقتی false باشد، دکمه/برچسب «Learn More» روی هاور نمایش داده نمیشود. افکت سهبعدی هاور حفظ میشود. */
|
||||
showPerspective?: boolean;
|
||||
}) => {
|
||||
const [transform, setTransform] = useState(
|
||||
"translate(-50%,-50%) rotateX(0deg)",
|
||||
);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)");
|
||||
};
|
||||
const onMouseLeave = () => {
|
||||
setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)");
|
||||
};
|
||||
|
||||
const wrapperClassName = cn(
|
||||
"group/pin relative z-10",
|
||||
href ? "cursor-pointer" : "cursor-default",
|
||||
containerClassName,
|
||||
);
|
||||
|
||||
const outerTransform = showPerspective
|
||||
? "rotateX(70deg) translateZ(0deg)"
|
||||
: "rotateX(0deg) translateZ(0deg)";
|
||||
|
||||
const content = showPerspective ? (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
transform: outerTransform,
|
||||
}}
|
||||
className="absolute top-1/2 left-1/2 mt-4 ml-[0.09375rem] w-full -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
transform: transform,
|
||||
}}
|
||||
className="absolute top-1/2 left-1/2 flex w-full items-start justify-start overflow-hidden rounded-2xl transition duration-700"
|
||||
>
|
||||
<div className={cn("relative z-10", className)}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
{title != null && <PinPerspective title={title} />}
|
||||
</>
|
||||
) : (
|
||||
<div className={cn("relative z-10", className)}>{children}</div>
|
||||
);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a
|
||||
className={wrapperClassName}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
href={href}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={wrapperClassName}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PinPerspective = ({
|
||||
title,
|
||||
}: {
|
||||
title?: string;
|
||||
href?: string;
|
||||
}) => {
|
||||
return (
|
||||
<motion.div className="pointer-events-none z-20 flex h-80 w-96 items-center justify-center opacity-0 transition duration-500 group-hover/pin:opacity-100">
|
||||
<div className="inset-0 -mt-7 h-full w-full flex-none">
|
||||
<div className="absolute inset-x-0 top-0 flex justify-center">
|
||||
<span className="relative z-10 flex items-center space-x-2 rounded-full bg-foreground px-4 py-0.5 ring-1 ring-border">
|
||||
<span className="text-caption relative z-20 inline-block py-0.5 font-bold text-background">
|
||||
{title}
|
||||
</span>
|
||||
|
||||
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-secondary/0 via-secondary to-secondary/0 transition-opacity duration-500 group-hover/btn:opacity-40"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
perspective: "1000px",
|
||||
transform: "rotateX(70deg) translateZ(0)",
|
||||
}}
|
||||
className="absolute top-1/2 left-1/2 mt-4 ml-[0.09375rem] -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<>
|
||||
<motion.div
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
x: "-50%",
|
||||
y: "-50%",
|
||||
}}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.5, 0],
|
||||
scale: 1,
|
||||
|
||||
z: 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
delay: 0,
|
||||
}}
|
||||
className="absolute top-1/2 left-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-primary/[0.08] shadow-lg"
|
||||
></motion.div>
|
||||
<motion.div
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
x: "-50%",
|
||||
y: "-50%",
|
||||
}}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.5, 0],
|
||||
scale: 1,
|
||||
|
||||
z: 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
delay: 2,
|
||||
}}
|
||||
className="absolute top-1/2 left-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-primary/[0.08] shadow-lg"
|
||||
></motion.div>
|
||||
<motion.div
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scale: 0,
|
||||
x: "-50%",
|
||||
y: "-50%",
|
||||
}}
|
||||
animate={{
|
||||
opacity: [0, 1, 0.5, 0],
|
||||
scale: 1,
|
||||
|
||||
z: 0,
|
||||
}}
|
||||
transition={{
|
||||
duration: 6,
|
||||
repeat: Infinity,
|
||||
delay: 4,
|
||||
}}
|
||||
className="absolute top-1/2 left-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-primary/[0.08] shadow-lg"
|
||||
></motion.div>
|
||||
</>
|
||||
</div>
|
||||
|
||||
<>
|
||||
<motion.div className="absolute right-1/2 bottom-1/2 h-20 w-px translate-y-[14px] bg-gradient-to-b from-transparent to-secondary blur-[2px] group-hover/pin:h-40" />
|
||||
<motion.div className="absolute right-1/2 bottom-1/2 h-20 w-px translate-y-[14px] bg-gradient-to-b from-transparent to-secondary group-hover/pin:h-40" />
|
||||
<motion.div className="absolute right-1/2 bottom-1/2 z-40 h-[4px] w-[4px] translate-x-[1.5px] translate-y-[14px] rounded-full bg-secondary blur-[3px]" />
|
||||
<motion.div className="absolute right-1/2 bottom-1/2 z-40 h-[2px] w-[2px] translate-x-[0.5px] translate-y-[14px] rounded-full bg-secondary" />
|
||||
</>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { AudioLines, Box, Globe, LayoutDashboard, ShieldUser, Users } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { SparklesCore } from "@/components/shadcnblocks/sparkles";
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Globe,
|
||||
title: "یکپارچهسازی آسان",
|
||||
description: "ساختهشده با فناوریهای روز و APIهای تمیز برای ادغام بیدردسر با سامانههای شما.",
|
||||
},
|
||||
{
|
||||
icon: Box,
|
||||
title: "عملکرد سریع",
|
||||
description: "بهینهسازی شده برای بارگذاری فوری و حداقل مصرف منابع.",
|
||||
},
|
||||
{
|
||||
icon: ShieldUser,
|
||||
title: "امنیت پیشفرض",
|
||||
description: "امنیت در سطح سازمان با حفاظت داخلی و رعایت استانداردها.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "همکاری تیمی",
|
||||
description: "طراحیشده برای تیمها با همکاری لحظهای و کنترل دسترسی.",
|
||||
},
|
||||
{
|
||||
icon: AudioLines,
|
||||
title: "یکپارچگی صوتی",
|
||||
description: "پردازش پیشرفته با پشتیبانی از درک زبان طبیعی.",
|
||||
},
|
||||
{
|
||||
icon: LayoutDashboard,
|
||||
title: "امکانات جامع پلتفرم",
|
||||
description: "قیمت لحظهای، مدیریت سفارش و مانده حساب آنلاین، ریز معاملات ۲۴/۷، کنترل وضعیت بازار و پنل ادمین.",
|
||||
},
|
||||
];
|
||||
|
||||
interface Feature294Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Feature294 = ({ className }: Feature294Props) => {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"dark relative w-full max-w-full overflow-x-hidden overflow-y-hidden bg-background py-32 text-foreground",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="relative container mx-auto flex h-full flex-col items-center justify-center px-6" dir="rtl">
|
||||
<div className="w-full max-w-lg text-center">
|
||||
<p className="text-caption relative z-20 font-medium leading-[var(--line-height-body)] tracking-wide text-muted-foreground/80">
|
||||
راهکار
|
||||
</p>
|
||||
<h2 className="text-heading-2 relative z-20 mt-2 text-center font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
یک پلتفرم اختصاصی
|
||||
<br />
|
||||
برای مدیریت کامل معاملات
|
||||
</h2>
|
||||
</div>
|
||||
<div className="relative mt-5 mb-10 h-40 w-[40rem]">
|
||||
{/* Gradients */}
|
||||
<div className="absolute inset-x-20 top-0 h-[2px] w-3/4 bg-gradient-to-r from-transparent via-primary to-transparent blur-sm" />
|
||||
<div className="absolute inset-x-20 top-0 h-px w-3/4 bg-gradient-to-r from-transparent via-primary to-transparent" />
|
||||
<div className="absolute inset-x-60 top-0 h-[5px] w-1/4 bg-gradient-to-r from-transparent via-secondary to-transparent blur-sm" />
|
||||
<div className="absolute inset-x-60 top-0 h-px w-1/4 bg-gradient-to-r from-transparent via-secondary to-transparent" />
|
||||
|
||||
{/* Core component */}
|
||||
<SparklesCore
|
||||
background="transparent"
|
||||
minSize={0.4}
|
||||
maxSize={1}
|
||||
particleDensity={500}
|
||||
className="h-full w-full"
|
||||
particleColor="#FFFFFF"
|
||||
/>
|
||||
|
||||
{/* Radial Gradient to prevent sharp edges */}
|
||||
<div className="absolute inset-0 h-full w-full bg-background [mask-image:radial-gradient(350px_130px_at_top,transparent_20%,white)]"></div>
|
||||
</div>
|
||||
|
||||
<p className="text-body-sm mb-6 text-center font-medium leading-[var(--line-height-body)] text-muted-foreground">
|
||||
ویژگیهای کلیدی:
|
||||
</p>
|
||||
|
||||
<div className="mx-auto grid w-full max-w-5xl grid-cols-1 gap-15 text-right md:grid-cols-2 md:text-start lg:grid-cols-3">
|
||||
{features.map((feature, index) => {
|
||||
const IconComponent = feature.icon;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex min-h-[140px] flex-row-reverse gap-4 rounded-lg border border-border/50 bg-card/30 p-4 transition-colors hover:bg-card/50 dark:border-primary/10 dark:bg-card/20 dark:hover:bg-card/30 md:flex-row"
|
||||
>
|
||||
<IconComponent className="mt-0.5 w-12 shrink-0 stroke-1 text-primary" />
|
||||
<div className="min-w-0 space-y-2">
|
||||
<h4 className="text-heading-4 font-medium leading-[var(--line-height-heading)] text-foreground">{feature.title}</h4>
|
||||
<p className="text-body-sm leading-[var(--line-height-body)] text-muted-foreground">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Feature294 };
|
||||
@@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowRight, ChevronDown } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import type { CarouselApi } from "@/components/ui/carousel";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
} from "@/components/ui/carousel";
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "feature-1",
|
||||
title: "Feature 1",
|
||||
description:
|
||||
"Nam vitae molestie arcu. Quisque eu libero orci. Aliquam imperdiet magna nec massa consectetur, id interdum ante congue.",
|
||||
href: "#",
|
||||
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-1.svg",
|
||||
},
|
||||
{
|
||||
id: "feature-2",
|
||||
title: "Feature 2",
|
||||
description:
|
||||
"Nam vitae molestie arcu. Quisque eu libero orci. Aliquam imperdiet magna nec massa consectetur, id interdum ante congue.",
|
||||
href: "#",
|
||||
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-2.svg",
|
||||
},
|
||||
{
|
||||
id: "feature-3",
|
||||
title: "Feature 3",
|
||||
description:
|
||||
"Nam vitae molestie arcu. Quisque eu libero orci. Aliquam imperdiet magna nec massa consectetur, id interdum ante congue.",
|
||||
href: "#",
|
||||
image: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-3.svg",
|
||||
},
|
||||
];
|
||||
|
||||
interface FeatureCarouselGridProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FeatureCarouselGrid = ({ className }: FeatureCarouselGridProps) => {
|
||||
const [selection, setSelection] = useState(0);
|
||||
const [carouselApi, setCarouselApi] = useState<CarouselApi>();
|
||||
useEffect(() => {
|
||||
if (!carouselApi) {
|
||||
return;
|
||||
}
|
||||
carouselApi.scrollTo(selection);
|
||||
}, [carouselApi, selection]);
|
||||
useEffect(() => {
|
||||
if (!carouselApi) {
|
||||
return;
|
||||
}
|
||||
const updateSelection = () => {
|
||||
setSelection(carouselApi.selectedScrollSnap());
|
||||
};
|
||||
carouselApi.on("select", updateSelection);
|
||||
return () => {
|
||||
carouselApi.off("select", updateSelection);
|
||||
};
|
||||
}, [carouselApi]);
|
||||
return (
|
||||
<section className={cn("py-32", className)}>
|
||||
<div className="container mx-auto">
|
||||
<div className="flex flex-col gap-8 md:flex-row-reverse">
|
||||
<div className="aspect-5/6 overflow-clip rounded-3xl bg-accent">
|
||||
<Carousel
|
||||
setApi={setCarouselApi}
|
||||
className="h-full w-full [&>div]:h-full"
|
||||
>
|
||||
<CarouselContent className="mx-0 h-full w-full">
|
||||
{features.map((feature) => (
|
||||
<CarouselItem key={feature.id} className="px-0">
|
||||
<img
|
||||
src={feature.image}
|
||||
alt={feature.title}
|
||||
className="h-full w-full object-cover object-center"
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
<div className="flex shrink-0 flex-col md:w-1/2 md:pr-8 lg:pr-24 lg:text-left 2xl:pr-32">
|
||||
<h2 className="text-heading-2 mb-6 font-semibold text-pretty">
|
||||
Features
|
||||
</h2>
|
||||
<p className="text-body mb-16 leading-relaxed text-muted-foreground">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Elig
|
||||
doloremque mollitia fugiat omnis! Porro facilis quo animi
|
||||
consequatur. Explicabo.
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{features.map((feature, i) => (
|
||||
<li
|
||||
key={feature.id}
|
||||
className="group relative w-full cursor-pointer px-6 py-3 transition data-open:bg-accent"
|
||||
data-open={selection === i ? "true" : undefined}
|
||||
onClick={() => setSelection(i)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-x-2">
|
||||
<div className="text-body-sm font-semibold text-accent-foreground">
|
||||
{feature.title}
|
||||
</div>
|
||||
<div className="flex size-8 items-center justify-center rounded-full bg-accent text-accent-foreground group-hover:bg-primary group-hover:text-primary-foreground group-data-open:bg-primary group-data-open:text-primary-foreground">
|
||||
<ChevronDown className="size-4 shrink-0 transition-transform duration-200 group-data-open:rotate-180" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-body-sm hidden font-medium group-data-open:block">
|
||||
<p className="my-4 text-muted-foreground lg:my-6">
|
||||
{feature.description}
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
className="group/link text-body-sm flex items-center pb-3 text-accent-foreground"
|
||||
>
|
||||
Learn more{" "}
|
||||
<ArrowRight className="ml-2 size-4 transition-transform group-hover/link:translate-x-1" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureCarouselGrid };
|
||||
@@ -0,0 +1,170 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronUp } from "lucide-react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
import type { CarouselApi } from "@/components/ui/carousel";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
const AUTOPLAY_DELAY_MS = 3200;
|
||||
|
||||
const FeatureCarousel = () => {
|
||||
const content = useContent();
|
||||
const { featureCarousel } = content.pages.home;
|
||||
const carouselItems = featureCarousel.items;
|
||||
|
||||
const [openCardIndex, setOpenCardIndex] = useState<number | null>(null);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [carouselApi, setCarouselApi] = useState<CarouselApi | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||
const handler = () => setIsMobile(mq.matches);
|
||||
handler();
|
||||
mq.addEventListener("change", handler);
|
||||
return () => mq.removeEventListener("change", handler);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMobile || !carouselApi) return;
|
||||
const timer = setInterval(() => {
|
||||
carouselApi.scrollNext();
|
||||
}, AUTOPLAY_DELAY_MS);
|
||||
return () => clearInterval(timer);
|
||||
}, [isMobile, carouselApi]);
|
||||
|
||||
const handleCardClick = (index: number) => {
|
||||
if (!isMobile) return;
|
||||
setOpenCardIndex((prev) => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="rahavarha" className="py-32">
|
||||
<div className="relative container mx-auto max-w-7xl px-6">
|
||||
<h2 className="text-heading-2 z-10 mb-10 text-center font-semibold tracking-tight text-foreground">
|
||||
{featureCarousel.heading}
|
||||
</h2>
|
||||
|
||||
<Carousel
|
||||
opts={{
|
||||
align: "center",
|
||||
loop: true,
|
||||
duration: 20,
|
||||
}}
|
||||
setApi={setCarouselApi}
|
||||
className="w-full"
|
||||
dir="ltr"
|
||||
>
|
||||
<CarouselContent className="py-5">
|
||||
{carouselItems.map((item, index) => (
|
||||
<CarouselItem
|
||||
key={index}
|
||||
className="text-background select-none md:basis-1/2 lg:basis-1/3 dark:text-foreground"
|
||||
>
|
||||
<DottedDiv className="group h-150 w-full mx-auto p-4">
|
||||
<div
|
||||
className={cn(
|
||||
"group/card relative h-full w-full bg-muted/50 p-4 transition-colors duration-300 ease-out group-hover:bg-muted",
|
||||
isMobile && "cursor-pointer"
|
||||
)}
|
||||
data-open={openCardIndex === index}
|
||||
onClick={() => handleCardClick(index)}
|
||||
role={isMobile ? "button" : undefined}
|
||||
aria-expanded={isMobile ? openCardIndex === index : undefined}
|
||||
>
|
||||
<div className="relative h-full w-full overflow-hidden rounded-3xl">
|
||||
<img
|
||||
src={item.imageSrc}
|
||||
alt={item.title}
|
||||
className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-105 group-active:scale-105 md:group-hover:scale-105 md:group-active:scale-105"
|
||||
/>
|
||||
{/* Overlay: hover on desktop, open state on mobile */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 bg-black/0 backdrop-blur-none transition-all duration-300 ease-out",
|
||||
"md:group-hover:bg-black/50 md:group-hover:backdrop-blur-md md:group-active:bg-black/50 md:group-active:backdrop-blur-md",
|
||||
"max-md:group-data-[open=true]/card:bg-black/50 max-md:group-data-[open=true]/card:backdrop-blur-md"
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
|
||||
{/* Text overlay */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-4 -ml-4 flex flex-col items-center justify-between p-6 sm:p-8 opacity-0 translate-y-4 transition-all duration-300 ease-out",
|
||||
"md:group-hover:opacity-100 md:group-hover:translate-y-0 md:group-active:opacity-100 md:group-active:translate-y-0",
|
||||
"max-md:group-data-[open=true]/card:opacity-100 max-md:group-data-[open=true]/card:translate-y-0"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-1 flex-col items-center justify-center pointer-events-none">
|
||||
<h3 className="text-heading-3 text-center font-semibold tracking-tight text-background dark:text-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
<div className="mx-auto mt-2 h-1 w-6 rounded-full bg-background/90 dark:bg-foreground/90" />
|
||||
<p className="text-body-sm mt-6 max-w-sm px-1 text-center leading-relaxed tracking-tight text-background/90 dark:text-foreground/90">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={item.link}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="pointer-events-auto group/cta mb-2 flex cursor-pointer flex-col items-center justify-center transition-transform duration-300 ease-out group-hover/cta:-translate-y-1"
|
||||
>
|
||||
<ChevronUp
|
||||
size={28}
|
||||
className="text-background dark:text-foreground"
|
||||
/>
|
||||
<p className="text-body mt-1 font-medium tracking-tight text-background dark:text-foreground">
|
||||
{featureCarousel.viewDetails}
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DottedDiv>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
|
||||
<div className="mt-5 flex justify-end gap-3">
|
||||
<CarouselPrevious className="static size-12" />
|
||||
<CarouselNext variant="default" className="static size-12" />
|
||||
</div>
|
||||
</Carousel>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureCarousel };
|
||||
|
||||
const DottedDiv = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => (
|
||||
<div className={cn("relative", className)}>
|
||||
<div className="absolute top-4 -left-[28px] h-[1.5px] w-[100%] bg-muted" />
|
||||
<div className="absolute bottom-4 -left-[28px] h-[1.5px] w-[100%] bg-muted" />
|
||||
<div className="absolute -top-25 left-4 h-[130%] w-[1.5px] bg-muted" />
|
||||
<div className="absolute -top-25 right-4 h-[130%] w-[1.5px] bg-muted" />
|
||||
<div className="absolute top-[12.5px] left-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
||||
<div className="absolute top-[12.5px] right-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
||||
<div className="absolute bottom-[12.5px] left-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
||||
<div className="absolute right-[12.5px] bottom-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
ShoppingCart,
|
||||
FileText,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "feature-1",
|
||||
title: "ثبت و مدیریت سفارش ها",
|
||||
description: "ثبت و پیگیری سفارشها",
|
||||
icon: ShoppingCart,
|
||||
},
|
||||
{
|
||||
id: "feature-2",
|
||||
title: "دسترسی کامل به گزارش گیری و معاملات",
|
||||
description: "گزارش گیری کامل و معاملات ۲۴ ساعته",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
id: "feature-3",
|
||||
title: "پنل مدیریت با ابزارهای پیشرفته",
|
||||
description: "ابزارهای پیشرفته برای مدیریت ادمین و کنترل سیستم",
|
||||
icon: Settings,
|
||||
},
|
||||
];
|
||||
|
||||
interface FeatureGridWithIconsProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FeatureGridWithIcons = ({ className }: FeatureGridWithIconsProps) => {
|
||||
return (
|
||||
<section className={cn("py-32", className)}>
|
||||
<div className="container mx-auto flex flex-col items-start gap-8 lg:gap-12 lg:px-16 xl:gap-32">
|
||||
<h2 className="text-heading-2 font-semibold md:shrink-0 lg:max-w-1/3 lg:max-w-3xl">
|
||||
یک پلتفرم اختصاصی برای مدیریت کامل معاملات
|
||||
</h3>
|
||||
<div className="grid w-full gap-6">
|
||||
{features.map((feature) => (
|
||||
<div
|
||||
key={feature.id}
|
||||
className="flex rounded-lg border border-border bg-accent p-6 md:p-8"
|
||||
>
|
||||
<feature.icon className="ml-3 size-6 shrink-0 lg:ml-6 lg:size-7 text-primary" />
|
||||
<div className="flex-1">
|
||||
<h4 className="text-heading-4 mb-2 font-medium text-accent-foreground">
|
||||
{feature.title}
|
||||
</h4>
|
||||
<p className="text-body-sm font-medium text-muted-foreground">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureGridWithIcons };
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
Infinity as InfinityIcon,
|
||||
MessagesSquare,
|
||||
Zap,
|
||||
ZoomIn,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const feature = [
|
||||
{
|
||||
title: "Quality",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi necessitatibus, culpa at vitae molestias tenetur explicabo.",
|
||||
icon: <ZoomIn className="size-6" />,
|
||||
},
|
||||
{
|
||||
title: "Innovation",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi necessitatibus, culpa at vitae molestias tenetur explicabo.",
|
||||
icon: <Zap className="size-6" />,
|
||||
},
|
||||
{
|
||||
title: "Customer Support",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi necessitatibus, culpa at vitae molestias tenetur explicabo.",
|
||||
icon: <MessagesSquare className="size-6" />,
|
||||
},
|
||||
{
|
||||
title: "Reliability",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi necessitatibus, culpa at vitae molestias tenetur explicabo.",
|
||||
icon: <InfinityIcon className="size-6" />,
|
||||
},
|
||||
];
|
||||
|
||||
interface FeatureListSectionProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FeatureListSection = ({ className }: FeatureListSectionProps) => {
|
||||
return (
|
||||
<section className={cn("py-32", className)}>
|
||||
<div className="container mx-auto">
|
||||
<div className="flex w-full flex-col items-center">
|
||||
<div className="flex flex-col items-center space-y-4 text-center sm:space-y-6 md:max-w-3xl md:text-center">
|
||||
<p className="text-caption font-medium text-muted-foreground">WHY WE ARE UNIQUE</p>
|
||||
<h2 className="text-heading-2 font-semibold">
|
||||
Bringing the best to you by the best in the industry
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground md:max-w-2xl">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi
|
||||
necessitatibus, culpa at vitae molestias tenetur explicabo.
|
||||
Voluptatum amet architecto suscipit pariatur eligendi repellendus
|
||||
mollitia dolore unde sint?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto mt-20 grid max-w-5xl gap-6 md:grid-cols-2">
|
||||
{feature.map((feature, idx) => (
|
||||
<div
|
||||
className="flex flex-col justify-between rounded-lg bg-accent p-6 md:min-h-[300px] md:p-8"
|
||||
key={idx}
|
||||
>
|
||||
<span className="mb-6 flex size-11 items-center justify-center rounded-full bg-background">
|
||||
{feature.icon}
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="text-heading-3 font-semibold">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-body-sm mt-2 leading-relaxed text-muted-foreground">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureListSection };
|
||||
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ChartArea,
|
||||
ChartNoAxesCombined,
|
||||
Layout,
|
||||
Medal,
|
||||
Monitor,
|
||||
Target,
|
||||
TrendingUp,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface FeaturePartnershipHighlightsProps {
|
||||
className?: string;
|
||||
/** مسیر تصویر وسط سکشن (مثلاً برای صفحه آبشده همکار) */
|
||||
centerImageSrc?: string;
|
||||
/** متن alt تصویر وسط */
|
||||
centerImageAlt?: string;
|
||||
}
|
||||
|
||||
const defaultCenterImage =
|
||||
"https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-1.svg";
|
||||
|
||||
const FeaturePartnershipHighlights = ({
|
||||
className,
|
||||
centerImageSrc,
|
||||
centerImageAlt = "",
|
||||
}: FeaturePartnershipHighlightsProps) => {
|
||||
const leftCards = [
|
||||
{
|
||||
icon: Target,
|
||||
title: "راهکارهای هدفمند",
|
||||
description:
|
||||
"امکانات متناسب با چالشهای خاص پروژه شما برای رسیدن به نتیجه دقیق.",
|
||||
},
|
||||
{
|
||||
icon: Layout,
|
||||
title: "یکپارچهسازی روان",
|
||||
description:
|
||||
"ادغام بیدردسر با سامانههای موجود برای حفظ تداوم و عملکرد.",
|
||||
},
|
||||
{
|
||||
icon: ChartArea,
|
||||
title: "بینش داده لحظهای",
|
||||
description:
|
||||
"مانیتورینگ داده در زمان واقعی برای تصمیمگیری بهتر و تنظیم سریعتر.",
|
||||
},
|
||||
];
|
||||
|
||||
const rightCards = [
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: "رشد راهبردی",
|
||||
description:
|
||||
"ابزارهای راهبردی برای مقیاسپذیری کسبوکار و پاسخ به نیازهای در حال تحول بازار.",
|
||||
},
|
||||
{
|
||||
icon: ChartNoAxesCombined,
|
||||
title: "تحلیلهای پیشرفته",
|
||||
description:
|
||||
"بینش عمیقتر با تحلیلهای پیشرفته برای پیشتازی در تصمیمگیری.",
|
||||
},
|
||||
{
|
||||
icon: Monitor,
|
||||
title: "مدیریت یکپارچه",
|
||||
description:
|
||||
"مدیریت و کنترل متمرکز همه جنبههای گردش کار از یک پلتفرم.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"border-t border-border/50 bg-background py-16 sm:py-24 md:py-32",
|
||||
className
|
||||
)}
|
||||
dir="rtl"
|
||||
>
|
||||
<div className="container mx-auto flex flex-col items-center px-4 sm:px-6">
|
||||
<div className="mx-auto flex max-w-3xl flex-col items-center gap-4 text-center">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="flex items-center gap-1.5 px-2.5 py-1.5 text-caption font-medium"
|
||||
>
|
||||
<Medal className="h-auto w-4" />
|
||||
برجستهها
|
||||
</Badge>
|
||||
<h2 className="text-heading-2 font-semibold text-foreground">
|
||||
توانایی همکاری خود را آزاد کنید
|
||||
</h2>
|
||||
<p className="text-body text-muted-foreground">
|
||||
مزایای فرآیند سادهشده را تجربه کنید؛ طراحیشده برای بهرهوری و
|
||||
موفقیت بیشتر.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-10 w-full max-w-6xl grid gap-2.5 sm:mt-14 lg:grid-cols-3 lg:gap-4">
|
||||
{/* ستون چپ — موبایل: اول */}
|
||||
<div className="flex flex-col gap-2.5 lg:gap-3">
|
||||
{leftCards.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-3 rounded-lg border border-border bg-card p-4 sm:p-6"
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="flex size-10 shrink-0 items-center justify-center rounded-md bg-muted sm:size-12">
|
||||
<card.icon className="h-5 w-5 sm:h-6 sm:w-6" />
|
||||
</span>
|
||||
<h3 className="text-heading-4 font-medium text-foreground">
|
||||
{card.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-body-sm text-muted-foreground sm:text-body">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* تصویر وسط — فقط دسکتاپ */}
|
||||
<div className="hidden items-center justify-center py-4 lg:flex lg:py-0">
|
||||
<img
|
||||
src={centerImageSrc ?? defaultCenterImage}
|
||||
alt={centerImageAlt}
|
||||
className="h-full w-full rounded-lg object-cover opacity-90"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ستون راست — موبایل: دوم */}
|
||||
<div className="flex flex-col gap-2.5 lg:gap-3">
|
||||
{rightCards.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-3 rounded-lg border border-border bg-card p-4 sm:p-6"
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="flex size-10 shrink-0 items-center justify-center rounded-md bg-muted sm:size-12">
|
||||
<card.icon className="h-5 w-5 sm:h-6 sm:w-6" />
|
||||
</span>
|
||||
<h3 className="text-heading-4 font-medium text-foreground">
|
||||
{card.title}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-body-sm text-muted-foreground sm:text-body">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeaturePartnershipHighlights };
|
||||
@@ -0,0 +1,105 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { Circle } from "lucide-react";
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface FeatureProductDetailShowcaseProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const productDetails = {
|
||||
brand: "مستر دکور",
|
||||
name: "ویترین طلا",
|
||||
description:
|
||||
"نمایش حرفه ای محصولات طلا با تجربه کاربری روان، تمرکز بر اعتماد مشتری و مدیریت یکپارچه در یک صفحه محصول کامل.",
|
||||
image: "/assets/main-pic/product/sampleghaleb.png",
|
||||
features: [
|
||||
"نمایش شفاف اطلاعات محصول با ساختار خوانا",
|
||||
"هماهنگ با رابط کاربری مدرن و واکنش گرا",
|
||||
"تمرکز بر تجربه مشتری در تصمیم خرید",
|
||||
"آماده اتصال به جریان های فروش و مشاوره",
|
||||
],
|
||||
info: [
|
||||
{
|
||||
title: "معرفی محصول",
|
||||
content:
|
||||
"این قالب برای نمایش یک محصول به صورت کامل طراحی شده است تا مخاطب در همان نگاه اول، تصویر دقیق، ویژگی ها و ارزش پیشنهادی محصول را درک کند.",
|
||||
},
|
||||
{
|
||||
title: "مخاطب هدف",
|
||||
content:
|
||||
"مناسب فروشگاه ها و کسب وکارهای فعال در بازار طلا که به دنبال نمایش حرفه ای محصولات و افزایش اعتماد کاربران در صفحه محصول هستند.",
|
||||
},
|
||||
{
|
||||
title: "مزیت برای کسب وکار",
|
||||
content:
|
||||
"ساختار ساده و تمیز این بخش باعث می شود تمرکز کاربر روی ارزش محصول باقی بماند و فرآیند آشنایی تا اقدام کاربر کوتاه تر شود.",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const FeatureProductDetailShowcase = ({
|
||||
className,
|
||||
}: FeatureProductDetailShowcaseProps) => {
|
||||
return (
|
||||
<section className={cn("py-20 md:py-28", className)} dir="rtl">
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
|
||||
<div className="grid gap-8 rounded-3xl border border-border bg-card p-4 md:p-6 lg:grid-cols-2 lg:gap-12 lg:p-8">
|
||||
<div className="overflow-hidden rounded-2xl border border-border bg-muted/40">
|
||||
<Image
|
||||
src={productDetails.image}
|
||||
alt={productDetails.name}
|
||||
width={1200}
|
||||
height={900}
|
||||
className="h-full max-h-[560px] w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<p className="text-caption font-medium text-muted-foreground">
|
||||
{productDetails.brand}
|
||||
</p>
|
||||
<h2 className="mt-2 text-heading-2 font-semibold text-foreground">
|
||||
{productDetails.name}
|
||||
</h2>
|
||||
<p className="mt-4 text-body text-muted-foreground">
|
||||
{productDetails.description}
|
||||
</p>
|
||||
|
||||
<ul className="mt-6 space-y-2">
|
||||
{productDetails.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-3 text-body-sm text-foreground">
|
||||
<Circle className="mt-1 h-2.5 w-2.5 fill-foreground stroke-foreground" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Accordion type="multiple" className="mt-8 w-full border-t border-border">
|
||||
{productDetails.info.map((item) => (
|
||||
<AccordionItem key={item.title} value={item.title}>
|
||||
<AccordionTrigger className="text-heading-4 font-medium text-foreground hover:no-underline">
|
||||
{item.title}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="text-body-sm text-muted-foreground">
|
||||
{item.content}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureProductDetailShowcase };
|
||||
@@ -0,0 +1,28 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface FeatureTextSectionProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FeatureTextSection = ({ className }: FeatureTextSectionProps) => {
|
||||
return (
|
||||
<section className={cn("py-32", className)}>
|
||||
<div className="container mx-auto">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<h2 className="text-heading-2 my-6 font-semibold text-pretty lg:max-w-3xl">
|
||||
روش سنتی، سرعت بازار امروز را ندارد
|
||||
</h2>
|
||||
<p className="text-body mb-8 leading-relaxed text-muted-foreground lg:max-w-3xl">
|
||||
در معاملات آبشده، تماسهای مکرر برای دریافت قیمت، هماهنگی خرید و فروش و ارسال مانده حساب به مشتریان، زمانبر و پرریسک است.
|
||||
ثبت دستی یا پراکنده اطلاعات، احتمال اختلاف حساب و سوءتفاهم را افزایش میدهد.
|
||||
</p>
|
||||
<Button variant="outline">بازاری که بر سرعت و دقت استوار است، نیاز به زیرساخت دقیق دارد.</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureTextSection };
|
||||
@@ -0,0 +1,149 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { ScrollableTabsList } from "@/components/shadcnblocks/scrollable-tabslist";
|
||||
import { ScrollBar } from "@/components/ui/scroll-area";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface FeatureWorkspaceTabsProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
value: "display",
|
||||
label: "نمایش حرفهای",
|
||||
eyebrow: "ویترین و استند",
|
||||
title: "ویترینی بسازید که فروش را سریع تر می کند",
|
||||
description:
|
||||
"چیدمان اصولی استندها باعث می شود سرویس های طلا و جواهر بهتر دیده شوند، تصمیم خرید سریع تر گرفته شود ",
|
||||
image: "/assets/main-pic/feature/feature1.webp",
|
||||
stats: [
|
||||
{ value: "۳۵۰+", label: "پروژه اجراشده در فروشگاه" },
|
||||
{ value: "۱۵+", label: "سال تجربه در دکور طلافروشی" },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: "identity",
|
||||
label: "هماهنگ با برند",
|
||||
eyebrow: "دکور مجموعه شما",
|
||||
title: "دکور هماهنگ با هویت برند طلافروشی شما",
|
||||
description:
|
||||
"روکش، متریال و جزئیات اجرایی را بر اساس سبک فروشگاه شما انتخاب می کنیم تا هر استند بخشی از هویت برندتان باشد و در ذهن مشتری ماندگار بماند.",
|
||||
image: "/assets/main-pic/feature/feature1.webp",
|
||||
stats: [
|
||||
{ value: "۹۳٪", label: "رضایت مشتریان در نظرسنجیها" },
|
||||
{ value: "۴۸ ساعت", label: "شروع پشتیبانی پس از تحویل" },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: "lifecycle",
|
||||
label: "از مشاوره تا پس از فروش",
|
||||
eyebrow: "همراهی مستر دکور",
|
||||
title: "از مشاوره تا نصب، یک مسیر شفاف و قابل اتکا",
|
||||
description:
|
||||
"از بازدید و نیازسنجی تا تولید و نصب، برنامه زمان بندی مشخص ارائه می کنیم و بعد از تحویل نیز با خدمات پس از فروش کنار شما می مانیم .",
|
||||
image: "/assets/main-pic/feature/feature1.webp",
|
||||
stats: [
|
||||
{ value: "۱۰۰٪", label: "هماهنگی با ابعاد واقعی فضا" },
|
||||
{ value: "گارانتی", label: "تعهد کیفی روی اجرای سازه" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const FeatureWorkspaceTabs = ({ className }: FeatureWorkspaceTabsProps) => {
|
||||
const [activeTab, setActiveTab] = useState("display");
|
||||
const activeItem = useMemo(
|
||||
() => tabItems.find((item) => item.value === activeTab) ?? tabItems[0],
|
||||
[activeTab],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className={cn("section-blur-separator py-20 md:py-28", className)} dir="rtl">
|
||||
<div className="mx-auto flex w-full max-w-7xl justify-center px-4 sm:px-6">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
defaultValue="display"
|
||||
className="mx-auto grid w-full max-w-6xl grid-cols-1 gap-8 lg:grid-cols-2 lg:items-center xl:gap-20"
|
||||
>
|
||||
<div className="flex flex-col-reverse items-center justify-between gap-8 text-center lg:flex-col lg:text-center">
|
||||
<div>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={activeItem.value}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
transition={{ duration: 0.35, ease: "easeOut" }}
|
||||
>
|
||||
<TabsContent
|
||||
value={activeItem.value}
|
||||
forceMount
|
||||
className="mt-0 flex flex-col items-center gap-6"
|
||||
>
|
||||
<span className="text-caption font-medium text-muted-foreground">
|
||||
{activeItem.eyebrow}
|
||||
</span>
|
||||
<div className="flex max-w-xl flex-col gap-4">
|
||||
<h2 className="text-heading-2 font-semibold text-foreground">
|
||||
{activeItem.title}
|
||||
</h2>
|
||||
<div className="tone-gradient-line" aria-hidden />
|
||||
<p className="text-body text-muted-foreground">{activeItem.description}</p>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<ScrollableTabsList className="w-full">
|
||||
<TabsList className="mx-auto h-12 overflow-y-hidden rounded-full">
|
||||
{tabItems.map((item) => (
|
||||
<TabsTrigger
|
||||
key={item.value}
|
||||
value={item.value}
|
||||
className="h-full rounded-full px-4 py-2 transition-all duration-300"
|
||||
>
|
||||
{item.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<ScrollBar orientation="horizontal" className="h-2" />
|
||||
</ScrollableTabsList>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={activeItem.value}
|
||||
initial={{ opacity: 0, scale: 0.985 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 1.01 }}
|
||||
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||
>
|
||||
<TabsContent value={activeItem.value} forceMount className="mt-0">
|
||||
<div className="relative overflow-hidden rounded-3xl">
|
||||
<Image
|
||||
src={activeItem.image}
|
||||
alt={activeItem.title}
|
||||
width={1200}
|
||||
height={900}
|
||||
className="h-[440px] w-full object-cover lg:h-[540px]"
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { FeatureWorkspaceTabs };
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardHeader } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Settings2, Sparkles, Zap } from "lucide-react";
|
||||
import { ReactNode } from "react";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
const featureIcons = [Zap, Settings2, Sparkles];
|
||||
|
||||
export default function FeaturesCardGrid() {
|
||||
const content = useContent();
|
||||
const { featuresCardGrid } = content.pages.home;
|
||||
const features = featuresCardGrid.items.map((item, i) => ({
|
||||
...item,
|
||||
icon: featureIcons[i] ?? Zap,
|
||||
}));
|
||||
|
||||
return (
|
||||
<section
|
||||
className="py-16 md:py-24 lg:py-32 border-t border-border/50"
|
||||
aria-labelledby="features-heading"
|
||||
>
|
||||
<div className="@container mx-auto max-w-5xl px-6" dir="rtl">
|
||||
<div className="text-center">
|
||||
<h2
|
||||
id="features-heading"
|
||||
className="text-heading-2 font-semibold leading-tight text-foreground"
|
||||
>
|
||||
{featuresCardGrid.heading}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="@min-4xl:max-w-full @min-4xl:grid-cols-3 mx-auto mt-10 grid max-w-sm gap-6 md:mt-14 lg:gap-8">
|
||||
{features.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Card
|
||||
key={item.title}
|
||||
className="group border-border/80 bg-card text-center shadow-sm transition-shadow hover:shadow-md hover:border-primary/20"
|
||||
>
|
||||
<CardHeader className="pb-4">
|
||||
<CardDecorator>
|
||||
<Icon
|
||||
className="size-6 text-primary"
|
||||
aria-hidden
|
||||
/>
|
||||
</CardDecorator>
|
||||
<h3 className="mt-5 text-heading-5 font-medium text-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-body-sm leading-relaxed text-muted-foreground">
|
||||
{item.description}
|
||||
</p>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const CardDecorator = ({ children }: { children: ReactNode }) => (
|
||||
<div className="mask-radial-from-40% mask-radial-to-60% relative mx-auto size-36 duration-200 [--color-border:color-mix(in_oklab,var(--color-primary)_0.12,transparent)] group-hover:[--color-border:color-mix(in_oklab,var(--color-primary)_0.2,transparent)]">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 bg-[linear-gradient(to_right,var(--color-border)_1px,transparent_1px),linear-gradient(to_bottom,var(--color-border)_1px,transparent_1px)] bg-[size:24px_24px] opacity-80"
|
||||
/>
|
||||
<div className="bg-card border-border/60 absolute inset-0 m-auto flex size-12 items-center justify-center rounded-lg border shadow-sm">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import { Target, CheckCircle, Shield, Briefcase, Headphones, TrendingUp } from 'lucide-react'
|
||||
import { useContent } from '@/components/layout/content-provider'
|
||||
|
||||
const icons = [Target, CheckCircle, Shield, Briefcase, Headphones, TrendingUp]
|
||||
|
||||
export default function FeaturesIconGrid() {
|
||||
const content = useContent()
|
||||
const { featuresIconGrid } = content.pages.home
|
||||
|
||||
return (
|
||||
<section className="py-12 md:py-16 lg:py-20 border-t border-border/50">
|
||||
<div className="mx-auto max-w-5xl space-y-6 px-6 md:space-y-10">
|
||||
<div className="relative z-10 mx-auto max-w-xl space-y-4 text-center md:space-y-6">
|
||||
<h2 className="text-heading-2 text-balance font-semibold">{featuresIconGrid.heading}</h2>
|
||||
<p className="text-body leading-relaxed text-muted-foreground">{featuresIconGrid.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto grid max-w-4xl divide-x divide-y border border-border dark:border-primary/20 dark:divide-primary/10 *:p-12 sm:grid-cols-2 lg:grid-cols-3 *:transition-colors *:hover:bg-primary/5 dark:*:hover:bg-primary/5">
|
||||
{featuresIconGrid.items.map((item, index) => {
|
||||
const Icon = icons[index] ?? Target
|
||||
return (
|
||||
<div key={index} className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="size-4 text-primary dark:text-primary" />
|
||||
<h4 className="text-heading-5 font-medium">{item}</h4>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function FeaturesPrimary() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="relative py-12 md:py-16 lg:py-20">
|
||||
<div className="relative z-0 mx-auto max-w-5xl space-y-6 px-6 md:space-y-10 lg:space-y-12 dark:[--color-border:color-mix(in_oklab,var(--color-white)_10%,transparent)]">
|
||||
<div className="relative z-10 mx-auto max-w-2xl space-y-4 text-center">
|
||||
<h2 className="text-heading-2 font-semibold leading-tight">
|
||||
ما چه میکنیم؟
|
||||
</h2>
|
||||
<p className="text-body leading-relaxed text-muted-foreground">ما بهصورت تخصصی برای فعالان بازار طلا و فلزات گرانبها نرمافزار طراحی میکنیم.محصولات ما بر اساس نیاز عملیاتی این بازار توسعه یافتهاند، نه صرفاً بر اساس الگوهای عمومی نرمافزاری. سامانههای ما در حال حاضر توسط مجموعههای فعال بازار مورد استفاده قرار میگیرند و در شرایط واقعی معامله آزموده شدهاند.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type ProductCategory = {
|
||||
title: string;
|
||||
image: {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
summary: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
type ProductCategories2Props = {
|
||||
title?: string;
|
||||
productCategories?: ProductCategory[];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const PRODUCT_CATEGORIES = {
|
||||
title: "ما چه خدماتی ارائه میدیم؟",
|
||||
productCategories: [
|
||||
{
|
||||
title: "ویترین طلا",
|
||||
summary: "استندها و ملزوماتی برای نمایش لوکس سرویس های طلا با چیدمان حرفه ای",
|
||||
image: {
|
||||
src: "/assets/main-pic/service 1.png",
|
||||
alt: "",
|
||||
},
|
||||
link: "/product?category=vitrin-tala",
|
||||
},
|
||||
{
|
||||
title: "ویترین خردسال",
|
||||
summary: "چیدمان هدفمند برای نمایش امن و منظم محصولات خردسال",
|
||||
image: {
|
||||
src: "/assets/main-pic/service 2.jpeg",
|
||||
alt: "",
|
||||
},
|
||||
link: "/product?category=vitrin-khorde-sal",
|
||||
},
|
||||
{
|
||||
title: "ویترین جواهر",
|
||||
summary: "طراحی ویترین جواهر با تاکید بر درخشش، جزئیات و تجربه بصری مشتری",
|
||||
image: {
|
||||
src: "/assets/main-pic/service 3.jpeg",
|
||||
alt: "",
|
||||
},
|
||||
link: "/product?category=vitrin-javaher",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const ProductCategories2 = ({
|
||||
title = PRODUCT_CATEGORIES.title,
|
||||
productCategories = PRODUCT_CATEGORIES.productCategories,
|
||||
className,
|
||||
}: ProductCategories2Props) => {
|
||||
return (
|
||||
<section className={cn("py-20 md:py-24 lg:py-28", className)}>
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
|
||||
<div className="flex flex-col gap-10">
|
||||
<h2 className="text-heading-2 animate-in text-center font-semibold text-foreground duration-600 fade-in slide-in-from-bottom-6">
|
||||
{title}
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-x-2.5 gap-y-10 lg:grid-cols-3">
|
||||
{productCategories?.map((item, index) => (
|
||||
<div
|
||||
key={item.title}
|
||||
style={{
|
||||
animationDelay: `${100 * index}ms`,
|
||||
}}
|
||||
className="animate-out opacity-0 duration-700 fade-in-100 fill-mode-forwards"
|
||||
>
|
||||
<ProductCategoryCard {...item} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const ProductCategoryCard = ({
|
||||
title,
|
||||
image,
|
||||
summary,
|
||||
link,
|
||||
}: ProductCategory) => {
|
||||
return (
|
||||
<div className="relative isolate h-full">
|
||||
<div className="relative h-full overflow-hidden rounded-2xl border border-primary/30 bg-overlay/25 shadow-2xl backdrop-blur-2xl backdrop-saturate-150">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -right-10 -top-10 z-0 h-36 w-36 rounded-full bg-primary/8 blur-3xl sm:h-44 sm:w-44"
|
||||
/>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -bottom-10 -left-10 z-0 h-36 w-36 rounded-full bg-primary/8 blur-3xl sm:h-44 sm:w-44"
|
||||
/>
|
||||
<div className="relative z-10">
|
||||
<Link href={link} className="group/card flex flex-col gap-0">
|
||||
<div className="relative aspect-[1.363277259] w-full overflow-hidden rounded-t-2xl">
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="absolute inset-0 h-full w-full origin-center object-cover object-center transition-transform duration-300 ease-out group-hover/card:scale-110 motion-reduce:transition-none motion-reduce:group-hover/card:scale-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1 px-3 py-4 sm:px-4 sm:pb-5">
|
||||
<h3 className="text-heading-4 text-center font-medium text-gold-300">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-body-sm text-center text-muted-foreground">
|
||||
{summary}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProductCategories2 };
|
||||
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "@/components/ui/carousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface ProductDetailGalleryProps {
|
||||
images: { src: string; alt: string }[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/** موبایل: کاروسل با زوم هاور. دسکتاپ: تصویر بزرگ + ردیف بندانگشتی؛ کلیک بندانگشتی → جابهجایی به نمای بزرگ */
|
||||
export function ProductDetailGallery({ images, className }: ProductDetailGalleryProps) {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
|
||||
if (!images.length) return null;
|
||||
|
||||
const safeIndex = Math.min(activeIndex, images.length - 1);
|
||||
const main = images[safeIndex] ?? images[0]!;
|
||||
|
||||
const zoomImageClass =
|
||||
"object-cover object-center transition-transform duration-500 ease-out motion-safe:group-hover:scale-[1.04]";
|
||||
|
||||
return (
|
||||
<div className={cn("w-full min-w-0", className)} dir="rtl">
|
||||
{/* موبایل: کاروسل */}
|
||||
<div className="md:hidden">
|
||||
<Carousel
|
||||
opts={{
|
||||
breakpoints: {
|
||||
"(min-width: 768px)": {
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CarouselContent className="gap-4">
|
||||
{images.map((img, index) => (
|
||||
<CarouselItem className="md:p-0" key={`${img.src}-m-${index}`}>
|
||||
<div className="group relative aspect-square overflow-hidden rounded-lg bg-muted/40">
|
||||
<Image
|
||||
src={img.src}
|
||||
alt={img.alt}
|
||||
fill
|
||||
sizes="100vw"
|
||||
className={zoomImageClass}
|
||||
priority={index === 0}
|
||||
/>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
<div className="md:hidden">
|
||||
<CarouselPrevious className="start-4" />
|
||||
<CarouselNext className="end-4" />
|
||||
</div>
|
||||
</Carousel>
|
||||
</div>
|
||||
|
||||
{/* دسکتاپ: بزرگ + بندانگشتیها */}
|
||||
<div className="hidden flex-col gap-4 md:flex xl:gap-5">
|
||||
<div className="group relative aspect-square w-full overflow-hidden rounded-lg bg-muted/40">
|
||||
<Image
|
||||
src={main.src}
|
||||
alt={main.alt}
|
||||
fill
|
||||
sizes="(min-width: 1280px) 40vw, (min-width: 768px) 45vw, 50vw"
|
||||
className={zoomImageClass}
|
||||
priority={safeIndex === 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{images.length > 1 ? (
|
||||
<div className="grid grid-cols-3 gap-4 xl:gap-5">
|
||||
{images.map((img, index) => {
|
||||
if (index === safeIndex) return null;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={`thumb-${img.src}-${index}`}
|
||||
onClick={() => setActiveIndex(index)}
|
||||
className={cn(
|
||||
"group/thumb relative aspect-square w-full overflow-hidden rounded-lg bg-muted",
|
||||
"ring-offset-2 ring-offset-background transition-[box-shadow,transform]",
|
||||
"hover:ring-2 hover:ring-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
||||
"active:scale-[0.98]",
|
||||
)}
|
||||
aria-label={`نمایش تصویر ${index + 1} در اندازه بزرگ`}
|
||||
>
|
||||
<Image
|
||||
src={img.src}
|
||||
alt=""
|
||||
fill
|
||||
sizes="(min-width: 1280px) 14vw, 18vw"
|
||||
className="object-cover object-center transition-transform duration-500 ease-out motion-safe:group-hover/thumb:scale-[1.03]"
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import type { ProductDetail } from "@/lib/catalog-products";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface ProductDetailSpecsProps {
|
||||
product: ProductDetail;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* بلوک توضیحات محصول: نام، رنگ، متریال، توضیح و ویژگیها
|
||||
* (الهام از product-detail2 بدون قیمت، فرم، تعداد و دکمه خرید)
|
||||
*/
|
||||
export function ProductDetailSpecs({ product, className }: ProductDetailSpecsProps) {
|
||||
return (
|
||||
<div className={cn("flex min-w-0 flex-col gap-5", className)} dir="rtl">
|
||||
<h1 className="text-heading-1 font-bold text-2xl text-foreground">{product.name}</h1>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-caption font-medium text-muted-foreground">رنگ</span>
|
||||
<span className="text-body text-foreground">{product.color}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-caption font-medium text-muted-foreground">متریال</span>
|
||||
<span className="text-body text-foreground">{product.material}</span>
|
||||
</div>
|
||||
|
||||
<p className="text-body text-muted-foreground leading-relaxed">{product.description}</p>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-heading-2 font-semibold text-foreground text-xl">ویژگیهای محصول</h2>
|
||||
<Accordion type="multiple" className="w-full">
|
||||
{product.features.map((item, index) => (
|
||||
<AccordionItem key={`${item.title}-${index}`} value={`feature-${index}`}>
|
||||
<AccordionTrigger className="text-start hover:no-underline">
|
||||
<h3 className="text-heading-4 font-medium text-foreground text-lg">{item.title}</h3>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<p className="text-body text-muted-foreground leading-relaxed">{item.content}</p>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import type { ProductDetail } from "@/lib/catalog-products";
|
||||
import { ProductDetailGallery } from "@/components/sections/feature/product-detail-gallery";
|
||||
import { ProductDetailSpecs } from "@/components/sections/feature/product-detail-specs";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface ProductDetailViewProps {
|
||||
product: ProductDetail;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ProductDetailView({ product, className }: ProductDetailViewProps) {
|
||||
return (
|
||||
<section className={cn("py-16 md:py-24 lg:py-28", className)} dir="rtl">
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2 lg:gap-12 lg:items-start">
|
||||
<ProductDetailGallery key={product.id} images={product.gallery} />
|
||||
<div className="lg:sticky lg:top-24">
|
||||
<ProductDetailSpecs product={product} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Eye } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import type { ProductCategory, ProductItem } from "@/lib/catalog-products";
|
||||
import { CATALOG_PRODUCTS } from "@/lib/catalog-products";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface CategorySectionMeta {
|
||||
category: ProductCategory;
|
||||
categoryHeading: string;
|
||||
promo: {
|
||||
kicker: string;
|
||||
title: string;
|
||||
ctaLabel: string;
|
||||
href: string;
|
||||
image: string;
|
||||
imageAlt: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ProductList10CatalogProps {
|
||||
initialCategory?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const CATEGORY_OPTIONS: Array<{ value: ProductCategory; label: string }> = [
|
||||
{ value: "vitrin-tala", label: "ویترین طلا" },
|
||||
{ value: "vitrin-khorde-sal", label: "ویترین خردسال" },
|
||||
{ value: "vitrin-javaher", label: "ویترین جواهر" },
|
||||
{ value: "mankan-service", label: "مانکن سرویس" },
|
||||
{ value: "mankan-ring", label: "مانکن انگشتر" },
|
||||
{ value: "mankan-earring", label: "مانکن گوشواره" },
|
||||
];
|
||||
|
||||
const CATEGORY_SECTIONS: CategorySectionMeta[] = [
|
||||
{
|
||||
category: "vitrin-tala",
|
||||
categoryHeading: "ویترین طلا",
|
||||
promo: {
|
||||
kicker: "خط ویترین طلا",
|
||||
title: "استندها و ویترین هایی برای نمایش حرفه ای سرویس های طلا با چیدمان لوکس و منظم.",
|
||||
ctaLabel: "مشاهده این دسته",
|
||||
href: "/product?category=vitrin-tala",
|
||||
image: "/assets/main-pic/product/card-header/محصول 1.jpg",
|
||||
imageAlt: "نمونه ویترین طلا",
|
||||
},
|
||||
},
|
||||
{
|
||||
category: "vitrin-khorde-sal",
|
||||
categoryHeading: "ویترین خردسال",
|
||||
promo: {
|
||||
kicker: "ویترین خردسال",
|
||||
title: "چیدمان امن، کاربردی و چشم نواز برای بخش خردسال.",
|
||||
ctaLabel: "مشاهده این دسته",
|
||||
href: "/product?category=vitrin-khorde-sal",
|
||||
image: "/assets/main-pic/product/card-header/محصول 2.jpg",
|
||||
imageAlt: "نمونه ویترین خردسال",
|
||||
},
|
||||
},
|
||||
{
|
||||
category: "vitrin-javaher",
|
||||
categoryHeading: "ویترین جواهر",
|
||||
promo: {
|
||||
kicker: "ویترین جواهر",
|
||||
title: "نمایش جواهر با تاکید بر درخشش سنگ ها و ظرافت جزئیات.",
|
||||
ctaLabel: "مشاهده این دسته",
|
||||
href: "/product?category=vitrin-javaher",
|
||||
image: "/assets/main-pic/product/card-header/محصول 3.jpg",
|
||||
imageAlt: "نمونه ویترین جواهر",
|
||||
},
|
||||
},
|
||||
{
|
||||
category: "mankan-service",
|
||||
categoryHeading: "مانکن سرویس",
|
||||
promo: {
|
||||
kicker: "مانکن سرویس",
|
||||
title: "مانکن های تخصصی برای ارائه کامل سرویس و افزایش جذابیت ویترین.",
|
||||
ctaLabel: "مشاهده این دسته",
|
||||
href: "/product?category=mankan-service",
|
||||
image: "/assets/main-pic/product/card-header/2محصول 4.jpg",
|
||||
imageAlt: "نمونه مانکن سرویس",
|
||||
},
|
||||
},
|
||||
{
|
||||
category: "mankan-ring",
|
||||
categoryHeading: "مانکن انگشتر",
|
||||
promo: {
|
||||
kicker: "مانکن انگشتر",
|
||||
title: "مانکن انگشتر با زاویه دید درست برای نمایش بهتر نگین و فرم رکاب.",
|
||||
ctaLabel: "مشاهده این دسته",
|
||||
href: "/product?category=mankan-ring",
|
||||
image: "/assets/main-pic/product/card-header/محصول 2.jpg",
|
||||
imageAlt: "نمونه مانکن انگشتر",
|
||||
},
|
||||
},
|
||||
{
|
||||
category: "mankan-earring",
|
||||
categoryHeading: "مانکن گوشواره",
|
||||
promo: {
|
||||
kicker: "مانکن گوشواره",
|
||||
title: "مانکن گوشواره برای نمایش واضح تر مدل ها در ویترین های پرتعداد.",
|
||||
ctaLabel: "مشاهده این دسته",
|
||||
href: "/product?category=mankan-earring",
|
||||
image: "/assets/main-pic/product/card-header/محصول 1.jpg",
|
||||
imageAlt: "نمونه مانکن گوشواره",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const PRODUCTS: ProductItem[] = CATALOG_PRODUCTS;
|
||||
|
||||
/** هر ۶ محصول = دو ردیف × سه ستون در گرید ۵ ستونه کنار پروموی ۲×۲ */
|
||||
const PRODUCTS_PER_PROMO_CHUNK = 6;
|
||||
|
||||
function chunkItems<T>(items: T[], chunkSize: number): T[][] {
|
||||
if (chunkSize <= 0) return items.length ? [items] : [];
|
||||
const chunks: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += chunkSize) {
|
||||
chunks.push(items.slice(i, i + chunkSize));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function CatalogProductCard({ product }: { product: ProductItem }) {
|
||||
const href = `/product/${product.id}`;
|
||||
const quickViewHref = href;
|
||||
|
||||
return (
|
||||
<Card
|
||||
id={`p-${product.id}`}
|
||||
className="group relative block scroll-mt-28 rounded-none border-none bg-background p-0 shadow-none"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="relative overflow-hidden">
|
||||
<AspectRatio ratio={1} className="overflow-hidden rounded-xl border border-border bg-muted/30">
|
||||
<Image
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
fill
|
||||
sizes="(min-width: 1280px) 22vw, (min-width: 768px) 25vw, 50vw"
|
||||
className="object-cover object-center transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</AspectRatio>
|
||||
<div className="absolute inset-x-5 top-1/2 z-[60] hidden -translate-y-1/2 md:block">
|
||||
<div className="flex flex-col gap-2 opacity-0 duration-700 lg:translate-y-4 lg:group-hover:translate-y-0 lg:group-hover:opacity-100">
|
||||
<Button size="lg" variant="primary" className="relative z-20 w-full" asChild>
|
||||
<Link href={quickViewHref}>
|
||||
<Eye className="size-4 shrink-0" aria-hidden />
|
||||
<span>مشاهده سریع</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
href={href}
|
||||
className="relative z-10 flex flex-col gap-1 pt-3 underline-offset-4 transition-colors hover:text-primary"
|
||||
aria-label={product.name}
|
||||
>
|
||||
<span className="text-caption font-medium text-primary">{product.categoryLabel}</span>
|
||||
<h3 className="text-heading-5 text-sm text-foreground group-hover:text-primary group-hover:underline">
|
||||
{product.name}
|
||||
</h3>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function FeaturedPromotionCard({
|
||||
className,
|
||||
categoryHeading,
|
||||
kicker,
|
||||
title,
|
||||
ctaLabel,
|
||||
href,
|
||||
image,
|
||||
imageAlt,
|
||||
}: CategorySectionMeta["promo"] & { categoryHeading: string; className?: string }) {
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
"relative flex w-full min-w-0 flex-col overflow-hidden rounded-xl border-none bg-muted bg-cover bg-center bg-no-repeat p-0 shadow-none",
|
||||
"lg:h-full lg:min-h-0",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt}
|
||||
fill
|
||||
className="object-cover object-center"
|
||||
sizes="(min-width: 1024px) 33vw, 100vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-background/95 via-background/55 to-transparent" />
|
||||
<CardHeader className="relative z-20 flex h-full min-h-0 flex-1 flex-col justify-end gap-2.5 p-10">
|
||||
<h2 className="text-heading-2 font-semibold text-foreground">{categoryHeading}</h2>
|
||||
<p className="text-caption font-medium text-primary">{kicker}</p>
|
||||
<p className="text-heading-3 font-semibold text-foreground">{title}</p>
|
||||
<div className="relative z-20 mt-5">
|
||||
<Button size="lg" variant="primary" asChild>
|
||||
<Link href={href}>{ctaLabel}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProductList10Catalog({ initialCategory, className }: ProductList10CatalogProps) {
|
||||
const validInitialCategory = CATEGORY_OPTIONS.some((item) => item.value === initialCategory)
|
||||
? (initialCategory as ProductCategory)
|
||||
: undefined;
|
||||
|
||||
const [selectedCategories, setSelectedCategories] = useState<ProductCategory[]>(
|
||||
validInitialCategory ? [validInitialCategory] : [],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (validInitialCategory) {
|
||||
setSelectedCategories([validInitialCategory]);
|
||||
return;
|
||||
}
|
||||
setSelectedCategories([]);
|
||||
}, [validInitialCategory]);
|
||||
|
||||
const toggleCategory = (value: ProductCategory) => {
|
||||
setSelectedCategories((prev) =>
|
||||
prev.includes(value) ? prev.filter((item) => item !== value) : [...prev, value],
|
||||
);
|
||||
};
|
||||
|
||||
const visibleSections = useMemo(() => {
|
||||
return CATEGORY_SECTIONS.map((section) => {
|
||||
const items = PRODUCTS.filter((p) => {
|
||||
if (p.category !== section.category) return false;
|
||||
if (selectedCategories.length === 0) return true;
|
||||
return selectedCategories.includes(p.category);
|
||||
});
|
||||
return { ...section, items };
|
||||
}).filter((s) => s.items.length > 0);
|
||||
}, [selectedCategories]);
|
||||
|
||||
return (
|
||||
<section className={cn("py-16 md:py-24 lg:py-28", className)} dir="rtl">
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
|
||||
<div className="mb-10 text-center md:mb-14">
|
||||
<h1 className="text-heading-1 font-bold text-foreground">محصولات</h1>
|
||||
<p className="mx-auto mt-4 max-w-3xl text-body text-muted-foreground">
|
||||
مجموعه استندها و ملزومات دکور ویترین طلافروشی را بر اساس دسته بندی بررسی کنید و متناسب با نیاز فروشگاه خود،
|
||||
بهترین گزینه را برای نمایش حرفه ای سرویس های طلا و جواهر انتخاب کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="border-border bg-card/40 rounded-2xl border p-4 sm:p-5"
|
||||
role="region"
|
||||
aria-label="فیلتر دستهبندی محصولات"
|
||||
>
|
||||
<div
|
||||
className="flex gap-2 overflow-x-auto pb-1 [-ms-overflow-style:none] [scrollbar-width:none] sm:flex-wrap sm:justify-center [&::-webkit-scrollbar]:hidden"
|
||||
dir="rtl"
|
||||
role="group"
|
||||
aria-label="انتخاب دسته برای فیلتر محصولات"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedCategories([])}
|
||||
className={cn(
|
||||
"shrink-0 rounded-full border px-4 py-2 text-body-sm font-medium transition-colors",
|
||||
selectedCategories.length === 0
|
||||
? "border-primary bg-primary/15 text-foreground"
|
||||
: "border-border bg-background text-muted-foreground hover:border-primary/40 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
همه دستهها
|
||||
</button>
|
||||
{CATEGORY_OPTIONS.map((item) => {
|
||||
const active = selectedCategories.includes(item.value);
|
||||
return (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => toggleCategory(item.value)}
|
||||
className={cn(
|
||||
"shrink-0 rounded-full border px-4 py-2 text-body-sm font-medium transition-colors",
|
||||
active
|
||||
? "border-primary bg-primary/15 text-foreground"
|
||||
: "border-border bg-background text-muted-foreground hover:border-primary/40 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 w-full min-w-0 md:mt-12">
|
||||
<div className="flex flex-col gap-5">
|
||||
{visibleSections.map((section) => {
|
||||
const itemChunks = chunkItems(section.items, PRODUCTS_PER_PROMO_CHUNK);
|
||||
return (
|
||||
<div key={section.category} className="flex flex-col gap-5">
|
||||
{itemChunks.map((chunk, chunkIndex) => {
|
||||
/* RTL: ستونهای ۱–۲ = سمت راست گرید، ۴–۵ = سمت چپ */
|
||||
const promoOnVisualRight = chunkIndex % 2 === 0;
|
||||
return (
|
||||
<div
|
||||
key={`${section.category}-chunk-${chunkIndex}`}
|
||||
className={cn(
|
||||
"flex flex-col gap-5",
|
||||
"lg:grid lg:grid-flow-dense lg:auto-rows-min lg:grid-cols-5 lg:items-stretch lg:gap-5",
|
||||
)}
|
||||
>
|
||||
<FeaturedPromotionCard
|
||||
className={cn(
|
||||
"w-full shrink-0 min-h-[18rem] sm:min-h-[20rem]",
|
||||
"lg:col-span-2 lg:row-span-2 lg:h-full lg:min-h-0 lg:w-auto",
|
||||
promoOnVisualRight ? "lg:col-start-1" : "lg:col-start-4",
|
||||
)}
|
||||
categoryHeading={section.categoryHeading}
|
||||
{...section.promo}
|
||||
/>
|
||||
<div className="grid min-w-0 grid-cols-2 gap-5 md:grid-cols-3 lg:contents">
|
||||
{chunk.map((product) => (
|
||||
<CatalogProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{visibleSections.length === 0 ? (
|
||||
<div className="mt-8 rounded-2xl border border-border bg-card p-8 text-center">
|
||||
<h3 className="text-heading-4 font-medium text-foreground">محصولی پیدا نشد</h3>
|
||||
<p className="mt-2 text-body-sm text-muted-foreground">فیلتر دستهبندی را تغییر دهید یا همه دستهها را نمایش دهید.</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Clock, ShoppingCart, Eye, FileText, ToggleLeft, Settings } from "lucide-react";
|
||||
|
||||
interface SolutionSectionProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SolutionSection({ className }: SolutionSectionProps) {
|
||||
const features = [
|
||||
{
|
||||
icon: Clock,
|
||||
title: "نمایش قیمت لحظهای به مشتریان",
|
||||
description: "دسترسی آنی به آخرین قیمتهای بازار"
|
||||
},
|
||||
{
|
||||
icon: ShoppingCart,
|
||||
title: "ثبت و مدیریت سفارشها",
|
||||
description: "سیستم جامع ثبت و پیگیری سفارشها"
|
||||
},
|
||||
{
|
||||
icon: Eye,
|
||||
title: "مشاهده آنلاین مانده حساب هر مشتری",
|
||||
description: "دسترسی لحظهای به وضعیت حساب مشتریان"
|
||||
},
|
||||
{
|
||||
icon: FileText,
|
||||
title: "دسترسی به ریز کامل معاملات در هر ساعت از شبانهروز",
|
||||
description: "گزارشگیری دقیق و کامل از تمام معاملات"
|
||||
},
|
||||
{
|
||||
icon: ToggleLeft,
|
||||
title: "کنترل وضعیت بازار (باز / بسته)",
|
||||
description: "مدیریت وضعیت بازار به صورت لحظهای"
|
||||
},
|
||||
{
|
||||
icon: Settings,
|
||||
title: "پنل مدیریت پیشرفته برای ادمین",
|
||||
description: "ابزارهای کامل مدیریت و کنترل سیستم"
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={cn("py-16 md:py-24 lg:py-32", className)}>
|
||||
<div className="container mx-auto max-w-6xl px-6">
|
||||
<div className="flex flex-col items-start gap-8 lg:gap-12 lg:px-16 xl:gap-32">
|
||||
<h2 className="text-heading-2 font-semibold md:shrink-0 lg:max-w-1/3 lg:max-w-3xl">
|
||||
یک پلتفرم اختصاصی برای مدیریت کامل معاملات
|
||||
</h2>
|
||||
<div className="grid w-full gap-6 md:grid-cols-2">
|
||||
{features.map((feature, index) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex rounded-lg border border-border bg-accent p-6 md:p-8"
|
||||
>
|
||||
<Icon className="mr-3 size-5 shrink-0 lg:mr-6 lg:size-6 text-primary" />
|
||||
<div>
|
||||
<div className="text-body-sm mb-3 h-5 font-semibold text-accent-foreground">
|
||||
{feature.title}
|
||||
</div>
|
||||
<div className="text-body-sm font-medium text-muted-foreground">
|
||||
{feature.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Hero174Props {
|
||||
pageHeading?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
imageSrc?: string;
|
||||
imageAlt?: string;
|
||||
ctaLabel?: string;
|
||||
ctaHref?: string;
|
||||
}
|
||||
|
||||
const defaultProps: Required<Hero174Props> = {
|
||||
pageHeading: "مستر دکور",
|
||||
title: "طراحی و تولید استند ویترین طلافروشی برای فروش بیشتر",
|
||||
description:
|
||||
"مستر دکور، تامین کننده تخصصی استند و ملزومات دکور ویترین طلا و جواهر است؛ راهکاری که چیدمان فروشگاه شما را حرفه ای تر می کند و توجه مشتری را دقیقاً روی سرویس های شما نگه می دارد.",
|
||||
imageSrc: "/assets/main-pic/mr_decor-mainpic.jpg",
|
||||
imageAlt: "نمونه ویترین و دکور فروشگاهی",
|
||||
ctaLabel: "مشاهده محصولات",
|
||||
ctaHref: "/product",
|
||||
};
|
||||
|
||||
export function Hero174(props: Hero174Props) {
|
||||
const {
|
||||
pageHeading,
|
||||
title,
|
||||
description,
|
||||
imageSrc,
|
||||
imageAlt,
|
||||
ctaLabel,
|
||||
ctaHref,
|
||||
} = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="relative min-h-screen w-full" aria-labelledby="home-hero-page-heading">
|
||||
<h1 id="home-hero-page-heading" className="sr-only">
|
||||
{pageHeading}
|
||||
</h1>
|
||||
|
||||
<div className="absolute inset-0">
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
fill
|
||||
priority
|
||||
quality={100}
|
||||
sizes="100vw"
|
||||
className="object-cover object-[center_58%]"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/62" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(0,0,0,0.34),transparent_60%)]" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom,rgba(0,0,0,0.3),transparent_62%)]" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex min-h-screen flex-col">
|
||||
<div className="flex flex-1 items-center justify-center px-6 pt-24 pb-14 text-center md:px-10">
|
||||
<div className="flex max-w-4xl flex-col items-center gap-5">
|
||||
<h2 className="text-heading-1 font-bold text-overlay-foreground">{title}</h2>
|
||||
<p className="max-w-2xl text-body text-overlay-foreground/90">{description}</p>
|
||||
<Button asChild size="lg" className="rounded-full">
|
||||
<Link href={ctaHref}>{ctaLabel}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Sparkles } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Hero197Props {
|
||||
className?: string;
|
||||
badge?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showButtons?: boolean;
|
||||
}
|
||||
|
||||
const Hero197 = ({
|
||||
className,
|
||||
badge = "Launching Soon",
|
||||
title = "Accelerate your development workflow",
|
||||
description = "Our intelligent platform helps teams automate routine tasks, streamline collaboration, and deliver higher quality code faster.",
|
||||
showButtons = true,
|
||||
}: Hero197Props) => {
|
||||
return (
|
||||
<section className={cn("relative overflow-hidden py-32", className)}>
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
{/* Background pattern with dots */}
|
||||
<div className="absolute inset-0 -z-10 h-full w-full bg-[radial-gradient(var(--primary)_1px,transparent_1px)] [background-size:20px_20px] opacity-25"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col gap-6 md:flex-row md:items-center md:justify-center">
|
||||
<div className="max-w-2xl w-full flex flex-col items-center text-center">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mb-6 bg-background px-4 py-1.5 text-sm"
|
||||
>
|
||||
{badge} <Sparkles className="ml-1 size-3.5" />
|
||||
</Badge>
|
||||
|
||||
<h1 className="text-heading-1 pt-4 font-bold leading-tight bg-linear-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p className="text-body mt-6 max-w-xl leading-relaxed text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
|
||||
{showButtons && (
|
||||
<div className="mt-8 flex flex-col gap-6 sm:flex-row sm:items-center justify-center">
|
||||
<Button size="lg" variant="primary" className="w-full sm:w-auto">
|
||||
شروع مشاوره
|
||||
</Button>
|
||||
<Button size="lg" variant="secondary" className="w-full sm:w-auto" asChild>
|
||||
<Link href="/consultation">درخواست مشاوره</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero197 };
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
interface Hero200Props {
|
||||
className?: string;
|
||||
/** تصویر داخل قاب موبایل (پیشنمایش اپ) */
|
||||
phoneImage?: { src: string; alt: string };
|
||||
}
|
||||
|
||||
const Hero200 = ({ className, phoneImage }: Hero200Props) => {
|
||||
const content = useContent();
|
||||
const hero = content.pages.product.forushgahSazTala.hero200;
|
||||
const appPreviewSrc = phoneImage?.src ?? "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/block-white-1.svg";
|
||||
const appPreviewAlt = phoneImage?.alt ?? "Gold store app";
|
||||
return (
|
||||
<section className={cn("bg-background pt-24 md:pt-32 lg:pt-40 pb-8 md:pb-12 lg:pb-16", className)}>
|
||||
<div className="border-b border-foreground/40">
|
||||
<div className="container mx-auto flex max-w-7xl flex-col items-center justify-center gap-4 px-6 text-center">
|
||||
<h2 className="text-heading-2 font-semibold leading-snug tracking-tight text-foreground/80" dir="rtl">
|
||||
{hero.subtitle}
|
||||
</h2>
|
||||
<div className="w-full overflow-hidden" dir="rtl">
|
||||
<h1 className="text-heading-1 font-bold leading-snug tracking-tight text-foreground">
|
||||
{hero.titleLine1}
|
||||
<br />
|
||||
{hero.titleLine2}
|
||||
<br />
|
||||
{hero.titleLine3}
|
||||
</h1>
|
||||
</div>
|
||||
<Button size="lg" variant="primary" className="my-10" dir="rtl" asChild>
|
||||
<Link href="/consultation">{hero.button}</Link>
|
||||
</Button>
|
||||
|
||||
{/* Iphone mockup with content */}
|
||||
<div className="h-[500px] w-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 200, scale: 0.8 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{ ease: [0, 0.71, 0.2, 1.01], duration: 0.8 }}
|
||||
className="relative mx-auto mt-6 flex h-[850px] w-[400px] items-center justify-center rounded-[75px] bg-black md:mt-12 md:h-[920px] md:w-[450px]"
|
||||
>
|
||||
<img
|
||||
className="absolute z-[2] scale-105 object-cover"
|
||||
alt="Gold phone frame"
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/mockups/phone-5.png"
|
||||
/>
|
||||
<div className="relative z-[1] h-full w-full overflow-hidden rounded-[3.25rem]">
|
||||
{phoneImage ? (
|
||||
<img
|
||||
className="h-full w-full object-cover object-top"
|
||||
alt={appPreviewAlt}
|
||||
src={appPreviewSrc}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="mt-20 flex justify-between px-0">
|
||||
<div className="flex items-end gap-2 px-12 text-heading-1 font-bold leading-snug tracking-tight text-background" dir="rtl" aria-hidden>
|
||||
طلا
|
||||
<div className="mb-2 size-3 rounded-full bg-primary md:size-5" />
|
||||
</div>
|
||||
<div className="mt-2 mr-8 flex flex-col items-end">
|
||||
<p className="text-body-sm leading-normal tracking-tight text-muted-foreground md:text-body">
|
||||
{hero.dateLabel}
|
||||
</p>
|
||||
<p className="-mt-1 text-heading-5 font-semibold leading-snug tracking-tight text-muted-foreground/50">
|
||||
{hero.appLabel}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
className="z-2 mx-auto mt-20 size-40 object-cover"
|
||||
alt={appPreviewAlt}
|
||||
src={appPreviewSrc}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero200 };
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Hero201Props {
|
||||
className?: string;
|
||||
smallTitle?: string;
|
||||
mainTitle?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const Hero201 = ({
|
||||
className,
|
||||
smallTitle = "Just Copy Paste",
|
||||
mainTitle = "Amazing components",
|
||||
description = "Discover our collection of beautifully designed, ready-to-use components that you can easily integrate into your projects.",
|
||||
}: Hero201Props) => {
|
||||
return (
|
||||
<section className={cn("py-16 md:py-24 lg:py-32", className)} dir="rtl">
|
||||
<div className="relative container mx-auto flex flex-col items-center overflow-hidden px-6">
|
||||
{/* دایرههای تزئینی پشت متن تا لرزش ایجاد نکنند */}
|
||||
<div className="pointer-events-none absolute inset-0 z-0 hidden overflow-hidden md:block" aria-hidden>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.6 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ ease: "easeInOut", duration: 0.6 }}
|
||||
className="absolute -top-80 -right-80 h-240 w-240 will-change-transform"
|
||||
style={{ contain: "layout paint" }}
|
||||
>
|
||||
<img
|
||||
className="h-full w-full rotate-12 opacity-30 dark:invert animate-[spin_15s_linear_infinite]"
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/illustrations/tokyo-solar-system-around-a-smiley.svg"
|
||||
alt=""
|
||||
/>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.6 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ ease: "easeInOut", duration: 0.6 }}
|
||||
className="absolute -bottom-80 -left-80 h-240 w-240 will-change-transform"
|
||||
style={{ contain: "layout paint" }}
|
||||
>
|
||||
<img
|
||||
className="h-full w-full rotate-12 opacity-30 dark:invert animate-[spin_15s_linear_infinite]"
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/illustrations/tokyo-solar-system-around-a-smiley.svg"
|
||||
alt=""
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col items-center">
|
||||
<p className="text-heading-5 mb-4 text-center font-semibold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
{smallTitle}
|
||||
</p>
|
||||
|
||||
<h2 className="text-heading-2 mx-auto max-w-4xl text-center font-bold leading-[var(--line-height-heading)] tracking-tight text-foreground">
|
||||
{mainTitle}
|
||||
</h2>
|
||||
|
||||
<p className="text-body mx-auto mt-6 max-w-3xl text-center leading-[var(--line-height-body)] text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero201 };
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Hero204Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Hero204 = ({ className }: Hero204Props) => {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"flex min-h-[50vh] flex-col items-center justify-center bg-background pt-24 pb-16 md:pt-32 md:pb-20 lg:pt-36 lg:pb-24",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto flex w-full max-w-7xl flex-col items-center justify-center gap-4 px-6 text-center">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className="max-w-2xl text-heading-1 font-bold tracking-tighter text-foreground"
|
||||
>
|
||||
درخواست دمو
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.1 }}
|
||||
className="mt-4 max-w-xl text-body text-muted-foreground"
|
||||
>
|
||||
سامانههای تخصصی مستر دکور را قبل از تصمیمگیری از نزدیک ببینید. فرم را پر کنید تا در اسرع وقت برای هماهنگی جلسه دمو و نمایش امکانات با شما تماس بگیریم.
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero204 };
|
||||
@@ -0,0 +1,235 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Carousel,
|
||||
type CarouselApi,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
} from "@/components/ui/carousel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface HeroCarouselSlide {
|
||||
src: string;
|
||||
alt: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export interface Hero226Props {
|
||||
slides: HeroCarouselSlide[];
|
||||
slideCtaLabel: string;
|
||||
/** برای دسترسیپذیری: تنها h1 معنادار صفحه (معمولاً نام برند) */
|
||||
pageHeading: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
type Props = Partial<Hero226Props>;
|
||||
|
||||
const defaultSlides: HeroCarouselSlide[] = [
|
||||
{
|
||||
src: "/assets/main-pic/banner-master-decoor.jpg",
|
||||
alt: "نمونه ویترین و دکور",
|
||||
title: "ویترین طلا",
|
||||
description: "نمایش حرفهای و مدیریت ویترین محصولات طلا.",
|
||||
href: "/product?category=vitrin-tala",
|
||||
},
|
||||
{
|
||||
src: "/assets/main-pic/banner-master-decoor.jpg",
|
||||
alt: "نمونه ویترین خردسال",
|
||||
title: "ویترین خردسال",
|
||||
description: "راهکار ویترین و فرایندهای مرتبط با خردسال.",
|
||||
href: "/product?category=vitrin-khorde-sal",
|
||||
},
|
||||
{
|
||||
src: "/assets/main-pic/banner-master-decoor.jpg",
|
||||
alt: "نمونه ویترین جواهر",
|
||||
title: "ویترین جواهر",
|
||||
description: "ویترین تخصصی برای جواهر و اقلام پرارزش.",
|
||||
href: "/product?category=vitrin-javaher",
|
||||
},
|
||||
{
|
||||
src: "/assets/main-pic/banner-master-decoor.jpg",
|
||||
alt: "نمونه دکور فروشگاه",
|
||||
title: "دکور و جعبه",
|
||||
description: "ترکیبی از تنوع، ظرافت و کیفیت برای فضای فروش شما.",
|
||||
href: "/about",
|
||||
},
|
||||
{
|
||||
src: "/assets/main-pic/banner-master-decoor.jpg",
|
||||
alt: "نمونه راهکار سفارشی",
|
||||
title: "راهکار سفارشی",
|
||||
description: "طراحی و تولید متناسب با مدل کاری مجموعه شما.",
|
||||
href: "/consultation",
|
||||
},
|
||||
];
|
||||
|
||||
const defaultProps: Required<Pick<Hero226Props, "slides" | "slideCtaLabel" | "pageHeading">> = {
|
||||
slides: defaultSlides,
|
||||
slideCtaLabel: "مشاهده",
|
||||
pageHeading: "مستر دکور",
|
||||
};
|
||||
|
||||
function isExternalHref(href: string) {
|
||||
return href.startsWith("http://") || href.startsWith("https://");
|
||||
}
|
||||
|
||||
function SlideCtaLink({
|
||||
href,
|
||||
slideCtaLabel,
|
||||
}: {
|
||||
href: string;
|
||||
slideCtaLabel: string;
|
||||
}) {
|
||||
const className =
|
||||
"group gap-2 rounded-full border border-gold-400/55 bg-background/15 px-4 py-2 text-body font-medium tracking-tight text-gold-300 backdrop-blur-sm hover:border-gold-400/75 hover:bg-gold-950/35 hover:text-gold-200";
|
||||
|
||||
const icon = (
|
||||
<ArrowRight
|
||||
className="size-4 shrink-0 transition-transform ease-out group-hover:translate-x-0.5 rtl:rotate-180"
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
|
||||
if (isExternalHref(href)) {
|
||||
return (
|
||||
<Button variant="outline" className={className} asChild>
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" className="inline-flex items-center">
|
||||
{slideCtaLabel}
|
||||
{icon}
|
||||
</a>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button variant="outline" className={className} asChild>
|
||||
<Link href={href} className="inline-flex items-center">
|
||||
{slideCtaLabel}
|
||||
{icon}
|
||||
</Link>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export function Hero226(props: Props) {
|
||||
const [api, setApi] = useState<CarouselApi>();
|
||||
const [current, setCurrent] = useState(0);
|
||||
|
||||
const { slides, slideCtaLabel, pageHeading, className } = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!api) return;
|
||||
const onSelect = () => setCurrent(api.selectedScrollSnap());
|
||||
onSelect();
|
||||
api.on("select", onSelect);
|
||||
return () => {
|
||||
api.off("select", onSelect);
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"scroll-mt-20 pt-20 pb-16 md:scroll-mt-24 md:pt-24 md:pb-20 lg:pb-28",
|
||||
className,
|
||||
)}
|
||||
aria-labelledby="home-hero-page-heading">
|
||||
<h1 id="home-hero-page-heading" className="sr-only">
|
||||
{pageHeading}
|
||||
</h1>
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<Carousel
|
||||
setApi={setApi}
|
||||
className="w-full"
|
||||
opts={{
|
||||
loop: true,
|
||||
slidesToScroll: 1,
|
||||
direction: "rtl",
|
||||
align: "center",
|
||||
}}
|
||||
>
|
||||
<CarouselContent className="flex w-full gap-2 sm:gap-4">
|
||||
{slides.map((img, index) => (
|
||||
<CarouselItem
|
||||
key={`${img.src}-${index}`}
|
||||
className="basis-full"
|
||||
>
|
||||
<div className="p-0.5 sm:p-1">
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex min-h-[min(70vh,28rem)] flex-col justify-end overflow-hidden !rounded-xl sm:min-h-[32rem] md:min-h-[36rem] lg:min-h-[42rem] lg:rounded-2xl",
|
||||
"border border-border/40 bg-muted animated-gradient-border p-6 sm:p-8",
|
||||
)}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<Image
|
||||
src={img.src}
|
||||
alt={img.alt}
|
||||
fill
|
||||
className="object-cover object-center"
|
||||
sizes="(max-width: 768px) 90vw, (max-width: 1200px) 80vw, 1200px"
|
||||
priority={index === 0}
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex w-full flex-col gap-4 text-start">
|
||||
<div className="max-w-full space-y-3 sm:max-w-2xl lg:max-w-3xl">
|
||||
{/*
|
||||
هر اسلاید h2 است؛ h1 فقط sr-only بالای سکشن (استاندارد تایپوگرافی)
|
||||
*/}
|
||||
<h2 className="text-balance text-heading-1 font-bold tracking-tight text-foreground">
|
||||
{img.title ?? ""}
|
||||
</h2>
|
||||
<p className="text-balance text-body leading-relaxed text-foreground/90">
|
||||
{img.description ?? ""}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap items-center justify-between gap-3 bg-transparent pt-1">
|
||||
<SlideCtaLink href={img.href ?? "/"} slideCtaLabel={slideCtaLabel} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CarouselItem>
|
||||
))}
|
||||
</CarouselContent>
|
||||
|
||||
<div className="mt-4 flex flex-wrap justify-center gap-2 sm:mt-6">
|
||||
{slides.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={() => api?.scrollTo(index)}
|
||||
className={cn(
|
||||
"h-2.5 min-h-2.5 min-w-2.5 rounded-full transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||
current === index ? "w-8 bg-primary" : "bg-muted-foreground/50 hover:bg-muted-foreground/70",
|
||||
)}
|
||||
aria-label={`رفتن به اسلاید ${index + 1}`}
|
||||
aria-current={current === index ? "true" : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Carousel>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
interface ButtonItem {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Hero247Props {
|
||||
heading?: string;
|
||||
description?: string;
|
||||
secondaryButton?: ButtonItem;
|
||||
primaryButton?: ButtonItem;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Hero247 = ({
|
||||
className,
|
||||
heading,
|
||||
description,
|
||||
secondaryButton: secondaryButtonProp,
|
||||
primaryButton: primaryButtonProp,
|
||||
}: Hero247Props) => {
|
||||
const content = useContent()
|
||||
const defaults = content.pages.home.hero247
|
||||
const headingText = heading ?? defaults.heading
|
||||
const descriptionText = description ?? defaults.description
|
||||
const secondaryButton = secondaryButtonProp ?? { text: defaults.secondaryButton, url: "#rahavarha" }
|
||||
const primaryButton = primaryButtonProp ?? { text: defaults.primaryButton, url: "#mozakerat" }
|
||||
return (
|
||||
<section className={cn("py-32", className)} dir="rtl">
|
||||
<div className="container mx-auto flex flex-col items-center px-6">
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
<motion.h2
|
||||
initial={{ y: 100, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
className="text-heading-2 max-w-3xl text-center font-semibold text-foreground dark:text-foreground"
|
||||
>
|
||||
{headingText}
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
initial={{ y: 60, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{
|
||||
duration: 1.2,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
delay: 0.2,
|
||||
}}
|
||||
className="text-body max-w-2xl whitespace-pre-line text-center leading-relaxed text-muted-foreground dark:text-muted-foreground"
|
||||
>
|
||||
{descriptionText}
|
||||
</motion.p>
|
||||
<div className="flex flex-col gap-4 font-medium md:flex-row md:gap-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 60 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: 1.2,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
delay: 0.25,
|
||||
}}
|
||||
>
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href={primaryButton.url}>{primaryButton.text}</Link>
|
||||
</Button>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 60 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{
|
||||
duration: 1.2,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
delay: 0.4,
|
||||
}}
|
||||
>
|
||||
<Button asChild size="lg" variant="secondary">
|
||||
<Link href={secondaryButton.url}>{secondaryButton.text}</Link>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero247 };
|
||||
@@ -0,0 +1,169 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Hero28Props {
|
||||
className?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showButton?: boolean;
|
||||
}
|
||||
|
||||
const Hero28 = ({
|
||||
className,
|
||||
title = "Welcome to Our Website",
|
||||
description = "Elig doloremque mollitia fugiat omnis! Porro facilis quo animi consequatur.",
|
||||
showButton = true,
|
||||
}: Hero28Props) => {
|
||||
return (
|
||||
<section className={cn("relative py-32 overflow-visible", className)}>
|
||||
{/* Background pattern */}
|
||||
<div className="absolute inset-x-0 top-0 container mx-auto hidden h-full overflow-visible lg:block pointer-events-none">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 520" className="w-full h-auto">
|
||||
<defs>
|
||||
<radialGradient id="text-backgroud" cx="50%" cy="50%" r={0.6}>
|
||||
<stop stopColor="black" offset={0.4} />
|
||||
<stop stopColor="black" offset={1} stopOpacity={0} />
|
||||
</radialGradient>
|
||||
<linearGradient id="icon-backgroud" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop stopColor="var(--color-accent)" offset={0} />
|
||||
<stop stopColor="var(--color-background)" offset={1} />
|
||||
</linearGradient>
|
||||
<linearGradient id="gold-coin-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="var(--gold-300)" />
|
||||
<stop offset="50%" stopColor="var(--gold-500)" />
|
||||
<stop offset="100%" stopColor="var(--gold-700)" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gold-bar-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stopColor="var(--gold-300)" />
|
||||
<stop offset="50%" stopColor="var(--gold-500)" />
|
||||
<stop offset="100%" stopColor="var(--gold-700)" />
|
||||
</linearGradient>
|
||||
<mask id="mask">
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
stroke="none"
|
||||
fill="black"
|
||||
/>
|
||||
<rect
|
||||
x="80"
|
||||
y="40"
|
||||
width="1260"
|
||||
height="380"
|
||||
rx="140"
|
||||
stroke="none"
|
||||
filter="url(#blur)"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="40"
|
||||
y="-120"
|
||||
width="1320"
|
||||
height="600"
|
||||
stroke="none"
|
||||
fill="url(#text-backgroud)"
|
||||
/>
|
||||
</mask>
|
||||
<filter id="blur" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="40" />
|
||||
</filter>
|
||||
</defs>
|
||||
<path
|
||||
stroke="var(--border)"
|
||||
strokeWidth={1}
|
||||
d="M0,40H1400M0,120H1400M0,200H1400M0,280H1400M0,360H1400M0,440H1400M60,0V520M140,0V520M220,0V520M300,0V520M380,0V520M460,0V520M540,0V520M620,0V520M700,0V520M780,0V520M860,0V520M940,0V520M1020,0V520M1100,0V520M1180,0V520M1260,0V520M1340,0V520"
|
||||
mask="url(#mask)"
|
||||
/>
|
||||
{/* سکه طلا */}
|
||||
<rect
|
||||
x={140}
|
||||
y={120}
|
||||
width={80}
|
||||
height={80}
|
||||
stroke="var(--border)"
|
||||
fill="url(#icon-backgroud)"
|
||||
/>
|
||||
<svg x={160} y={140} width={40} height={40} viewBox="0 0 40 40" className="overflow-visible">
|
||||
<circle cx={20} cy={20} r={16} fill="url(#gold-coin-grad)" stroke="var(--gold-700)" strokeWidth={1.5} />
|
||||
<circle cx={20} cy={20} r={10} fill="none" stroke="var(--gold-700)" strokeWidth={1} opacity={0.8} />
|
||||
</svg>
|
||||
{/* شمش طلا */}
|
||||
<rect
|
||||
x={60}
|
||||
y={280}
|
||||
width={80}
|
||||
height={80}
|
||||
stroke="var(--border)"
|
||||
fill="url(#icon-backgroud)"
|
||||
/>
|
||||
<svg x={80} y={300} width={40} height={40} viewBox="0 0 40 40" className="overflow-visible">
|
||||
<rect x={6} y={12} width={28} height={16} rx={3} fill="url(#gold-bar-grad)" stroke="var(--gold-700)" strokeWidth={1.5} />
|
||||
<rect x={10} y={14} width={4} height={12} rx={1} fill="var(--gold-600)" opacity={0.6} />
|
||||
<rect x={26} y={14} width={4} height={12} rx={1} fill="var(--gold-600)" opacity={0.6} />
|
||||
</svg>
|
||||
{/* سکه طلا */}
|
||||
<rect
|
||||
x={300}
|
||||
y={360}
|
||||
width={80}
|
||||
height={80}
|
||||
stroke="var(--border)"
|
||||
fill="url(#icon-backgroud)"
|
||||
/>
|
||||
<svg x={320} y={380} width={40} height={40} viewBox="0 0 40 40" className="overflow-visible">
|
||||
<circle cx={20} cy={20} r={16} fill="url(#gold-coin-grad)" stroke="var(--gold-700)" strokeWidth={1.5} />
|
||||
<circle cx={20} cy={20} r={10} fill="none" stroke="var(--gold-700)" strokeWidth={1} opacity={0.8} />
|
||||
</svg>
|
||||
{/* شمش طلا */}
|
||||
<rect
|
||||
x={1180}
|
||||
y={40}
|
||||
width={80}
|
||||
height={80}
|
||||
stroke="var(--border)"
|
||||
fill="url(#icon-backgroud)"
|
||||
/>
|
||||
<svg x={1200} y={60} width={40} height={40} viewBox="0 0 40 40" className="overflow-visible">
|
||||
<rect x={6} y={12} width={28} height={16} rx={3} fill="url(#gold-bar-grad)" stroke="var(--gold-700)" strokeWidth={1.5} />
|
||||
<rect x={10} y={14} width={4} height={12} rx={1} fill="var(--gold-600)" opacity={0.6} />
|
||||
<rect x={26} y={14} width={4} height={12} rx={1} fill="var(--gold-600)" opacity={0.6} />
|
||||
</svg>
|
||||
{/* سکه طلا */}
|
||||
<rect
|
||||
x={1260}
|
||||
y={280}
|
||||
width={80}
|
||||
height={80}
|
||||
stroke="var(--border)"
|
||||
fill="url(#icon-backgroud)"
|
||||
/>
|
||||
<svg x={1280} y={300} width={40} height={40} viewBox="0 0 40 40" className="overflow-visible">
|
||||
<circle cx={20} cy={20} r={16} fill="url(#gold-coin-grad)" stroke="var(--gold-700)" strokeWidth={1.5} />
|
||||
<circle cx={20} cy={20} r={10} fill="none" stroke="var(--gold-700)" strokeWidth={1} opacity={0.8} />
|
||||
</svg>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 container mx-auto flex flex-col items-center text-center px-4 md:px-6 min-h-[400px]">
|
||||
<h1 className="text-heading-1 my-6 font-bold text-pretty">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-body mb-8 max-w-3xl leading-relaxed text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
{showButton && (
|
||||
<div className="mb-8">
|
||||
<Button variant="primary">Primary</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero28 };
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Hero49Props {
|
||||
className?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showButton?: boolean;
|
||||
/** تصویر داخل فریم موبایل (مثلاً اسکرینشات اپ) */
|
||||
phoneImage?: string;
|
||||
}
|
||||
|
||||
const Hero49 = ({
|
||||
className,
|
||||
title = "Master your monetary matters",
|
||||
description = "Minimize financial stress and build wealth with our intuitive, AI-enhanced budgeting tool. Our platform analyzes your spending and offers insights for smarter financial decisions.",
|
||||
showButton = true,
|
||||
phoneImage,
|
||||
}: Hero49Props) => {
|
||||
const phoneImageSrc =
|
||||
phoneImage ?? "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-dark-7-tall.svg";
|
||||
return (
|
||||
<section className={cn("bg-background py-32", className)}>
|
||||
<div className="container mx-auto flex flex-col items-center px-4 pt-24 md:pt-32 lg:pt-40">
|
||||
<div className="flex max-w-3xl flex-col items-center gap-8 text-center">
|
||||
<h1 className="text-heading-1 font-bold text-foreground">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-body max-w-xl leading-relaxed tracking-tight text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
{showButton && (
|
||||
<Button variant="primary" size="lg" className="gap-2">
|
||||
<img
|
||||
alt="google play and app store download icons"
|
||||
loading="lazy"
|
||||
width="44"
|
||||
height="18"
|
||||
decoding="async"
|
||||
data-nimg="1"
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/hero49/download.svg"
|
||||
/>
|
||||
<p>Get started now</p>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative translate-x-[14.7%]">
|
||||
{/* Image inside Iphone */}
|
||||
<div className="absolute top-[12%] left-[36.5%]! h-[67%]! w-[31%]! -translate-x-[52%] overflow-hidden rounded-[10px] bg-muted">
|
||||
<img
|
||||
className="min-h-full min-w-full object-cover"
|
||||
src={phoneImageSrc}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
{/* Iphone Image */}
|
||||
<img
|
||||
alt=""
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/hero49/iphone.png"
|
||||
className="relative z-10"
|
||||
loading="lazy"
|
||||
width="1008.71"
|
||||
height="857"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero49 };
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useContent } from "@/components/layout/content-provider";
|
||||
|
||||
interface Hero91Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Hero91 = ({ className }: Hero91Props) => {
|
||||
const content = useContent();
|
||||
const { hero91 } = content.pages.home;
|
||||
const heroImageSrc = content.images.hero91.image;
|
||||
const heroVideoSrc = content.images.hero91.video;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"py-24 md:py-32 lg:py-40",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="container mx-auto flex max-w-7xl flex-col items-center gap-8 px-6 lg:flex-row lg:items-center lg:justify-between lg:gap-12">
|
||||
<div className="flex flex-col gap-6 lg:w-1/2" dir="rtl">
|
||||
<h1 className="text-heading-1 font-bold leading-tight text-foreground whitespace-pre-line">
|
||||
{hero91.title}
|
||||
</h1>
|
||||
<p className="text-body leading-relaxed text-muted-foreground">
|
||||
{hero91.description}
|
||||
</p>
|
||||
<div className="flex flex-col gap-4 font-medium md:flex-row md:gap-4">
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href="/contact">{hero91.primaryButton}</Link>
|
||||
</Button>
|
||||
<Button asChild size="lg" variant="secondary">
|
||||
<Link href="/consultation">{hero91.secondaryButton}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full max-w-full overflow-hidden rounded-2xl bg-black lg:w-1/2 lg:max-w-[640px] aspect-square",
|
||||
"shadow-[0_0_50px_rgba(37,102,90,0.28),0_0_90px_rgba(37,102,90,0.12)] dark:shadow-[0_0_50px_rgba(201,157,77,0.28),0_0_90px_rgba(201,157,77,0.12)]",
|
||||
)}
|
||||
>
|
||||
<div className="absolute inset-0">
|
||||
{heroVideoSrc ? (
|
||||
<video
|
||||
src={heroVideoSrc}
|
||||
className="h-full w-full object-cover"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="metadata"
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src={heroImageSrc}
|
||||
alt=""
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
unoptimized
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero91 };
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
|
||||
import { LineShadowText } from "@/components/ui/line-shadow-text";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface Hero233Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Hero233 = ({ className }: Hero233Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"relative h-screen w-screen overflow-hidden bg-background py-32",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="relative z-20 container flex flex-col items-center justify-center gap-4 py-10 text-center md:mt-22">
|
||||
<div className="absolute -z-1 size-full max-w-3xl bg-background blur-xl" />
|
||||
<Button variant="outline" className="my-16 w-fit gap-2 md:my-5">
|
||||
<span className="size-2 rounded-full bg-current" />
|
||||
<span>Flexible Plan customized for you</span>
|
||||
</Button>
|
||||
<div className="relative flex max-w-4xl items-center justify-center text-center">
|
||||
<h1 className="text-heading-1 relative z-10 font-bold tracking-tight">
|
||||
<span className="mr-3 text-muted-foreground/50">
|
||||
The only app you Need to Stay
|
||||
</span>
|
||||
<LineShadowText> Productive </LineShadowText>
|
||||
<span className="text-muted-foreground/50"> ever</span>
|
||||
<span>.</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p className="text-body mt-5 max-w-xl bg-background leading-relaxed text-muted-foreground/80">
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Ipsum animi,
|
||||
ipsam provident optio delectus neque aliquid cumque. Beatae, odio!
|
||||
</p>
|
||||
<div className="my-5 flex flex-wrap justify-center gap-4">
|
||||
<Button variant="secondary" className="w-fit gap-2">
|
||||
<span>Documentation</span>
|
||||
<ArrowRight className="size-4 -rotate-45 transition-all ease-out group-hover:ml-3 group-hover:rotate-0" />
|
||||
</Button>
|
||||
<Button variant="primary" className="w-fit gap-2">
|
||||
<span>Get Started</span>
|
||||
<ArrowRight className="size-4 -rotate-45 transition-all ease-out group-hover:ml-3 group-hover:rotate-0" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-0 flex size-full justify-center">
|
||||
{Array.from({ length: isMobile ? 7 : 18 }).map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "100%" }}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
delay: i * 0.05,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
className="w-24 border-l bg-gradient-to-b to-transparent transition-all ease-in-out hover:scale-110 hover:from-black/2"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { Hero233 };
|
||||
@@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment, useState } from "react";
|
||||
import { Play } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
export interface HeroContactProps {
|
||||
className?: string;
|
||||
/** برچسب بالای عنوان (مثلاً نام شرکت) */
|
||||
eyebrow?: string;
|
||||
/** عنوان اصلی - فقط یک h1 در صفحه */
|
||||
heading: string;
|
||||
/** توضیح زیر عنوان */
|
||||
description: string;
|
||||
/** متن دکمه اصلی (مثلاً «فرم تماس») */
|
||||
ctaLabel?: string;
|
||||
/** آدرس اسکرول به فرم (مثلاً #contact-form) */
|
||||
ctaHref?: string;
|
||||
/** مسیر تصویر هیرو (مثلاً /assets/contact-us/contact-us.jpg) */
|
||||
imageSrc?: string;
|
||||
/** متن جایگزین تصویر */
|
||||
imageAlt?: string;
|
||||
/** متن دکمه ویدیو (اختیاری - در صورت خالی بودن دکمه ویدیو نمایش داده نمیشود) */
|
||||
videoLabel?: string;
|
||||
/** آیدی ویدیو یوتیوب برای embed (اختیاری) */
|
||||
videoId?: string;
|
||||
}
|
||||
|
||||
const HeroContact = ({
|
||||
className,
|
||||
eyebrow,
|
||||
heading,
|
||||
description,
|
||||
ctaLabel = "فرم تماس",
|
||||
ctaHref = "#contact-form",
|
||||
imageSrc,
|
||||
imageAlt = "تماس با ما",
|
||||
videoLabel,
|
||||
videoId,
|
||||
}: HeroContactProps) => {
|
||||
const [isVideoOpen, setIsVideoOpen] = useState(false);
|
||||
|
||||
const handleCtaClick = (e: React.MouseEvent) => {
|
||||
if (ctaHref.startsWith("#")) {
|
||||
e.preventDefault();
|
||||
const el = document.getElementById(ctaHref.slice(1));
|
||||
el?.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<section
|
||||
className={cn(
|
||||
"flex min-h-[60vh] flex-col justify-center pt-28 pb-16 md:pt-36 md:pb-24",
|
||||
className
|
||||
)}
|
||||
aria-labelledby="hero-contact-heading"
|
||||
>
|
||||
<div className="container mx-auto w-full max-w-7xl px-4 md:px-6">
|
||||
<div className="grid grid-cols-1 items-center justify-items-center gap-10 lg:grid-cols-2 lg:justify-items-stretch">
|
||||
<div className="flex w-full max-w-xl flex-col gap-6 lg:max-w-[40rem]">
|
||||
{eyebrow && (
|
||||
<p className="text-caption font-medium tracking-wider text-muted-foreground uppercase">
|
||||
{eyebrow}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-6 text-center lg:text-right">
|
||||
<h1
|
||||
id="hero-contact-heading"
|
||||
className="text-heading-1 font-bold leading-tight text-foreground"
|
||||
>
|
||||
{heading}
|
||||
</h1>
|
||||
<p className="text-body text-balance text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-4 py-4 lg:justify-start">
|
||||
<Button
|
||||
asChild
|
||||
className="h-fit w-fit rounded-sm px-5 py-3.5 text-body-sm font-medium tracking-wider uppercase text-foreground"
|
||||
>
|
||||
<a
|
||||
href={ctaHref}
|
||||
onClick={ctaHref.startsWith("#") ? handleCtaClick : undefined}
|
||||
className="inline-flex items-center justify-center"
|
||||
>
|
||||
<span className="text-inherit">{ctaLabel}</span>
|
||||
</a>
|
||||
</Button>
|
||||
{videoLabel && videoId && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={() => setIsVideoOpen(true)}
|
||||
className="flex h-fit w-fit flex-nowrap items-center gap-2 rounded-sm bg-transparent px-5 py-3.5 text-body-sm font-medium tracking-wider uppercase text-foreground"
|
||||
>
|
||||
<Play className="h-4 w-4 fill-foreground" aria-hidden />
|
||||
<span>{videoLabel}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full justify-center">
|
||||
{imageSrc ? (
|
||||
<div className="relative mx-auto aspect-[4/3] w-full max-w-[25rem] overflow-hidden rounded-[35px]">
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={imageAlt}
|
||||
className="size-full object-cover object-center"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative mx-auto aspect-[0.789340102/1] w-full max-w-[25rem]">
|
||||
<div className="absolute bottom-0 left-0 z-30 w-[63%]">
|
||||
<AspectRatio
|
||||
ratio={0.724137931 / 1}
|
||||
className="overflow-hidden rounded-lg"
|
||||
>
|
||||
<img
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-1.svg"
|
||||
alt=""
|
||||
className="size-full object-cover object-center"
|
||||
/>
|
||||
</AspectRatio>
|
||||
</div>
|
||||
<div className="absolute top-1/2 left-1/2 z-20 w-[63%] -translate-x-1/2 -translate-y-1/2">
|
||||
<AspectRatio
|
||||
ratio={0.724137931 / 1}
|
||||
className="overflow-hidden rounded-lg"
|
||||
>
|
||||
<img
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-dark-1.svg"
|
||||
alt=""
|
||||
className="size-full object-cover object-center"
|
||||
/>
|
||||
</AspectRatio>
|
||||
</div>
|
||||
<div className="absolute top-0 right-0 z-10 w-[63%]">
|
||||
<AspectRatio
|
||||
ratio={0.724137931 / 1}
|
||||
className="overflow-hidden rounded-lg"
|
||||
>
|
||||
<img
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-1.svg"
|
||||
alt=""
|
||||
className="size-full object-cover object-center"
|
||||
/>
|
||||
</AspectRatio>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{videoId && (
|
||||
<Dialog open={isVideoOpen} onOpenChange={setIsVideoOpen}>
|
||||
<DialogContent className="sm:max-w-[800px]" dir="rtl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>ویدیو معرفی</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="aspect-video">
|
||||
<iframe
|
||||
className="h-full w-full"
|
||||
src={`https://www.youtube.com/embed/${videoId}`}
|
||||
title="ویدیو معرفی"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export { HeroContact };
|
||||
@@ -0,0 +1,244 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, ArrowRight, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Image from "next/image";
|
||||
import { TextEffect } from "@/components/ui/text-effect";
|
||||
import { AnimatedGroup } from "@/components/ui/animated-group";
|
||||
import { HeroHeader } from "./header";
|
||||
import { LogoItem } from "@/components/ui/logo-item";
|
||||
|
||||
const transitionVariants = {
|
||||
item: {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
filter: "blur(12px)",
|
||||
y: 12,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
filter: "blur(0px)",
|
||||
y: 0,
|
||||
transition: {
|
||||
type: "spring",
|
||||
bounce: 0.3,
|
||||
duration: 1.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function HeroSection() {
|
||||
return (
|
||||
<>
|
||||
<main className="overflow-hidden">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 isolate hidden opacity-65 contain-strict lg:block"
|
||||
>
|
||||
<div className="w-140 h-320 -translate-y-87.5 absolute left-0 top-0 -rotate-45 rounded-full bg-[radial-gradient(68.54%_68.72%_at_55.02%_31.46%,hsla(0,0%,85%,.08)_0,hsla(0,0%,55%,.02)_50%,hsla(0,0%,45%,0)_80%)]" />
|
||||
<div className="h-320 absolute left-0 top-0 w-60 -rotate-45 rounded-full bg-[radial-gradient(50%_50%_at_50%_50%,hsla(0,0%,85%,.06)_0,hsla(0,0%,45%,.02)_80%,transparent_100%)] [translate:5%_-50%]" />
|
||||
<div className="h-320 -translate-y-87.5 absolute left-0 top-0 w-60 -rotate-45 bg-[radial-gradient(50%_50%_at_50%_50%,hsla(0,0%,85%,.04)_0,hsla(0,0%,45%,.02)_80%,transparent_100%)]" />
|
||||
</div>
|
||||
<section className="bg-background">
|
||||
<div className="relative pt-24 md:pt-36">
|
||||
<AnimatedGroup
|
||||
variants={{
|
||||
container: {
|
||||
visible: {
|
||||
transition: {
|
||||
delayChildren: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
item: {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
type: "spring",
|
||||
bounce: 0.3,
|
||||
duration: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
className="mask-b-from-35% mask-b-to-90% absolute inset-0 top-56 -z-20 lg:top-32"
|
||||
>
|
||||
<Image
|
||||
src="/assets/main-pic/main-pic.jpg"
|
||||
alt="background"
|
||||
className="hidden size-full lg:block"
|
||||
width="1150"
|
||||
height="613"
|
||||
/>
|
||||
</AnimatedGroup>
|
||||
|
||||
|
||||
<div className="mx-auto max-w-7xl px-6">
|
||||
<div className="text-center sm:mx-auto lg:mr-auto lg:mt-0">
|
||||
<AnimatedGroup variants={transitionVariants}>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="hover:bg-background dark:hover:border-t-border bg-muted group mx-auto flex w-fit items-center gap-4 rounded-full border border-border p-1 ps-4 shadow-md transition-colors duration-300 dark:border-border dark:shadow-lg"
|
||||
>
|
||||
<span className="text-body-sm text-foreground">
|
||||
معرفی نسل جدید یکپارچهسازی و ارتباطات هوشمند
|
||||
</span>
|
||||
<span className="dark:border-background block h-4 w-0.5 border-s bg-card dark:bg-muted"></span>
|
||||
|
||||
<div className="bg-background group-hover:bg-muted size-6 overflow-hidden rounded-full duration-500">
|
||||
<div className="flex w-12 translate-x-1/2 duration-500 ease-in-out group-hover:translate-x-0">
|
||||
<span className="flex size-6">
|
||||
<ArrowRight className="m-auto size-3" />
|
||||
</span>
|
||||
<span className="flex size-6">
|
||||
<ArrowLeft className="m-auto size-3" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</AnimatedGroup>
|
||||
|
||||
<TextEffect
|
||||
preset="fade-in-blur"
|
||||
speedSegment={0.3}
|
||||
as="h1"
|
||||
className="text-heading-1 mx-auto mt-8 max-w-4xl font-bold text-balance leading-relaxed tracking-normal lg:mt-16"
|
||||
>
|
||||
راهحلهای مدرن برای تعامل هوشمند با مشتری و سیستمهای شما
|
||||
</TextEffect>
|
||||
<TextEffect
|
||||
per="line"
|
||||
preset="fade-in-blur"
|
||||
speedSegment={0.3}
|
||||
delay={0.5}
|
||||
as="p"
|
||||
className="text-body mx-auto mt-8 max-w-2xl text-balance leading-relaxed tracking-normal"
|
||||
>
|
||||
ما مجموعهای از ابزارهای یکپارچهسازی، اتوماسیون و ارتباطات ارائه میکنیم که به شما کمک میکند دادهها، کاربران و فرآیندهای کسبوکارتان را از هر سیستم یا پلتفرمی — از وبسایت و CRM گرفته تا تلگرام و سرویسهای سفارشی — به شکلی دقیق، سریع و قابلاعتماد بهم متصل کنید.
|
||||
</TextEffect>
|
||||
|
||||
<AnimatedGroup
|
||||
variants={{
|
||||
container: {
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: 0.05,
|
||||
delayChildren: 0.75,
|
||||
},
|
||||
},
|
||||
},
|
||||
...transitionVariants,
|
||||
}}
|
||||
className="mt-12 flex flex-col items-center justify-center gap-4 md:flex-row"
|
||||
>
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<Link href="/contact">
|
||||
<span className="text-nowrap">شروع کنید</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</AnimatedGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatedGroup
|
||||
variants={{
|
||||
container: {
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: 0.05,
|
||||
delayChildren: 0.75,
|
||||
},
|
||||
},
|
||||
},
|
||||
...transitionVariants,
|
||||
}}
|
||||
>
|
||||
<div className="relative mt-8 overflow-hidden px-2 sm:mt-12 md:mt-20 p-2">
|
||||
<div className="ring-background bg-background relative max-w-5xl mx-auto overflow-hidden rounded-2xl border ring-1 aspect-[3/2]">
|
||||
<Image
|
||||
src="/assets/main-pic/Untitled-1 copy-min.jpg"
|
||||
alt="یکپارچهسازی سیستمها و شبکه دیجیتال"
|
||||
fill
|
||||
className="object-cover rounded-2xl inline-block my-0"
|
||||
quality={100}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedGroup>
|
||||
</div>
|
||||
</section>
|
||||
<section className="bg-background pb-16 pt-16 md:pb-32">
|
||||
<div className="group relative m-auto max-w-5xl px-6">
|
||||
<div className="absolute inset-0 z-10 flex scale-95 items-center justify-center opacity-0 duration-500 group-hover:scale-100 group-hover:opacity-100">
|
||||
<Link
|
||||
href="/"
|
||||
className="block text-sm duration-150 hover:text-accent transition-colors"
|
||||
>
|
||||
<span> اتصال به سایر سیستم ها </span>
|
||||
|
||||
<ChevronRight className="ml-1 inline-block size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="group-hover:blur-xs mx-auto mt-12 grid max-w-4xl grid-cols-4 gap-x-12 gap-y-8 transition-all duration-500 group-hover:opacity-50 sm:gap-x-16 sm:gap-y-14">
|
||||
<LogoItem
|
||||
icon="logos:telegram"
|
||||
alt="Telegram Logo"
|
||||
size="md"
|
||||
className="translate-x-1"
|
||||
/>
|
||||
<LogoItem
|
||||
icon="simple-icons:n8n"
|
||||
alt="n8n Logo"
|
||||
size="lg"
|
||||
className="translate-x-4 "
|
||||
/>
|
||||
<LogoItem
|
||||
icon="skill-icons:wordpress"
|
||||
alt="WordPress Logo"
|
||||
size="md"
|
||||
className="-translate-x-2"
|
||||
/>
|
||||
<LogoItem
|
||||
icon="logos:google-icon"
|
||||
alt="Google Logo"
|
||||
size="md"
|
||||
className="translate-x-5"
|
||||
/>
|
||||
<div className="col-span-4 flex justify-center gap-x-12 sm:gap-x-40">
|
||||
<LogoItem
|
||||
icon="logos:whatsapp-icon"
|
||||
alt="WhatsApp Logo"
|
||||
size="md"
|
||||
className="translate-x-2"
|
||||
/>
|
||||
<LogoItem
|
||||
icon="logos:microsoft-windows-icon"
|
||||
alt="Microsoft Windows Logo"
|
||||
size="md"
|
||||
className="translate-x-2"
|
||||
/>
|
||||
<LogoItem
|
||||
icon="skill-icons:github-dark"
|
||||
alt="github Logo"
|
||||
size="md"
|
||||
className="translate-x-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Autoplay, EffectCoverflow } from "swiper/modules";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css/effect-coverflow";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css";
|
||||
import "swiper/css/effect-cards";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface HeroPartnersProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const HeroPartners = ({ className }: HeroPartnersProps) => {
|
||||
const images = [
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random11.jpeg",
|
||||
alt: "لوگوی شرکت همکار و تیم",
|
||||
name: "شرکت راهکارهای فناوری",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random1.jpeg",
|
||||
alt: "دفتر مرکزی شرکت همکار",
|
||||
name: "آزمایشگاههای نوآوری",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random2.jpeg",
|
||||
alt: "جلسه همکاری همکاران",
|
||||
name: "همکاران جهانی",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random11.jpeg",
|
||||
alt: "لوگوی شرکت همکار و تیم",
|
||||
name: "شرکت راهکارهای فناوری",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random1.jpeg",
|
||||
alt: "دفتر مرکزی شرکت همکار",
|
||||
name: "آزمایشگاههای نوآوری",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random2.jpeg",
|
||||
alt: "جلسه همکاری همکاران",
|
||||
name: "همکاران جهانی",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random11.jpeg",
|
||||
alt: "لوگوی شرکت همکار و تیم",
|
||||
name: "شرکت راهکارهای فناوری",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random1.jpeg",
|
||||
alt: "دفتر مرکزی شرکت همکار",
|
||||
name: "آزمایشگاههای نوآوری",
|
||||
},
|
||||
{
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/lummi/random2.jpeg",
|
||||
alt: "جلسه همکاری همکاران",
|
||||
name: "همکاران جهانی",
|
||||
},
|
||||
];
|
||||
|
||||
const css = `
|
||||
.mySwiperHeroAbshodeHamkar {
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.mySwiperHeroAbshodeHamkar .swiper-slide {
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.mySwiperHeroAbshodeHamkar .swiper-slide img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.swiper-3d .swiper-slide-shadow-left {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.swiper-3d .swiper-slide-shadow-right{
|
||||
background: none;
|
||||
}
|
||||
`;
|
||||
return (
|
||||
<section className={cn("py-32 mx-auto", className)}>
|
||||
<style>{css}</style>
|
||||
<div className="container px-0 sm:px-8 mx-auto">
|
||||
<div className="mx-auto flex max-w-2xl flex-col items-center justify-center gap-4 px-8 sm:px-0">
|
||||
<h1 className="text-center text-heading-1 font-bold">
|
||||
همکاران ما
|
||||
</h1>
|
||||
<p className="text-center text-body text-muted-foreground">
|
||||
مدیریت معاملات بین همکاران صنف طلا با تمرکز بر امنیت، دقت و سرعت در مقیاس واقعی بازار.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-12 h-[420px] w-full lg:px-20">
|
||||
<div className="absolute left-0 z-10 h-full w-12 bg-gradient-to-r from-background to-transparent sm:via-background md:w-58 lg:left-16" />
|
||||
<div className="absolute right-0 z-10 h-full w-12 bg-gradient-to-l from-background to-transparent sm:via-background md:w-58 lg:right-16" />
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="w-full"
|
||||
>
|
||||
<Swiper
|
||||
autoplay={{
|
||||
delay: 1500,
|
||||
disableOnInteraction: false,
|
||||
}}
|
||||
effect="coverflow"
|
||||
grabCursor={true}
|
||||
slidesPerView="auto"
|
||||
centeredSlides={true}
|
||||
loop={true}
|
||||
coverflowEffect={{
|
||||
rotate: 50,
|
||||
stretch: 0,
|
||||
depth: 100,
|
||||
modifier: 1,
|
||||
slideShadows: true,
|
||||
}}
|
||||
className="mySwiperHeroAbshodeHamkar"
|
||||
modules={[EffectCoverflow, Autoplay]}
|
||||
>
|
||||
{images.map((image, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<img
|
||||
className="h-full w-full overflow-hidden rounded-3xl object-cover shadow-lg"
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto mt-4 flex w-fit justify-center">
|
||||
<Button size="lg" variant="primary">
|
||||
همکار شوید
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { HeroPartners };
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface HeroProductProps {
|
||||
heading?: string;
|
||||
headingLine2?: string;
|
||||
subheading?: string;
|
||||
description?: string;
|
||||
image?: {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
buttons?: {
|
||||
primary?: {
|
||||
text: string;
|
||||
url: string;
|
||||
};
|
||||
secondary?: {
|
||||
text: string;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const HeroProduct = ({
|
||||
subheading = "",
|
||||
heading = " سامانه معاملات آبشده همکاران",
|
||||
headingLine2,
|
||||
description = "سامانه تخصصی ثبت، مدیریت و تسویه معاملات آبشده برای فعالان حرفهای بازار.\nبدون تماسهای مکرر، بدون اختلاف حساب، با دسترسی لحظهای به مانده و ریز معاملات.",
|
||||
buttons = {
|
||||
primary: {
|
||||
text: "درخواست جلسه معرفی",
|
||||
url: "#",
|
||||
}
|
||||
},
|
||||
image = {
|
||||
src: "https://deifkwefumgah.cloudfront.net/shadcnblocks/block/placeholder-dark-7-tall.svg",
|
||||
alt: "Placeholder",
|
||||
},
|
||||
className,
|
||||
}: HeroProductProps) => {
|
||||
return (
|
||||
<section className={cn("pt-24 pb-16 md:py-24 lg:py-32", className)}>
|
||||
<div className="container mx-auto flex flex-col items-center gap-10 px-6 lg:my-0 lg:flex-row">
|
||||
<div className="flex flex-col gap-7 text-center lg:w-2/3 lg:text-start">
|
||||
<h1 className="text-heading-1 font-bold leading-[var(--line-height-heading)] text-foreground">
|
||||
<span>{heading}</span>
|
||||
{headingLine2 != null && headingLine2 !== "" && (
|
||||
<>
|
||||
<br />
|
||||
<span>{headingLine2}</span>
|
||||
</>
|
||||
)}
|
||||
{!headingLine2 && subheading ? (
|
||||
<span className="text-muted-foreground">{subheading}</span>
|
||||
) : null}
|
||||
</h1>
|
||||
<p className="max-w-2xl text-body leading-[var(--line-height-body)] text-justify text-muted-foreground whitespace-pre-line">
|
||||
{description}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 lg:justify-start">
|
||||
<Button asChild size="lg" variant="primary">
|
||||
<a href={buttons.primary?.url}>
|
||||
<ArrowUpRight className="size-4" />
|
||||
<span className="whitespace-nowrap">{buttons.primary?.text}</span>
|
||||
</a>
|
||||
</Button>
|
||||
{buttons.secondary?.url && buttons.secondary?.text && (
|
||||
<Button asChild size="lg" variant="secondary">
|
||||
<a href={buttons.secondary.url}>{buttons.secondary.text}</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<div className="absolute top-2.5 left-1/2! h-[92%]! w-[69%]! -translate-x-[52%] overflow-hidden rounded-[35px]">
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="size-full object-cover object-[50%_0%]"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
className="relative z-10"
|
||||
src="https://deifkwefumgah.cloudfront.net/shadcnblocks/block/mockups/phone-2.png"
|
||||
width={450}
|
||||
height={889}
|
||||
alt="iphone"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export { HeroProduct };
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
import Footer5 from './footer'
|
||||
|
||||
|
||||
export default function HeroSectionTailark() {
|
||||
return (
|
||||
<>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
'use client'
|
||||
|
||||
import { CircleArrowRight, Files, Settings } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface AboutCompanySectionProps {
|
||||
className?: string
|
||||
mainHeading: string
|
||||
description?: string
|
||||
title: string
|
||||
subtitle: string
|
||||
bio: string
|
||||
vision: string
|
||||
philosophyHeading: string
|
||||
outro: string
|
||||
imageLogo?: { src: string; alt: string }
|
||||
imageOffice?: { src: string; alt: string }
|
||||
}
|
||||
|
||||
export function AboutCompanySection({
|
||||
className,
|
||||
mainHeading,
|
||||
description,
|
||||
title,
|
||||
subtitle,
|
||||
bio,
|
||||
vision,
|
||||
philosophyHeading,
|
||||
outro,
|
||||
imageLogo,
|
||||
imageOffice,
|
||||
}: AboutCompanySectionProps) {
|
||||
const valueCards = [
|
||||
{
|
||||
icon: Files,
|
||||
heading: philosophyHeading,
|
||||
body: vision,
|
||||
},
|
||||
{
|
||||
icon: CircleArrowRight,
|
||||
heading: 'همراهی تخصصی',
|
||||
body: 'تیم ما با تمرکز بر کیفیت اجرا، پاسخگویی سریع و توسعه مستمر کنار کسبوکار شما است.',
|
||||
},
|
||||
{
|
||||
icon: Settings,
|
||||
heading: 'رویکرد اجرایی',
|
||||
body: outro,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<section className={cn('py-20 md:py-24 lg:py-32', className)} dir="rtl">
|
||||
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6 flex flex-col gap-14 lg:gap-24">
|
||||
<div className="flex flex-col items-center gap-4 text-center md:gap-6">
|
||||
<h1 className="text-heading-1 font-bold text-foreground">{mainHeading}</h1>
|
||||
<p className="text-body mx-auto max-w-3xl text-muted-foreground md:text-body-sm">
|
||||
{description ?? bio}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<div className="overflow-hidden rounded-2xl border border-border bg-card">
|
||||
{imageOffice ? (
|
||||
<img
|
||||
src={imageOffice.src}
|
||||
alt={imageOffice.alt}
|
||||
className="h-auto w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full min-h-64 items-center justify-center bg-muted p-8">
|
||||
<div className="text-center">
|
||||
<p className="text-heading-3 font-semibold text-foreground">{title}</p>
|
||||
<p className="mt-2 text-body-sm text-muted-foreground">{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-between gap-8 rounded-2xl border border-border bg-accent/30 p-8 md:p-10">
|
||||
<div className="flex items-center gap-4">
|
||||
{imageLogo ? (
|
||||
<img src={imageLogo.src} alt={imageLogo.alt} className="h-14 w-14 rounded-xl object-cover" />
|
||||
) : (
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-muted text-heading-4 font-semibold text-muted-foreground">
|
||||
{title.slice(0, 1)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="text-caption font-medium text-muted-foreground">معرفی برند</p>
|
||||
<h2 className="text-heading-3 font-semibold text-foreground">{title}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-body leading-[var(--line-height-relaxed)] text-foreground">{bio}</p>
|
||||
<p className="text-body-sm text-muted-foreground">{vision}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-8 md:gap-14">
|
||||
<div className="max-w-3xl">
|
||||
<h2 className="text-heading-2 mb-4 font-semibold text-foreground">{philosophyHeading}</h2>
|
||||
<p className="text-body text-muted-foreground">{outro}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
{valueCards.map(({ icon: Icon, heading, body }, idx) => (
|
||||
<div key={idx} className="flex flex-col rounded-2xl border border-border bg-card p-6">
|
||||
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-accent">
|
||||
<Icon className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<h3 className="text-heading-4 mb-3 font-medium text-foreground">{heading}</h3>
|
||||
<p className="text-body-sm text-muted-foreground">{body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
motion,
|
||||
useAnimationFrame,
|
||||
useMotionTemplate,
|
||||
useMotionValue,
|
||||
useTransform,
|
||||
animate,
|
||||
} from "motion/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function AppleCardsCarousel({
|
||||
items,
|
||||
direction = "left",
|
||||
speed = "fast",
|
||||
pauseOnHover = true,
|
||||
className,
|
||||
}: {
|
||||
items: {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
src?: string;
|
||||
}[];
|
||||
direction?: "left" | "right";
|
||||
speed?: "fast" | "normal" | "slow";
|
||||
pauseOnHover?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [start, setStart] = useState(false);
|
||||
useEffect(() => {
|
||||
setStart(true);
|
||||
}, []);
|
||||
|
||||
const baseVelocity = {
|
||||
fast: 50,
|
||||
normal: 35,
|
||||
slow: 20,
|
||||
};
|
||||
|
||||
const baseX = useMotionValue(0);
|
||||
const directionFactor = React.useRef<number>(1);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
useAnimationFrame((t, delta) => {
|
||||
if (hovered) return;
|
||||
|
||||
let moveBy = directionFactor.current * baseVelocity[speed] * (delta / 1000);
|
||||
|
||||
if (direction === "left") {
|
||||
directionFactor.current = -1;
|
||||
} else {
|
||||
directionFactor.current = 1;
|
||||
}
|
||||
|
||||
moveBy += directionFactor.current * moveBy * 0.5;
|
||||
|
||||
baseX.set(baseX.get() + moveBy);
|
||||
});
|
||||
|
||||
const scrollLeft = () => {
|
||||
if (scrollRef.current) {
|
||||
// Calculate responsive card width + gap
|
||||
const cardWidth = window.innerWidth < 640 ? 280 : window.innerWidth < 768 ? 320 : 350;
|
||||
const scrollAmount = cardWidth + 16; // card width + gap
|
||||
const newX = baseX.get() + scrollAmount;
|
||||
animate(baseX, newX, { duration: 0.5, ease: "easeOut" });
|
||||
}
|
||||
};
|
||||
|
||||
const scrollRight = () => {
|
||||
if (scrollRef.current) {
|
||||
// Calculate responsive card width + gap
|
||||
const cardWidth = window.innerWidth < 640 ? 280 : window.innerWidth < 768 ? 320 : 350;
|
||||
const scrollAmount = cardWidth + 16; // card width + gap
|
||||
const newX = baseX.get() - scrollAmount;
|
||||
animate(baseX, newX, { duration: 0.5, ease: "easeOut" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
"relative w-full overflow-hidden [mask-image:linear-gradient(to_right,transparent_0%,white_10%,white_90%,transparent_100%)]",
|
||||
className
|
||||
)}
|
||||
onMouseEnter={() => pauseOnHover && setHovered(true)}
|
||||
onMouseLeave={() => pauseOnHover && setHovered(false)}
|
||||
>
|
||||
<div className="flex justify-center">
|
||||
<motion.div
|
||||
ref={scrollRef}
|
||||
style={{
|
||||
x: baseX,
|
||||
willChange: "transform",
|
||||
}}
|
||||
className="flex flex-nowrap gap-4 items-center"
|
||||
>
|
||||
{start &&
|
||||
items.map((item, idx) => (
|
||||
<div
|
||||
key={`item-${idx}`}
|
||||
className="relative shrink-0 w-[280px] sm:w-[320px] md:w-[350px]"
|
||||
>
|
||||
<div className="group relative h-[400px] sm:h-[420px] md:h-[450px] w-full overflow-hidden rounded-3xl p-6 sm:p-7 md:p-8">
|
||||
{item.src && (
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={item.src}
|
||||
alt={item.title}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-black/60 via-black/40 to-black/60" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_bottom_right,var(--background)_0%,rgba(255,255,255,0.08)_50%,transparent_60%)] opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
<div className="relative z-10">
|
||||
<div className="mb-4 flex items-center gap-4">
|
||||
{item.icon}
|
||||
<div>
|
||||
<h3 className="text-heading-3 font-semibold text-overlay-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-body-sm text-overlay-foreground/90">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{start &&
|
||||
items.map((item, idx) => (
|
||||
<div
|
||||
key={`item-duplicate-${idx}`}
|
||||
className="relative shrink-0 w-[280px] sm:w-[320px] md:w-[350px]"
|
||||
>
|
||||
<div className="group relative h-[400px] sm:h-[420px] md:h-[450px] w-full overflow-hidden rounded-3xl p-6 sm:p-7 md:p-8">
|
||||
{item.src && (
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={item.src}
|
||||
alt={item.title}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-black/60 via-black/40 to-black/60" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_bottom_right,var(--background)_0%,rgba(255,255,255,0.08)_50%,transparent_60%)] opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
<div className="relative z-10">
|
||||
<div className="mb-4 flex items-center gap-4">
|
||||
{item.icon}
|
||||
<div>
|
||||
<h3 className="text-heading-3 font-semibold text-overlay-foreground">
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-body-sm text-overlay-foreground/90">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-lg"
|
||||
onClick={scrollRight}
|
||||
aria-label="Scroll left"
|
||||
>
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-lg"
|
||||
onClick={scrollLeft}
|
||||
aria-label="Scroll right"
|
||||
>
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user