refactor: remove disabled home sections and their unused assets
@@ -1,8 +1,2 @@
|
|||||||
# Copy this file to .env.local and fill in your real keys.
|
# Optional backend API base URL. Copy to .env.local to configure.
|
||||||
|
|
||||||
# Neshan (نشان) map API key — get one from https://platform.neshan.org
|
|
||||||
# Used by components/MapSection.jsx for the static map image.
|
|
||||||
NEXT_PUBLIC_NESHAN_API_KEY=
|
|
||||||
|
|
||||||
|
|
||||||
NEXT_PUBLIC_API_BASE_URL=https://your-real-backend.com
|
NEXT_PUBLIC_API_BASE_URL=https://your-real-backend.com
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="bb7b50a5-be9c-4cd7-a05b-979f0e91b173" name="Changes" comment="feat: add landing page device images and fix machine branding">
|
<list default="true" id="bb7b50a5-be9c-4cd7-a05b-979f0e91b173" name="Changes" comment="feat: add landing page device images and fix machine branding">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/public/images/machine-slide-2.webp" beforeDir="false" afterPath="$PROJECT_DIR$/public/images/machine-slide-2.webp" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -204,7 +203,8 @@
|
|||||||
<workItem from="1789361888102" duration="68000" />
|
<workItem from="1789361888102" duration="68000" />
|
||||||
<workItem from="1789362025574" duration="23000" />
|
<workItem from="1789362025574" duration="23000" />
|
||||||
<workItem from="1789362137185" duration="377000" />
|
<workItem from="1789362137185" duration="377000" />
|
||||||
<workItem from="1789362644534" duration="4681000" />
|
<workItem from="1789362644534" duration="6512000" />
|
||||||
|
<workItem from="1789383269194" duration="679000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="update">
|
<task id="LOCAL-00001" summary="update">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -462,7 +462,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1789368598921</updated>
|
<updated>1789368598921</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="33" />
|
<task id="LOCAL-00033" summary="feat: add landing page device images and fix machine branding">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1789369799782</created>
|
||||||
|
<option name="number" value="00033" />
|
||||||
|
<option name="presentableId" value="LOCAL-00033" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1789369799782</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="34" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
|||||||
@@ -1,132 +1,56 @@
|
|||||||
# روبات مارکت — Vending Machine Showcase
|
# روبات مارکت — Vending Machine Showcase
|
||||||
|
|
||||||
پروژه Next.js (App Router) صفحه معرفی محصولات «روبات مارکت»، با انیمیشنهای GSAP/ScrollTrigger،
|
صفحه معرفی دستگاههای وندینگ روبات مارکت، ساختهشده با Next.js، React و Ant Design.
|
||||||
مدیریت سبد خرید با Redux Toolkit، حالت تیره/روشن، دو زبانه (فارسی/انگلیسی)، نقشه، فرم تیکت
|
|
||||||
پشتیبانی، و ویجت چت.
|
|
||||||
|
|
||||||
## اجرای پروژه
|
## اجرا
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
سپس `http://localhost:3000` را باز کنید.
|
سایت در http://localhost:3000 در دسترس است.
|
||||||
|
|
||||||
## ساختار پروژه
|
## ساخت نسخه نهایی
|
||||||
|
|
||||||
```
|
```bash
|
||||||
app/
|
npm run build
|
||||||
layout.jsx # پوسته HTML، فونت وزیرمتن
|
npm start
|
||||||
page.jsx # چیدمان کامل صفحه
|
|
||||||
providers.jsx # Redux + Theme + Language + AntD ConfigProvider
|
|
||||||
globals.css # توکنهای رنگ دارک/لایت، جلوه اسپاتلایت، تایپرایتر
|
|
||||||
context/
|
|
||||||
ThemeContext.jsx # حالت دارک/لایت (ذخیره در localStorage)
|
|
||||||
LanguageContext.jsx # زبان فا/en (ذخیره در localStorage + جهت صفحه)
|
|
||||||
store/ # Redux (سبد خرید)
|
|
||||||
data/
|
|
||||||
content.js # منبع واحد محتوای دوزبانه (fa/en) — همهی متنها اینجاست
|
|
||||||
components/
|
|
||||||
Header.jsx # هدر + سوییچ زبان/تم + دکمه ورود به پنل کاربری
|
|
||||||
ThemeToggle.jsx / LanguageToggle.jsx
|
|
||||||
HeroSection.jsx # هدلاین تایپشونده + پسزمینه رنگی در حال چرخش
|
|
||||||
HorizontalShowcase.jsx # اسکرول افقی پینشده + ویدیوی پسزمینه
|
|
||||||
VendingCard.jsx # کارت دستگاه + جلوه اسپاتلایت
|
|
||||||
FloatingItems.jsx
|
|
||||||
FeaturesSection.jsx
|
|
||||||
AboutSection.jsx
|
|
||||||
LocationsSection.jsx
|
|
||||||
MapSection.jsx # نقشه Neshan (کلید API را خودتان اضافه میکنید)
|
|
||||||
CatalogSection.jsx # کاتالوگ محصولات قابل سفارش
|
|
||||||
IntroVideoSection.jsx # ویدیوی معرفی شرکت
|
|
||||||
TicketForm.jsx # فرم تیکت پشتیبانی
|
|
||||||
ChatWidget.jsx # آیکون شناور چت + مودال گفتگو
|
|
||||||
CartDrawer.jsx
|
|
||||||
Footer.jsx
|
|
||||||
public/
|
|
||||||
images/, gifs/, video/ # داراییهای placeholder — در پیام قبلی توضیح داده شده
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## ویژگیهای اضافهشده در این نسخه
|
خروجی standalone برای اجرای Docker تنظیم شده است.
|
||||||
|
|
||||||
### نقشه (Neshan)
|
## ساختار و بخشهای فعال
|
||||||
`components/MapSection.jsx` از **Neshan Static Map API** استفاده میکند (یک تصویر ساده، بدون نیاز
|
|
||||||
به اسکریپت/SDK جاوااسکریپت). کلید API را اینجا وارد کنید:
|
|
||||||
|
|
||||||
1. یک فایل `.env.local` در ریشه پروژه بسازید (از روی `.env.local.example` کپی کنید)
|
- `app/page.jsx`: صفحه اصلی شامل هدر، سبد خرید، هیرو، ویترین دستگاهها، مزایا، درباره ما، محلهای مناسب نصب و فوتر.
|
||||||
2. مقدار `NEXT_PUBLIC_NESHAN_API_KEY` را با کلید واقعی خودتان از
|
- `components/HeroSection.jsx`: اسلایدر تصاویر دستگاهها.
|
||||||
[platform.neshan.org](https://platform.neshan.org) پر کنید
|
- `components/ShowcaseSection.jsx`: انتخاب نمایش موبایل یا ویترین افقی دسکتاپ.
|
||||||
|
- `components/HorizontalShowcase.jsx`: ویترین افقی با GSAP و ویدیوی پسزمینه وابسته به اسکرول.
|
||||||
|
- `components/MobileShowcase.jsx`: فهرست دستگاهها در موبایل.
|
||||||
|
- `components/VendingCard.jsx` و `components/FloatingItems.jsx`: نمایش دستگاه و جلوههای تصویری.
|
||||||
|
- `components/CartDrawer.jsx` و `store/`: سبد خرید و مدیریت تعداد دستگاهها با Redux.
|
||||||
|
- `data/content.js`: محتوای فارسی و انگلیسی، مشخصات و قیمت دستگاهها و مسیر رسانهها.
|
||||||
|
- `app/context/` و `app/providers.jsx`: زبان، جهت صفحه، تم و تنظیمات Ant Design.
|
||||||
|
- `app/globals.css`: رنگها و استایلهای مشترک.
|
||||||
|
- `public/`: تصاویر دستگاهها، اقلام شناور، گیفها و ویدیوی ویترین.
|
||||||
|
|
||||||
تا وقتی کلید خالی باشد، بهجای نقشه یک پیام راهنما («برای فعال شدن نقشه، کلید API نشان را در
|
## زبان و تم
|
||||||
تنظیمات پروژه قرار دهید») نمایش داده میشود — سایت خراب نمیشود. نقشه در حالت تیره/روشن هم استایل
|
|
||||||
متفاوتی میگیرد (`standard-night` / `standard-day`). لینک «مشاهده در نشان» کنار هر آدرس به نقشه
|
|
||||||
تعاملی neshan.org میرود و نیازی به کلید ندارد. مختصات فعلی در `data/content.js`
|
|
||||||
(`mapLocations`) تقریبی هستند — با پین دقیق جایگزین کنید.
|
|
||||||
|
|
||||||
### کاتالوگ محصولات
|
زبان فارسی/انگلیسی و حالت روشن/تیره از هدر تغییر میکنند و ترجیح کاربر در localStorage ذخیره میشود.
|
||||||
`components/CatalogSection.jsx` — گرید محصولاتی (زعفران، تنقلات، نوشیدنی، قهوه، چای و ...) که
|
ویترین افقی برای محاسبات اسکرول همیشه جهت LTR دارد، اما متن کارتها جهت زبان فعال را رعایت میکند.
|
||||||
مشتریان دستگاه میتوانند با قیمت کمتر از بازار سفارش دهند. هر آیتم قیمت بازار (خطخورده) و قیمت
|
|
||||||
ویژه را نشان میدهد و دکمه «افزودن به سفارش» آن را به همان سبد خرید Redux اضافه میکند (در کنار
|
|
||||||
دستگاهها). لیست محصولات، قیمتها و تصاویر در `data/content.js` (کلید `catalog`) قابل ویرایش است؛
|
|
||||||
تصاویر placeholder در `public/images/catalog/` قرار دارند.
|
|
||||||
|
|
||||||
### سکشن ویدیوی معرفی شرکت
|
## ورود و سبد خرید
|
||||||
`components/IntroVideoSection.jsx` — یک پلیر ویدیوی مستقل (جدا از ویدیوی پسزمینه اسکرول افقی) با
|
|
||||||
پوستر و دکمه پخش استاندارد مرورگر. برای جایگزینی، فایل واقعی را در
|
|
||||||
`public/video/company-intro-placeholder.mp4` بگذارید (یا مسیر `introVideoSrc` را در
|
|
||||||
`data/content.js` عوض کنید)؛ پوستر هم از `public/images/video-poster.svg` میآید.
|
|
||||||
|
|
||||||
### رنگبندی متناوب سکشنها
|
دکمه ورود به https://panel.my-rm.com متصل است.
|
||||||
هر سکشن اصلی صفحه (بهجز هیرو و بخش اسکرول افقی که پسزمینه مخصوص خودشان را دارند) بهترتیب بین سه
|
سبد خرید در حافظه نگهداری میشود و با بارگذاری مجدد صفحه پاک میشود.
|
||||||
تن رنگی برند میچرخد: `bg-page` → `bg-surface` → `bg-elevated` → ... بههمراه یک خط جداکننده ظریف
|
پرداخت هنوز متصل نیست؛ دکمه پرداخت در `CartDrawer.jsx` پیام نمایشی نشان میدهد.
|
||||||
بالای هر بخش. اینها همان توکنهای دارک/لایت هستند، پس با تغییر تم هم هماهنگ میمانند. برای تغییر
|
|
||||||
ترتیب یا اضافه کردن سکشن جدید، کافیست یکی از این سه کلاس را روی `<section>` بگذارید.
|
|
||||||
|
|
||||||
### حالت دارک/لایت
|
## تنظیم API
|
||||||
دکمه سوییچ در هدر (آیکون خورشید/ماه). ترجیح در `localStorage` ذخیره میشود و در اولین بار بر اساس
|
|
||||||
تنظیمات سیستم کاربر تعیین میشود. رنگبندی از طریق متغیرهای CSS در `app/globals.css`
|
|
||||||
(`--color-bg-page`, `--color-text-primary`, ...) و کلاسهای کمکی `bg-page` / `bg-surface` /
|
|
||||||
`text-primary` / `text-secondary` / `border-subtle` مدیریت میشود؛ برای هر رنگ جدید فقط همینجا را
|
|
||||||
ویرایش کنید. تم Ant Design هم بهصورت خودکار (`darkAlgorithm` / `defaultAlgorithm`) هماهنگ میشود.
|
|
||||||
|
|
||||||
### دو زبانه (فارسی / انگلیسی)
|
برای تعیین آدرس بکاند، از `.env.local.example` یک فایل `.env.local` بسازید و
|
||||||
دکمه «EN / فا» در هدر. تمام متنهای سایت (منو، هیرو، محصولات، درباره ما، مکانهای نصب، فرم تیکت،
|
`NEXT_PUBLIC_API_BASE_URL` را تنظیم کنید. کلاینت مشترک API در `lib/axios.js` قرار دارد.
|
||||||
چت، فوتر) از `data/content.js` خوانده میشوند — یک آبجکت با کلیدهای `fa` و `en`. تغییر زبان همچنین
|
|
||||||
جهت صفحه (`dir`) و `lang` تگ `html` را بهروزرسانی میکند. برای اضافه یا اصلاح متن، فقط
|
|
||||||
`data/content.js` را ویرایش کنید؛ کامپوننتها نیازی به تغییر ندارند.
|
|
||||||
|
|
||||||
> نکته فنی: بخش اسکرول افقی (`HorizontalShowcase`) بهعمد همیشه `dir="ltr"` روی خود track دارد
|
## جایگزینی رسانهها
|
||||||
> (چون محاسبات GSAP به آن نیاز دارد)، اما هر کارت داخلش جهت متن را بر اساس زبان فعال تنظیم میکند.
|
|
||||||
|
|
||||||
### فرم تیکت پشتیبانی
|
فایلهای واقعی را با مسیرهای موجود در `data/content.js` جایگزین کنید یا مسیرها را در همان فایل تغییر دهید.
|
||||||
`components/TicketForm.jsx` — فرم Ant Design با اعتبارسنجی (نام، تلفن، ایمیل اختیاری، موضوع،
|
ویدیوی پسزمینه ویترین از `public/video/showcase-placeholder.mp4` خوانده میشود.
|
||||||
اولویت، پیام). ارسال فعلاً فقط یک پیام موفقیت نمایش میدهد؛ بلوک کامنتشده
|
|
||||||
`// TODO: Connect to the real ticketing/support API` دقیقاً نشان میدهد کجا باید axios را وصل کنید.
|
|
||||||
|
|
||||||
### ویجت چت
|
|
||||||
دکمه شناور گوشه صفحه (`components/ChatWidget.jsx`) یک مودال گفتگوی ساده باز میکند. پیامها فعلاً
|
|
||||||
در state محلی نگهداری میشوند و یک پاسخ خودکار نمایشی میدهند. بلوک
|
|
||||||
`// TODO: Connect to a real chat/support backend` محل اتصال به بکاند واقعی (یا وبسوکت) را مشخص
|
|
||||||
کرده.
|
|
||||||
|
|
||||||
### هیرو: تایپرایتر + پسزمینه رنگی در حال تغییر
|
|
||||||
هدلاین با افکت تایپرایتر بین چند جمله (`data/content.js` → `hero.typingPhrases`) میچرخد.
|
|
||||||
پسزمینه بهصورت حلقهای بین سبز برند (`#00a693`)، مشکی و سفید تغییر رنگ میدهد (با GSAP). یک لایه
|
|
||||||
scrim تیره روی آن قرار دارد تا متن در هر سه حالت خوانا بماند. وقتی ویدیوی هیرو را آماده کردید،
|
|
||||||
داخل `components/HeroSection.jsx` یک بلوک `TODO` کامنتشده دقیقاً نشان میدهد چطور بهجای پسزمینه
|
|
||||||
رنگی، ویدیو را جایگزین کنید (فقط از حالت کامنت خارجش کنید).
|
|
||||||
|
|
||||||
### ورود به پنل کاربری
|
|
||||||
دکمه «ورود به پنل کاربری» / «Sign In» در هدر اضافه شده (در `components/Header.jsx`). چون گفتید
|
|
||||||
لینکدهی را خودتان انجام میدهید، فعلاً فقط یک `onClick` خالی با کامنت TODO دارد — مسیر واقعی پنل
|
|
||||||
کاربری را همانجا وصل کنید.
|
|
||||||
|
|
||||||
## جایگزینی داراییهای واقعی (تصاویر/گیف/ویدیو)
|
|
||||||
همانطور که قبلاً توضیح داده شد، فایلهای داخل `public/images`, `public/gifs`, `public/video`
|
|
||||||
placeholder هستند. برای جایگزینی، فایل جدید را با همان نام/مسیر جایگزین کنید یا مسیر را در
|
|
||||||
`data/content.js` تغییر دهید.
|
|
||||||
|
|
||||||
## اتصال درگاه پرداخت
|
|
||||||
در `components/CartDrawer.jsx` داخل `handleCheckout` بلوک `// TODO: Connect to payment gateway API`
|
|
||||||
با نمونهکد axios کامنتشده وجود دارد.
|
|
||||||
|
|||||||
@@ -219,35 +219,3 @@ html[dir='rtl'] body {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* نوار لوگوی بیپایان (trusted-by marquee) */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
@keyframes marqueeScroll {
|
|
||||||
from {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
/* دقیقاً نصف عرض، چون لیست دو برابر شده */
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-marquee {
|
|
||||||
animation: marqueeScroll 28s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-marquee:hover {
|
|
||||||
animation-play-state: paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Chat widget */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
.chat-bubble {
|
|
||||||
max-width: 80%;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-radius: 14px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,32 +6,18 @@ import HeroSection from '@/components/HeroSection';
|
|||||||
import CartDrawer from '@/components/CartDrawer';
|
import CartDrawer from '@/components/CartDrawer';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
import Header from '@/components/Header';
|
import Header from '@/components/Header';
|
||||||
import TrustedBySection from '@/components/TrustedBySection';
|
|
||||||
import CatalogSection from '@/components/CatalogSection';
|
|
||||||
import IntroVideoSection from '@/components/IntroVideoSection';
|
|
||||||
import MapSection from '@/components/MapSection';
|
|
||||||
import TicketForm from '@/components/TicketForm';
|
|
||||||
import ChatWidget from '@/components/ChatWidget';
|
|
||||||
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="relative bg-page">
|
<main className="relative bg-page">
|
||||||
<Header/>
|
<Header/>
|
||||||
<CartDrawer/>
|
<CartDrawer/>
|
||||||
{/*<ChatWidget/>*/}
|
|
||||||
<HeroSection/>
|
<HeroSection/>
|
||||||
<ShowcaseSection/>
|
<ShowcaseSection/>
|
||||||
{/*<TrustedBySection/>*/}
|
|
||||||
<FeaturesSection/>
|
<FeaturesSection/>
|
||||||
<AboutSection/>
|
<AboutSection/>
|
||||||
{/*<CatalogSection/>*/}
|
|
||||||
<LocationsSection/>
|
<LocationsSection/>
|
||||||
{/*<IntroVideoSection/>*/}
|
|
||||||
{/*<MapSection/>*/}
|
|
||||||
{/*<TicketForm/>*/}
|
|
||||||
<Footer/>
|
<Footer/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
// کاتالوگ محصولات جانبی (زعفران، تنقلات، نوشیدنی و ...) که مشتریان دستگاه با قیمت کمتر از بازار
|
|
||||||
// میتوانند سفارش دهند. دکمه «افزودن به سفارش» از همان سبد خرید Redux دستگاهها استفاده میکند.
|
|
||||||
|
|
||||||
import { Button, message } from 'antd';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { addToCart } from '@/store/cartSlice';
|
|
||||||
import { useLanguage } from '@/app/context/LanguageContext';
|
|
||||||
|
|
||||||
function toLocaleNumber(n, locale) {
|
|
||||||
return n.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CatalogSection() {
|
|
||||||
const { t, locale } = useLanguage();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const currencySuffix = locale === 'fa' ? ' ریال' : ' Rials';
|
|
||||||
|
|
||||||
const handleAdd = (item) => {
|
|
||||||
dispatch(
|
|
||||||
addToCart({
|
|
||||||
id: `catalog-${item.id}`,
|
|
||||||
name: item.name,
|
|
||||||
priceNumeric: item.priceNumeric,
|
|
||||||
priceLabel: `${toLocaleNumber(item.priceNumeric, locale)}${currencySuffix}`,
|
|
||||||
image: item.image,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
message.success(t.catalog.addToast(item.name));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="catalog" className="relative px-6 md:px-16 py-20 md:py-28 bg-page border-t border-subtle">
|
|
||||||
<div className="max-w-6xl mx-auto">
|
|
||||||
<div className="text-center mb-4">
|
|
||||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.catalog.eyebrow}</span>
|
|
||||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3">{t.catalog.title}</h2>
|
|
||||||
</div>
|
|
||||||
<p className="text-secondary text-sm md:text-base text-center max-w-2xl mx-auto mb-12">
|
|
||||||
{t.catalog.subtitle}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
|
||||||
{t.catalog.items.map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.id}
|
|
||||||
className="bg-surface border border-subtle rounded-2xl p-5 flex flex-col hover:border-brand-500/60 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-4 mb-4">
|
|
||||||
<img src={item.image} alt={item.name} className="w-16 h-16 object-contain shrink-0" />
|
|
||||||
<div className="min-w-0">
|
|
||||||
<span className="text-[11px] text-brand-400 font-semibold">{item.category}</span>
|
|
||||||
<h3 className="text-primary font-bold text-sm leading-6">{item.name}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-auto flex items-end justify-between gap-2">
|
|
||||||
<div>
|
|
||||||
<p className="text-xs text-secondary line-through opacity-70">
|
|
||||||
{toLocaleNumber(item.originalPriceNumeric, locale)}
|
|
||||||
{currencySuffix}
|
|
||||||
</p>
|
|
||||||
<p className="text-base font-bold text-primary">
|
|
||||||
{toLocaleNumber(item.priceNumeric, locale)}
|
|
||||||
{currencySuffix}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size="middle"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={() => handleAdd(item)}
|
|
||||||
>
|
|
||||||
{t.catalog.addToOrder}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
// آیکون شناور چت گوشه صفحه؛ با کلیک یک مودال گفتگوی ساده باز میشود (فعلاً بدون اتصال به بکاند واقعی).
|
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
import { Modal, Input, Button, Tooltip } from 'antd';
|
|
||||||
import { MessageOutlined, SendOutlined, CloseOutlined } from '@ant-design/icons';
|
|
||||||
import { useLanguage } from '@/app/context/LanguageContext';
|
|
||||||
|
|
||||||
export default function ChatWidget() {
|
|
||||||
const { t, dir } = useLanguage();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [draft, setDraft] = useState('');
|
|
||||||
const [messages, setMessages] = useState([]);
|
|
||||||
const scrollRef = useRef(null);
|
|
||||||
|
|
||||||
// Seed the bot greeting once, in the current language.
|
|
||||||
useEffect(() => {
|
|
||||||
setMessages([{ from: 'bot', text: t.chat.initialBotMessage }]);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [t.chat.initialBotMessage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
|
|
||||||
}, [messages, open]);
|
|
||||||
|
|
||||||
const handleSend = () => {
|
|
||||||
const text = draft.trim();
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
setMessages((prev) => [...prev, { from: 'user', text }]);
|
|
||||||
setDraft('');
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
|
||||||
// TODO: Connect to a real chat/support backend (e.g. via axios or a
|
|
||||||
// websocket) instead of this local placeholder reply.
|
|
||||||
//
|
|
||||||
// import axios from 'axios';
|
|
||||||
// const { data } = await axios.post('/api/chat', { message: text });
|
|
||||||
// setMessages((prev) => [...prev, { from: 'bot', text: data.reply }]);
|
|
||||||
// api() <===> todo : use this func to fetch ...
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
|
||||||
setTimeout(() => {
|
|
||||||
setMessages((prev) => [...prev, { from: 'bot', text: t.chat.fallbackReply }]);
|
|
||||||
}, 600);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Tooltip title={t.chat.fabLabel} placement="left">
|
|
||||||
<button
|
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
aria-label={t.chat.fabLabel}
|
|
||||||
className="fixed bottom-6 z-40 w-14 h-14 rounded-full bg-brand hover:bg-brand-400 text-ink-950 shadow-brandGlow flex items-center justify-center text-2xl transition-transform hover:scale-105"
|
|
||||||
style={dir === 'rtl' ? { left: '1.5rem' } : { right: '1.5rem' }}
|
|
||||||
>
|
|
||||||
<MessageOutlined />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
onCancel={() => setOpen(false)}
|
|
||||||
footer={null}
|
|
||||||
closeIcon={<CloseOutlined />}
|
|
||||||
title={t.chat.title}
|
|
||||||
centered
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={scrollRef}
|
|
||||||
className="flex flex-col gap-3 h-80 overflow-y-auto py-2 px-1"
|
|
||||||
>
|
|
||||||
{messages.map((m, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className={`chat-bubble ${
|
|
||||||
m.from === 'user'
|
|
||||||
? 'self-end bg-brand text-ink-950'
|
|
||||||
: 'self-start bg-surface-strong text-primary'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{m.text}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2 mt-3 border-t border-subtle pt-3">
|
|
||||||
<Input
|
|
||||||
size="large"
|
|
||||||
value={draft}
|
|
||||||
onChange={(e) => setDraft(e.target.value)}
|
|
||||||
onPressEnter={handleSend}
|
|
||||||
placeholder={t.chat.placeholder}
|
|
||||||
/>
|
|
||||||
<Button type="primary" size="large" icon={<SendOutlined />} onClick={handleSend}>
|
|
||||||
{t.chat.send}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -36,11 +36,7 @@ export default function Footer() {
|
|||||||
<li><a href="#hero" className="hover:text-brand-400">{t.nav.home}</a></li>
|
<li><a href="#hero" className="hover:text-brand-400">{t.nav.home}</a></li>
|
||||||
<li><a href="#showcase" className="hover:text-brand-400">{t.nav.showcase}</a></li>
|
<li><a href="#showcase" className="hover:text-brand-400">{t.nav.showcase}</a></li>
|
||||||
<li><a href="#about" className="hover:text-brand-400">{t.nav.about}</a></li>
|
<li><a href="#about" className="hover:text-brand-400">{t.nav.about}</a></li>
|
||||||
{/*<li><a href="#catalog" className="hover:text-brand-400">{t.nav.catalog}</a></li>*/}
|
|
||||||
<li><a href="#locations" className="hover:text-brand-400">{t.nav.locations}</a></li>
|
<li><a href="#locations" className="hover:text-brand-400">{t.nav.locations}</a></li>
|
||||||
{/*<li><a href="#video" className="hover:text-brand-400">{t.nav.video}</a></li>*/}
|
|
||||||
{/*<li><a href="#map" className="hover:text-brand-400">{t.nav.map}</a></li>*/}
|
|
||||||
{/*<li><a href="#support" className="hover:text-brand-400">{t.nav.support}</a></li>*/}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ export default function Header() {
|
|||||||
{href: '#hero', label: t.nav.home},
|
{href: '#hero', label: t.nav.home},
|
||||||
{href: '#showcase', label: t.nav.showcase},
|
{href: '#showcase', label: t.nav.showcase},
|
||||||
{href: '#about', label: t.nav.about},
|
{href: '#about', label: t.nav.about},
|
||||||
// {href: '#catalog', label: t.nav.catalog},
|
|
||||||
{href: '#locations', label: t.nav.locations},
|
{href: '#locations', label: t.nav.locations},
|
||||||
// {href: '#video', label: t.nav.video},
|
|
||||||
// {href: '#map', label: t.nav.map},
|
|
||||||
// {href: '#support', label: t.nav.support},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
// سکشن مستقل ویدیوی معرفی شرکت (جدا از ویدیوی پسزمینه ویترین محصولات).
|
|
||||||
|
|
||||||
import { useLanguage } from '@/app/context/LanguageContext';
|
|
||||||
import { introVideoSrc, introVideoPoster } from '@/data/content';
|
|
||||||
|
|
||||||
export default function IntroVideoSection() {
|
|
||||||
const { t } = useLanguage();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="video" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
|
|
||||||
<div className="max-w-5xl mx-auto text-center">
|
|
||||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.introVideo.eyebrow}</span>
|
|
||||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-3">
|
|
||||||
{t.introVideo.title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-secondary text-sm md:text-base mb-10">{t.introVideo.subtitle}</p>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
TODO: replace /public/video/company-intro-placeholder.mp4 with the real
|
|
||||||
company introduction video (keep the same filename, or update
|
|
||||||
`introVideoSrc` in data/content.js). The poster image shows before
|
|
||||||
playback starts — swap /public/images/video-poster.svg the same way.
|
|
||||||
*/}
|
|
||||||
<div className="rounded-2xl overflow-hidden border border-subtle shadow-brandGlow">
|
|
||||||
<video controls poster={introVideoPoster} className="w-full aspect-video bg-black">
|
|
||||||
<source src={introVideoSrc} type="video/mp4" />
|
|
||||||
{t.introVideo.videoNotSupported}
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
// نقشه نشان (Neshan) — از Static Map API استفاده میکند. تا وقتی کلید API خالی باشد،
|
|
||||||
// بهجای نقشه یک پیام راهنما نشان داده میشود (نه تصویر خراب).
|
|
||||||
|
|
||||||
import {useState} from 'react';
|
|
||||||
import {EnvironmentOutlined} from '@ant-design/icons';
|
|
||||||
import {useLanguage} from '@/app/context/LanguageContext';
|
|
||||||
import {useTheme} from '@/app/context/ThemeContext';
|
|
||||||
import {mapLocations} from '@/data/content';
|
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Neshan (نشان) map integration.
|
|
||||||
//
|
|
||||||
// TODO: put your Neshan API key here, or (preferred) set it as an environment
|
|
||||||
// variable in `.env.local`:
|
|
||||||
// NEXT_PUBLIC_NESHAN_API_KEY=your-key-here
|
|
||||||
// Get a key from https://platform.neshan.org
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
const NESHAN_API_KEY = process.env.NEXT_PUBLIC_NESHAN_API_KEY || '';
|
|
||||||
|
|
||||||
// Neshan's Static Map API — a plain image, no JS SDK/script tag needed.
|
|
||||||
// Verify query param names/values against the current Neshan docs
|
|
||||||
// (https://platform.neshan.org/api/static-map/) if this ever needs updating —
|
|
||||||
// `type` accepts their day/night map styles, e.g. "standard-day" / "standard-night".
|
|
||||||
function staticMapUrl({lat, lng}, mapStyle) {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
key: NESHAN_API_KEY,
|
|
||||||
type: mapStyle,
|
|
||||||
center: `${lat},${lng}`,
|
|
||||||
zoom: '15',
|
|
||||||
size: '600x400',
|
|
||||||
marker: `${lat},${lng}`,
|
|
||||||
});
|
|
||||||
return `https://api.neshan.org/v5/static?${params.toString()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function neshanViewUrl({lat, lng}) {
|
|
||||||
return `https://neshan.org/maps/@${lat},${lng},15z`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MapThumb({coords, mapStyle, alt}) {
|
|
||||||
const [errored, setErrored] = useState(false);
|
|
||||||
const {t} = useLanguage();
|
|
||||||
|
|
||||||
if (!NESHAN_API_KEY || errored) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="w-full h-full min-h-[280px] flex flex-col items-center justify-center gap-3 bg-surface text-secondary text-sm text-center p-6">
|
|
||||||
<EnvironmentOutlined className="text-3xl text-brand-400"/>
|
|
||||||
<p>{t.map.configNotice}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
|
||||||
src={staticMapUrl(coords, mapStyle)}
|
|
||||||
alt={alt}
|
|
||||||
onError={() => setErrored(true)}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MapSection() {
|
|
||||||
const {t} = useLanguage();
|
|
||||||
const {theme} = useTheme();
|
|
||||||
const mapStyle = theme === 'dark' ? 'standard-night' : 'standard-day';
|
|
||||||
|
|
||||||
const pins = [
|
|
||||||
{
|
|
||||||
key: 'factory',
|
|
||||||
label: t.map.factoryLabel,
|
|
||||||
coords: mapLocations.factory,
|
|
||||||
address: t.contactInfo.factoryAddress
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'sales',
|
|
||||||
label: t.map.salesLabel,
|
|
||||||
coords: mapLocations.salesOffice,
|
|
||||||
address: t.contactInfo.salesOfficeAddress
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="map" className="relative px-6 md:px-16 py-20 md:py-28 bg-page border-t border-subtle">
|
|
||||||
<div className="max-w-6xl mx-auto">
|
|
||||||
<div className="text-center mb-12">
|
|
||||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.map.eyebrow}</span>
|
|
||||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3">{t.map.title}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid lg:grid-cols-[1.3fr,1fr] gap-6">
|
|
||||||
<div className="rounded-2xl overflow-hidden border border-subtle h-[360px] lg:h-auto min-h-[360px]">
|
|
||||||
<MapThumb coords={pins[0].coords} mapStyle={mapStyle} alt={pins[0].label}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
{pins.map((pin) => (
|
|
||||||
<div key={pin.key} className="bg-surface border border-subtle rounded-2xl p-5">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div
|
|
||||||
className="w-10 h-10 shrink-0 rounded-lg bg-brand/15 border border-subtle flex items-center justify-center text-brand-400">
|
|
||||||
<EnvironmentOutlined/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-primary font-bold text-sm mb-1">{pin.label}</h3>
|
|
||||||
<p className="text-secondary text-sm leading-7 mb-2">{pin.address}</p>
|
|
||||||
<a
|
|
||||||
href={neshanViewUrl(pin.coords)}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-brand-400 hover:text-brand-300 text-xs font-semibold transition-colors"
|
|
||||||
>
|
|
||||||
{t.map.openInMaps} ↗
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
// فرم ثبت تیکت پشتیبانی با اعتبارسنجی antd؛ ارسال واقعی به بکاند در بلوک TODO داخل کد مشخص شده.
|
|
||||||
|
|
||||||
import { Form, Input, Select, Button, message } from 'antd';
|
|
||||||
import { useLanguage } from '@/app/context/LanguageContext';
|
|
||||||
|
|
||||||
const { TextArea } = Input;
|
|
||||||
|
|
||||||
export default function TicketForm() {
|
|
||||||
const { t, locale } = useLanguage();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
// TODO: Connect to the real ticketing/support API.
|
|
||||||
//
|
|
||||||
// import axios from 'axios';
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// await axios.post('/api/tickets', values);
|
|
||||||
// } catch (err) {
|
|
||||||
// message.error('...');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// api() <===> todo : use this func to fetch ...
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
console.log('Ticket submitted (placeholder):', values);
|
|
||||||
message.success(t.ticket.successMsg);
|
|
||||||
form.resetFields();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section id="support" className="relative px-6 md:px-16 py-20 md:py-28 bg-surface border-t border-subtle">
|
|
||||||
<div className="max-w-2xl mx-auto">
|
|
||||||
<div className="text-center mb-10">
|
|
||||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.ticket.eyebrow}</span>
|
|
||||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-3">
|
|
||||||
{t.ticket.title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-secondary text-sm md:text-base">{t.ticket.subtitle}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
onFinish={handleFinish}
|
|
||||||
requiredMark={false}
|
|
||||||
initialValues={{ priority: 'medium' }}
|
|
||||||
>
|
|
||||||
<div className="grid md:grid-cols-2 gap-x-4">
|
|
||||||
<Form.Item
|
|
||||||
label={<span className="text-secondary">{t.ticket.name}</span>}
|
|
||||||
name="name"
|
|
||||||
rules={[{ required: true, message: t.ticket.requiredMsg }]}
|
|
||||||
>
|
|
||||||
<Input size="large" placeholder={t.ticket.namePlaceholder} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={<span className="text-secondary">{t.ticket.phone}</span>}
|
|
||||||
name="phone"
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: t.ticket.requiredMsg },
|
|
||||||
{ pattern: /^[0-9+\s-]{7,15}$/, message: t.ticket.invalidPhone },
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input size="large" placeholder={t.ticket.phonePlaceholder} dir="ltr" />
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-x-4">
|
|
||||||
<Form.Item
|
|
||||||
label={<span className="text-secondary">{t.ticket.email}</span>}
|
|
||||||
name="email"
|
|
||||||
rules={[{ type: 'email', message: t.ticket.invalidEmail }]}
|
|
||||||
>
|
|
||||||
<Input size="large" placeholder={t.ticket.emailPlaceholder} dir="ltr" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={<span className="text-secondary">{t.ticket.priority}</span>}
|
|
||||||
name="priority"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
size="large"
|
|
||||||
options={t.ticket.priorities.map((p) => ({ value: p.value, label: p.label }))}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={<span className="text-secondary">{t.ticket.subject}</span>}
|
|
||||||
name="subject"
|
|
||||||
rules={[{ required: true, message: t.ticket.requiredMsg }]}
|
|
||||||
>
|
|
||||||
<Input size="large" placeholder={t.ticket.subjectPlaceholder} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={<span className="text-secondary">{t.ticket.message}</span>}
|
|
||||||
name="message"
|
|
||||||
rules={[{ required: true, message: t.ticket.requiredMsg }]}
|
|
||||||
>
|
|
||||||
<TextArea rows={5} placeholder={t.ticket.messagePlaceholder} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item className="mb-0">
|
|
||||||
<Button type="primary" size="large" htmlType="submit" block>
|
|
||||||
{t.ticket.submit}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* سکشن «مشتریانی که به ما اعتماد کردهاند» — یک نوار لوگوی بیپایان (marquee)
|
|
||||||
* که بهآرامی و پیوسته حرکت میکند. برای ساخت افکت حلقهی بیپایان، لیست
|
|
||||||
* لوگوها را دو بار پشتسرهم رندر میکنیم و با انیمیشن CSS آن را جابهجا میکنیم.
|
|
||||||
*
|
|
||||||
* نکته فنی: مسیر (track) همیشه جهت LTR دارد، چون منطق «حرکت به چپ» انیمیشن
|
|
||||||
* فرض بر همین جهت است — دقیقاً همان مشکلی که در اسکرول افقی محصولات داشتیم و
|
|
||||||
* حلش کردیم. اینجا هم برای جلوگیری از همان باگ، جهت را ثابت نگه داشتیم.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useLanguage } from '@/app/context/LanguageContext';
|
|
||||||
|
|
||||||
export default function TrustedBySection() {
|
|
||||||
const { t } = useLanguage();
|
|
||||||
const clients = t.trustedBy.clients;
|
|
||||||
// لیست لوگوها را دو برابر میکنیم تا وقتی نیمه اول با انیمیشن رد شد،
|
|
||||||
// نیمه دوم دقیقاً از همانجا ادامه بدهد و چشم متوجه پرش/شکاف نشود.
|
|
||||||
const doubledClients = [...clients, ...clients];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="relative px-6 md:px-16 py-16 md:py-20 bg-page border-t border-subtle overflow-hidden">
|
|
||||||
<div className="max-w-6xl mx-auto">
|
|
||||||
<div className="text-center mb-10">
|
|
||||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.trustedBy.eyebrow}</span>
|
|
||||||
<h2 className="text-xl md:text-2xl font-extrabold text-primary mt-2">{t.trustedBy.title}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* یک محو تدریجی (fade mask) دو طرف نوار، تا لبههای لوگوها بریده بهنظر نرسد */}
|
|
||||||
<div
|
|
||||||
className="relative"
|
|
||||||
style={{
|
|
||||||
maskImage: 'linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%)',
|
|
||||||
WebkitMaskImage: 'linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex w-max animate-marquee gap-10" dir="ltr">
|
|
||||||
{doubledClients.map((client, i) => (
|
|
||||||
<div
|
|
||||||
key={`${client.id}-${i}`}
|
|
||||||
className="w-40 md:w-48 shrink-0 flex items-center justify-center bg-surface border border-subtle rounded-xl p-4 opacity-80 hover:opacity-100 transition-opacity"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={`/images/clients/client-${((i % clients.length) + 1)}.svg`}
|
|
||||||
alt={client.name}
|
|
||||||
className="w-full h-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -96,10 +96,6 @@ export const content = {
|
|||||||
showcase: 'دستگاهها',
|
showcase: 'دستگاهها',
|
||||||
about: 'درباره ما',
|
about: 'درباره ما',
|
||||||
locations: 'مکانهای نصب',
|
locations: 'مکانهای نصب',
|
||||||
catalog: 'کاتالوگ محصولات',
|
|
||||||
video: 'ویدیوی معرفی',
|
|
||||||
map: 'نقشه',
|
|
||||||
support: 'پشتیبانی',
|
|
||||||
contact: 'تماس با ما',
|
contact: 'تماس با ما',
|
||||||
login: 'ورود به پنل کاربری',
|
login: 'ورود به پنل کاربری',
|
||||||
},
|
},
|
||||||
@@ -148,18 +144,6 @@ export const content = {
|
|||||||
gifAlt: (name) => `نمایش عملکرد ${name}`,
|
gifAlt: (name) => `نمایش عملکرد ${name}`,
|
||||||
},
|
},
|
||||||
machines: machinesFa,
|
machines: machinesFa,
|
||||||
trustedBy: {
|
|
||||||
eyebrow: 'اعتماد شما، افتخار ماست',
|
|
||||||
title: 'کسبوکارهایی که به روبات مارکت اعتماد کردهاند',
|
|
||||||
clients: [
|
|
||||||
{id: 'c1', name: 'هتل پارسیان'},
|
|
||||||
{id: 'c2', name: 'دانشگاه فردوسی'},
|
|
||||||
{id: 'c3', name: 'بیمارستان رضوی'},
|
|
||||||
{id: 'c4', name: 'فروشگاههای زنجیرهای رفاه'},
|
|
||||||
{id: 'c5', name: 'مجتمع اداری آسمان'},
|
|
||||||
{id: 'c6', name: 'باشگاه ورزشی تلاش'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
id: 'warranty',
|
id: 'warranty',
|
||||||
@@ -243,114 +227,6 @@ export const content = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
catalog: {
|
|
||||||
eyebrow: 'کاتالوگ محصولات',
|
|
||||||
title: 'محصولات قابل سفارش برای دستگاه شما',
|
|
||||||
subtitle:
|
|
||||||
'با خرید دستگاه روبات مارکت، میتوانید این محصولات را با قیمت عمده و کمتر از بازار سفارش دهید؛ تیم ما هم چیدمان اولیه و شارژ دستگاه شما را انجام میدهد.',
|
|
||||||
addToOrder: 'افزودن به سفارش',
|
|
||||||
originalPriceLabel: 'قیمت بازار',
|
|
||||||
machinePriceLabel: 'قیمت ویژه مشتریان روبات مارکت',
|
|
||||||
addToast: (name) => `${name} به لیست سفارش اضافه شد`,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'saffron',
|
|
||||||
name: 'زعفران ممتاز درجهیک',
|
|
||||||
category: 'زعفران',
|
|
||||||
image: '/images/catalog/saffron.svg',
|
|
||||||
originalPriceNumeric: 12000000,
|
|
||||||
priceNumeric: 8900000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chips',
|
|
||||||
name: 'چیپس و پفک (بسته عمده)',
|
|
||||||
category: 'تنقلات',
|
|
||||||
image: '/images/snacks/chips-1.svg',
|
|
||||||
originalPriceNumeric: 450000,
|
|
||||||
priceNumeric: 320000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'soda',
|
|
||||||
name: 'نوشابه و نوشیدنی گازدار',
|
|
||||||
category: 'نوشیدنی',
|
|
||||||
image: '/images/snacks/soda-1.svg',
|
|
||||||
originalPriceNumeric: 250000,
|
|
||||||
priceNumeric: 180000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'water',
|
|
||||||
name: 'آب معدنی',
|
|
||||||
category: 'نوشیدنی',
|
|
||||||
image: '/images/catalog/water.svg',
|
|
||||||
originalPriceNumeric: 120000,
|
|
||||||
priceNumeric: 80000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'coffee',
|
|
||||||
name: 'قهوه فوری',
|
|
||||||
category: 'گرم',
|
|
||||||
image: '/images/catalog/coffee.svg',
|
|
||||||
originalPriceNumeric: 650000,
|
|
||||||
priceNumeric: 470000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'tea',
|
|
||||||
name: 'چای کیسهای',
|
|
||||||
category: 'گرم',
|
|
||||||
image: '/images/catalog/tea.svg',
|
|
||||||
originalPriceNumeric: 380000,
|
|
||||||
priceNumeric: 270000
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
introVideo: {
|
|
||||||
eyebrow: 'ویدیوی معرفی',
|
|
||||||
title: 'با روبات مارکت آشنا شوید',
|
|
||||||
subtitle: 'نگاهی کوتاه به کارخانه، فرآیند تولید و عملکرد دستگاههای روبات مارکت.',
|
|
||||||
videoNotSupported: 'مرورگر شما از پخش ویدیو پشتیبانی نمیکند.',
|
|
||||||
},
|
|
||||||
map: {
|
|
||||||
eyebrow: 'موقعیت ما روی نقشه',
|
|
||||||
title: 'کارخانه و دفاتر روبات مارکت',
|
|
||||||
factoryLabel: 'کارخانه',
|
|
||||||
salesLabel: 'دفتر فروش مرکزی',
|
|
||||||
openInMaps: 'مشاهده در نشان',
|
|
||||||
configNotice: 'برای فعال شدن نقشه، کلید API نشان را در تنظیمات پروژه قرار دهید.',
|
|
||||||
},
|
|
||||||
ticket: {
|
|
||||||
eyebrow: 'پشتیبانی',
|
|
||||||
title: 'ثبت تیکت پشتیبانی',
|
|
||||||
subtitle: 'سوال یا درخواستی دارید؟ فرم زیر را پر کنید تا تیم پشتیبانی در سریعترین زمان با شما تماس بگیرد.',
|
|
||||||
name: 'نام و نام خانوادگی',
|
|
||||||
namePlaceholder: 'مثال: علی رضایی',
|
|
||||||
phone: 'شماره تماس',
|
|
||||||
phonePlaceholder: '09xxxxxxxxx',
|
|
||||||
email: 'ایمیل (اختیاری)',
|
|
||||||
emailPlaceholder: 'example@email.com',
|
|
||||||
subject: 'موضوع',
|
|
||||||
subjectPlaceholder: 'موضوع درخواست خود را بنویسید',
|
|
||||||
priority: 'اولویت',
|
|
||||||
priorities: [
|
|
||||||
{value: 'low', label: 'کم'},
|
|
||||||
{value: 'medium', label: 'متوسط'},
|
|
||||||
{value: 'high', label: 'فوری'},
|
|
||||||
],
|
|
||||||
message: 'پیام شما',
|
|
||||||
messagePlaceholder: 'توضیحات کامل درخواست یا مشکل خود را بنویسید…',
|
|
||||||
submit: 'ارسال تیکت',
|
|
||||||
requiredMsg: 'این فیلد الزامی است',
|
|
||||||
invalidPhone: 'شماره تماس معتبر نیست',
|
|
||||||
invalidEmail: 'ایمیل معتبر نیست',
|
|
||||||
successMsg: 'تیکت شما با موفقیت ثبت شد. بهزودی با شما تماس میگیریم.',
|
|
||||||
},
|
|
||||||
chat: {
|
|
||||||
fabLabel: 'گفتگوی آنلاین',
|
|
||||||
title: 'پشتیبانی آنلاین روبات مارکت',
|
|
||||||
placeholder: 'پیام خود را بنویسید…',
|
|
||||||
send: 'ارسال',
|
|
||||||
initialBotMessage: 'سلام! 👋 چطور میتونم امروز کمکتون کنم؟',
|
|
||||||
fallbackReply: 'ممنون از پیامتون؛ همکاران ما بهزودی پاسخ میدهند.',
|
|
||||||
},
|
|
||||||
footer: {
|
footer: {
|
||||||
aboutLabel: 'روبات مارکت',
|
aboutLabel: 'روبات مارکت',
|
||||||
aboutText: 'ارائهدهنده دستگاههای وندینگ هوشمند برای کسبوکارهای مدرن — محصولی از شرکت نگین سازان پیشگام رادمهر آتوسا.',
|
aboutText: 'ارائهدهنده دستگاههای وندینگ هوشمند برای کسبوکارهای مدرن — محصولی از شرکت نگین سازان پیشگام رادمهر آتوسا.',
|
||||||
@@ -386,10 +262,6 @@ export const content = {
|
|||||||
showcase: 'Machines',
|
showcase: 'Machines',
|
||||||
about: 'About Us',
|
about: 'About Us',
|
||||||
locations: 'Where to Install',
|
locations: 'Where to Install',
|
||||||
catalog: 'Product Catalog',
|
|
||||||
video: 'Intro Video',
|
|
||||||
map: 'Map',
|
|
||||||
support: 'Support',
|
|
||||||
contact: 'Contact',
|
contact: 'Contact',
|
||||||
login: 'Sign In',
|
login: 'Sign In',
|
||||||
},
|
},
|
||||||
@@ -438,19 +310,6 @@ export const content = {
|
|||||||
gifAlt: (name) => `${name} in action`,
|
gifAlt: (name) => `${name} in action`,
|
||||||
},
|
},
|
||||||
machines: machinesEn,
|
machines: machinesEn,
|
||||||
trustedBy: {
|
|
||||||
eyebrow: 'Trusted by businesses like yours',
|
|
||||||
title: 'Businesses That Trust Robot Market',
|
|
||||||
// Sample/placeholder names — replace with real client names.
|
|
||||||
clients: [
|
|
||||||
{id: 'c1', name: 'Parsian Hotel'},
|
|
||||||
{id: 'c2', name: 'Ferdowsi University'},
|
|
||||||
{id: 'c3', name: 'Razavi Hospital'},
|
|
||||||
{id: 'c4', name: 'Refah Retail Chain'},
|
|
||||||
{id: 'c5', name: 'Asemaan Office Complex'},
|
|
||||||
{id: 'c6', name: 'Talash Sports Club'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
features: [
|
features: [
|
||||||
{
|
{
|
||||||
id: 'warranty',
|
id: 'warranty',
|
||||||
@@ -534,114 +393,6 @@ export const content = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
catalog: {
|
|
||||||
eyebrow: 'Product Catalog',
|
|
||||||
title: 'Products You Can Order for Your Machine',
|
|
||||||
subtitle:
|
|
||||||
'When you buy a Robot Market machine, you can order these products at a wholesale price below market rate — our team also handles the initial stocking and layout for you.',
|
|
||||||
addToOrder: 'Add to Order',
|
|
||||||
originalPriceLabel: 'Market price',
|
|
||||||
machinePriceLabel: 'Robot Market customer price',
|
|
||||||
addToast: (name) => `${name} added to your order`,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'saffron',
|
|
||||||
name: 'Premium Grade-1 Saffron',
|
|
||||||
category: 'Saffron',
|
|
||||||
image: '/images/catalog/saffron.svg',
|
|
||||||
originalPriceNumeric: 12000000,
|
|
||||||
priceNumeric: 8900000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chips',
|
|
||||||
name: 'Chips & Crisps (bulk pack)',
|
|
||||||
category: 'Snacks',
|
|
||||||
image: '/images/snacks/chips-1.svg',
|
|
||||||
originalPriceNumeric: 450000,
|
|
||||||
priceNumeric: 320000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'soda',
|
|
||||||
name: 'Soda & Carbonated Drinks',
|
|
||||||
category: 'Drinks',
|
|
||||||
image: '/images/snacks/soda-1.svg',
|
|
||||||
originalPriceNumeric: 250000,
|
|
||||||
priceNumeric: 180000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'water',
|
|
||||||
name: 'Mineral Water',
|
|
||||||
category: 'Drinks',
|
|
||||||
image: '/images/catalog/water.svg',
|
|
||||||
originalPriceNumeric: 120000,
|
|
||||||
priceNumeric: 80000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'coffee',
|
|
||||||
name: 'Instant Coffee',
|
|
||||||
category: 'Hot Drinks',
|
|
||||||
image: '/images/catalog/coffee.svg',
|
|
||||||
originalPriceNumeric: 650000,
|
|
||||||
priceNumeric: 470000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'tea',
|
|
||||||
name: 'Tea Bags',
|
|
||||||
category: 'Hot Drinks',
|
|
||||||
image: '/images/catalog/tea.svg',
|
|
||||||
originalPriceNumeric: 380000,
|
|
||||||
priceNumeric: 270000
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
introVideo: {
|
|
||||||
eyebrow: 'Intro Video',
|
|
||||||
title: 'Get to Know Robot Market',
|
|
||||||
subtitle: 'A short look at the factory, our production process, and the machines in action.',
|
|
||||||
videoNotSupported: 'Your browser does not support video playback.',
|
|
||||||
},
|
|
||||||
map: {
|
|
||||||
eyebrow: 'Find Us on the Map',
|
|
||||||
title: 'Robot Market Factory & Offices',
|
|
||||||
factoryLabel: 'Factory',
|
|
||||||
salesLabel: 'Central Sales Office',
|
|
||||||
openInMaps: 'Open in Neshan',
|
|
||||||
configNotice: 'Add your Neshan API key in the project settings to activate the map.',
|
|
||||||
},
|
|
||||||
ticket: {
|
|
||||||
eyebrow: 'Support',
|
|
||||||
title: 'Submit a Support Ticket',
|
|
||||||
subtitle: 'Have a question or request? Fill out the form below and our support team will get back to you as soon as possible.',
|
|
||||||
name: 'Full Name',
|
|
||||||
namePlaceholder: 'e.g. John Smith',
|
|
||||||
phone: 'Phone Number',
|
|
||||||
phonePlaceholder: '09xxxxxxxxx',
|
|
||||||
email: 'Email (optional)',
|
|
||||||
emailPlaceholder: 'example@email.com',
|
|
||||||
subject: 'Subject',
|
|
||||||
subjectPlaceholder: 'Write the subject of your request',
|
|
||||||
priority: 'Priority',
|
|
||||||
priorities: [
|
|
||||||
{value: 'low', label: 'Low'},
|
|
||||||
{value: 'medium', label: 'Medium'},
|
|
||||||
{value: 'high', label: 'Urgent'},
|
|
||||||
],
|
|
||||||
message: 'Your Message',
|
|
||||||
messagePlaceholder: 'Describe your request or issue in full…',
|
|
||||||
submit: 'Submit Ticket',
|
|
||||||
requiredMsg: 'This field is required',
|
|
||||||
invalidPhone: 'Invalid phone number',
|
|
||||||
invalidEmail: 'Invalid email address',
|
|
||||||
successMsg: 'Your ticket was submitted successfully. We will contact you soon.',
|
|
||||||
},
|
|
||||||
chat: {
|
|
||||||
fabLabel: 'Live Chat',
|
|
||||||
title: 'Robot Market Live Support',
|
|
||||||
placeholder: 'Type your message…',
|
|
||||||
send: 'Send',
|
|
||||||
initialBotMessage: "Hi! 👋 How can we help you today?",
|
|
||||||
fallbackReply: "Thanks for your message — our team will reply shortly.",
|
|
||||||
},
|
|
||||||
footer: {
|
footer: {
|
||||||
aboutLabel: 'Robot Market',
|
aboutLabel: 'Robot Market',
|
||||||
aboutText: 'Smart vending machines for modern businesses — a product of Negin Sazan Pishgam Radmehr Atousa Co.',
|
aboutText: 'Smart vending machines for modern businesses — a product of Negin Sazan Pishgam Radmehr Atousa Co.',
|
||||||
@@ -676,15 +427,3 @@ export const floatingSnacks = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const showcaseVideoSrc = '/video/showcase-placeholder.mp4';
|
export const showcaseVideoSrc = '/video/showcase-placeholder.mp4';
|
||||||
|
|
||||||
// Dedicated company introduction video (different from the horizontal-showcase
|
|
||||||
// scroll-scrubbed background video above). Drop the real file at this path.
|
|
||||||
export const introVideoSrc = '/video/company-intro-placeholder.mp4';
|
|
||||||
export const introVideoPoster = '/images/video-poster.svg';
|
|
||||||
|
|
||||||
// Placeholder coordinates for Toos Industrial Town, Mashhad (approximate).
|
|
||||||
// Replace with the exact factory pin — see README.
|
|
||||||
export const mapLocations = {
|
|
||||||
factory: {lat: 36.42, lng: 59.445},
|
|
||||||
salesOffice: {lat: 36.3110, lng: 59.5683}, // approximate central Mashhad placeholder
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 120" width="140" height="120">
|
|
||||||
<rect x="20" y="30" width="80" height="70" rx="8" fill="#6b3f2a" stroke="#3a1f13" stroke-width="3"/>
|
|
||||||
<path d="M100 45 q22 0 22 20 q0 20 -22 20" fill="none" stroke="#3a1f13" stroke-width="6"/>
|
|
||||||
<ellipse cx="60" cy="30" rx="40" ry="10" fill="#c8a27a"/>
|
|
||||||
<text x="60" y="72" font-family="Tahoma, sans-serif" font-size="12" fill="#ffe9d6" text-anchor="middle">قهوه</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 473 B |
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 140" width="120" height="140">
|
|
||||||
<rect x="15" y="20" width="90" height="105" rx="10" fill="#7a1f2b" stroke="#3d0f15" stroke-width="3"/>
|
|
||||||
<rect x="15" y="20" width="90" height="34" rx="10" fill="#00a693"/>
|
|
||||||
<path d="M40 70 q10 -14 20 0 q10 -14 20 0" stroke="#ffcf4d" stroke-width="3" fill="none"/>
|
|
||||||
<text x="60" y="100" font-family="Tahoma, sans-serif" font-size="13" fill="#ffe9b0" text-anchor="middle">زعفران</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 490 B |
@@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 140" width="120" height="140">
|
|
||||||
<rect x="20" y="25" width="80" height="95" rx="6" fill="#00a693" stroke="#017065" stroke-width="3"/>
|
|
||||||
<rect x="30" y="15" width="60" height="16" rx="4" fill="#e6f7f4"/>
|
|
||||||
<line x1="60" y1="0" x2="60" y2="15" stroke="#8fdccc" stroke-width="2"/>
|
|
||||||
<text x="60" y="80" font-family="Tahoma, sans-serif" font-size="13" fill="#e6f7f4" text-anchor="middle">چای</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 462 B |
@@ -1,5 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 160" width="90" height="160">
|
|
||||||
<path d="M30 10 h30 v18 l8 10 v100 a6 6 0 0 1 -6 6 h-34 a6 6 0 0 1 -6 -6 v-100 l8 -10 z" fill="#bfe9ff" stroke="#00a693" stroke-width="3"/>
|
|
||||||
<rect x="26" y="70" width="38" height="30" fill="#ffffff" opacity="0.85"/>
|
|
||||||
<text x="45" y="90" font-family="Tahoma, sans-serif" font-size="11" fill="#017065" text-anchor="middle">آب</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 430 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
|
|
||||||
<rect x="2" y="2" width="196" height="76" rx="12" fill="none" stroke="#00a693" stroke-opacity="0.3" stroke-width="2"/>
|
|
||||||
<circle cx="40" cy="40" r="20" fill="#00a693"/>
|
|
||||||
<text x="40" y="47" font-family="Tahoma, sans-serif" font-size="20" font-weight="bold" fill="#ffffff" text-anchor="middle">پ</text>
|
|
||||||
<rect x="72" y="32" width="100" height="8" rx="4" fill="#00a693" fill-opacity="0.35"/>
|
|
||||||
<rect x="72" y="46" width="70" height="6" rx="3" fill="#00a693" fill-opacity="0.2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 575 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
|
|
||||||
<rect x="2" y="2" width="196" height="76" rx="12" fill="none" stroke="#00a693" stroke-opacity="0.3" stroke-width="2"/>
|
|
||||||
<circle cx="40" cy="40" r="20" fill="#017065"/>
|
|
||||||
<text x="40" y="47" font-family="Tahoma, sans-serif" font-size="20" font-weight="bold" fill="#ffffff" text-anchor="middle">ف</text>
|
|
||||||
<rect x="72" y="32" width="100" height="8" rx="4" fill="#00a693" fill-opacity="0.35"/>
|
|
||||||
<rect x="72" y="46" width="70" height="6" rx="3" fill="#00a693" fill-opacity="0.2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 575 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
|
|
||||||
<rect x="2" y="2" width="196" height="76" rx="12" fill="none" stroke="#00a693" stroke-opacity="0.3" stroke-width="2"/>
|
|
||||||
<circle cx="40" cy="40" r="20" fill="#00a693"/>
|
|
||||||
<text x="40" y="47" font-family="Tahoma, sans-serif" font-size="20" font-weight="bold" fill="#ffffff" text-anchor="middle">ر</text>
|
|
||||||
<rect x="72" y="32" width="100" height="8" rx="4" fill="#00a693" fill-opacity="0.35"/>
|
|
||||||
<rect x="72" y="46" width="70" height="6" rx="3" fill="#00a693" fill-opacity="0.2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 575 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
|
|
||||||
<rect x="2" y="2" width="196" height="76" rx="12" fill="none" stroke="#00a693" stroke-opacity="0.3" stroke-width="2"/>
|
|
||||||
<circle cx="40" cy="40" r="20" fill="#0b3d38"/>
|
|
||||||
<text x="40" y="47" font-family="Tahoma, sans-serif" font-size="20" font-weight="bold" fill="#ffffff" text-anchor="middle">ر</text>
|
|
||||||
<rect x="72" y="32" width="100" height="8" rx="4" fill="#00a693" fill-opacity="0.35"/>
|
|
||||||
<rect x="72" y="46" width="70" height="6" rx="3" fill="#00a693" fill-opacity="0.2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 575 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
|
|
||||||
<rect x="2" y="2" width="196" height="76" rx="12" fill="none" stroke="#00a693" stroke-opacity="0.3" stroke-width="2"/>
|
|
||||||
<circle cx="40" cy="40" r="20" fill="#00a693"/>
|
|
||||||
<text x="40" y="47" font-family="Tahoma, sans-serif" font-size="20" font-weight="bold" fill="#ffffff" text-anchor="middle">آ</text>
|
|
||||||
<rect x="72" y="32" width="100" height="8" rx="4" fill="#00a693" fill-opacity="0.35"/>
|
|
||||||
<rect x="72" y="46" width="70" height="6" rx="3" fill="#00a693" fill-opacity="0.2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 575 B |
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" width="200" height="80">
|
|
||||||
<rect x="2" y="2" width="196" height="76" rx="12" fill="none" stroke="#00a693" stroke-opacity="0.3" stroke-width="2"/>
|
|
||||||
<circle cx="40" cy="40" r="20" fill="#017065"/>
|
|
||||||
<text x="40" y="47" font-family="Tahoma, sans-serif" font-size="20" font-weight="bold" fill="#ffffff" text-anchor="middle">ت</text>
|
|
||||||
<rect x="72" y="32" width="100" height="8" rx="4" fill="#00a693" fill-opacity="0.35"/>
|
|
||||||
<rect x="72" y="46" width="70" height="6" rx="3" fill="#00a693" fill-opacity="0.2"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 575 B |
@@ -1,12 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720" width="1280" height="720">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="posterBg" x1="0" y1="0" x2="1" y2="1">
|
|
||||||
<stop offset="0" stop-color="#0b3d38"/>
|
|
||||||
<stop offset="1" stop-color="#04120f"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<rect width="1280" height="720" fill="url(#posterBg)"/>
|
|
||||||
<circle cx="640" cy="360" r="70" fill="#00a693" opacity="0.9"/>
|
|
||||||
<path d="M615 325 l70 35 l-70 35 z" fill="#04120f"/>
|
|
||||||
<text x="640" y="480" font-family="Tahoma, sans-serif" font-size="30" fill="#e6f7f4" text-anchor="middle">ویدیوی معرفی روبات مارکت — placeholder</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 649 B |