update
This commit is contained in:
Generated
+27
-9
@@ -3,14 +3,9 @@
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="bb7b50a5-be9c-4cd7-a05b-979f0e91b173" name="Changes" comment="update">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/jsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/jsconfig.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/app/globals.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/globals.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/app/page.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/app/page.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/animations/ScrollReveal.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/animations/ScrollReveal.jsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/hero/FloatingProducts.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/hero/FloatingProducts.jsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/hero/Hero.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/hero/Hero.jsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/hero/MachineAssembly.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/hero/MachineAssembly.jsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/layout/Navbar.jsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/layout/Navbar.jsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/globals.css" beforeDir="false" afterPath="$PROJECT_DIR$/app/globals.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/content.js" beforeDir="false" afterPath="$PROJECT_DIR$/data/content.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/video/showcase-placeholder.mp4" beforeDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -45,6 +40,7 @@
|
||||
"git-widget-placeholder": "main",
|
||||
"js.debugger.nextJs.config.created.client": "true",
|
||||
"js.debugger.nextJs.config.created.server": "true",
|
||||
"last_opened_file_path": "D:/Project/GitHubRepo/robot-market-landing",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
@@ -84,6 +80,12 @@
|
||||
<updated>1785394346997</updated>
|
||||
<workItem from="1785394348169" duration="14851000" />
|
||||
<workItem from="1785556493808" duration="2292000" />
|
||||
<workItem from="1785581294427" duration="1693000" />
|
||||
<workItem from="1785583200703" duration="181000" />
|
||||
<workItem from="1785583393176" duration="49000" />
|
||||
<workItem from="1785583486761" duration="9000" />
|
||||
<workItem from="1785583517424" duration="145000" />
|
||||
<workItem from="1785645096071" duration="26000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="update">
|
||||
<option name="closed" value="true" />
|
||||
@@ -93,7 +95,23 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1785394671723</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="2" />
|
||||
<task id="LOCAL-00002" summary="update">
|
||||
<option name="closed" value="true" />
|
||||
<created>1785581338739</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1785581338740</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="update">
|
||||
<option name="closed" value="true" />
|
||||
<created>1785582935941</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1785582935941</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="4" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// context زبان سایت: نگهداری زبان فعلی (fa/en)، ذخیره در localStorage، و تنظیم جهت صفحه (rtl/ltr).
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { content } from '@/data/content';
|
||||
|
||||
|
||||
@@ -5,16 +5,20 @@ import { createContext, useContext, useEffect, useState } from 'react';
|
||||
const ThemeContext = createContext(null);
|
||||
|
||||
export function ThemeProvider({ children }) {
|
||||
const [theme, setTheme] = useState('dark');
|
||||
// پیشفرض سایت حالا «روشن» (light) است. اگر کاربر قبلاً تم را عوض کرده باشد
|
||||
// (ذخیرهشده در localStorage) همان ترجیح رو حفظ میکنیم؛ در غیر این صورت فقط
|
||||
// اگر سیستم کاربر صراحتاً روی حالت تیره تنظیم شده باشد، تیره را انتخاب میکنیم.
|
||||
const [theme, setTheme] = useState('light');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// On mount: read saved preference, else fall back to system preference.
|
||||
// On mount: read saved preference, else fall back to system preference
|
||||
// (but default stays light unless the OS explicitly prefers dark).
|
||||
useEffect(() => {
|
||||
const saved = window.localStorage.getItem('rm-theme');
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
setTheme(saved);
|
||||
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
setTheme('light');
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
setTheme('dark');
|
||||
}
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
+33
-53
@@ -56,11 +56,9 @@ html[dir='rtl'] body {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-scrollbar-track);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-scrollbar-thumb);
|
||||
border-radius: 999px;
|
||||
@@ -68,51 +66,21 @@ html[dir='rtl'] body {
|
||||
|
||||
/* Semantic surface/text utilities — read the theme variables above so every
|
||||
component that uses them automatically supports dark + light mode. */
|
||||
.bg-page {
|
||||
background-color: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.bg-elevated {
|
||||
background-color: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
.bg-surface {
|
||||
background-color: var(--color-bg-surface);
|
||||
}
|
||||
|
||||
.bg-surface-strong {
|
||||
background-color: var(--color-bg-surface-strong);
|
||||
}
|
||||
|
||||
.border-subtle {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.bg-page { background-color: var(--color-bg-page); }
|
||||
.bg-elevated { background-color: var(--color-bg-elevated); }
|
||||
.bg-surface { background-color: var(--color-bg-surface); }
|
||||
.bg-surface-strong { background-color: var(--color-bg-surface-strong); }
|
||||
.border-subtle { border-color: var(--color-border); }
|
||||
.text-primary { color: var(--color-text-primary); }
|
||||
.text-secondary { color: var(--color-text-secondary); }
|
||||
|
||||
/* Opacity variants of the tokens above (Tailwind's /NN modifier only works on
|
||||
Tailwind-generated utilities, not these hand-written classes). */
|
||||
.bg-page-60 {
|
||||
background-color: color-mix(in srgb, var(--color-bg-page) 60%, transparent);
|
||||
}
|
||||
|
||||
.bg-page-85 {
|
||||
background-color: color-mix(in srgb, var(--color-bg-page) 85%, transparent);
|
||||
}
|
||||
|
||||
.bg-elevated-95 {
|
||||
background-color: color-mix(in srgb, var(--color-bg-elevated) 95%, transparent);
|
||||
}
|
||||
|
||||
.text-primary-90 {
|
||||
color: color-mix(in srgb, var(--color-text-primary) 90%, transparent);
|
||||
}
|
||||
.bg-page-60 { background-color: color-mix(in srgb, var(--color-bg-page) 60%, transparent); }
|
||||
.bg-page-70 { background-color: color-mix(in srgb, var(--color-bg-page) 70%, transparent); }
|
||||
.bg-page-85 { background-color: color-mix(in srgb, var(--color-bg-page) 85%, transparent); }
|
||||
.bg-elevated-95 { background-color: color-mix(in srgb, var(--color-bg-elevated) 95%, transparent); }
|
||||
.text-primary-90 { color: color-mix(in srgb, var(--color-text-primary) 90%, transparent); }
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Horizontal scroll showcase (pinned via GSAP ScrollTrigger) */
|
||||
@@ -120,7 +88,7 @@ html[dir='rtl'] body {
|
||||
.horizontal-track {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
/*width: 100vw;*/
|
||||
width: max-content;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@@ -165,7 +133,8 @@ html[dir='rtl'] body {
|
||||
height: 190px;
|
||||
border-radius: 999px;
|
||||
border: 2px solid rgba(0, 166, 147, 0.9);
|
||||
box-shadow: 0 0 0 4px rgba(0, 166, 147, 0.15),
|
||||
box-shadow:
|
||||
0 0 0 4px rgba(0, 166, 147, 0.15),
|
||||
0 0 40px 6px rgba(0, 166, 147, 0.45);
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
@@ -182,7 +151,6 @@ html[dir='rtl'] body {
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@@ -208,12 +176,24 @@ html[dir='rtl'] body {
|
||||
}
|
||||
|
||||
@keyframes caretBlink {
|
||||
0%, 49% {
|
||||
opacity: 1;
|
||||
}
|
||||
50%, 100% {
|
||||
opacity: 0;
|
||||
}
|
||||
0%, 49% { opacity: 1; }
|
||||
50%, 100% { 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;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
+5
-4
@@ -15,11 +15,12 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
// Default server-rendered shell: Persian + dark. ThemeProvider/LanguageProvider
|
||||
// read localStorage on mount and correct lang/dir/class client-side if needed —
|
||||
// suppressHydrationWarning avoids a noisy (harmless) mismatch warning for that swap.
|
||||
// پوسته پیشفرض سمت سرور: فارسی + حالت روشن (Light).
|
||||
// ThemeProvider/LanguageProvider بعد از mount شدن، اگر کاربر قبلاً تم/زبان
|
||||
// دیگری را انتخاب کرده بود (ذخیرهشده در localStorage)، آن را روی <html> اعمال
|
||||
// میکنند؛ suppressHydrationWarning جلوی هشدار بیضرر عدمتطابق را میگیرد.
|
||||
return (
|
||||
<html lang="fa" dir="rtl" className={`${vazirmatn.variable} dark`} suppressHydrationWarning>
|
||||
<html lang="fa" dir="rtl" className={`${vazirmatn.variable} light`} suppressHydrationWarning>
|
||||
<body className="bg-page text-primary antialiased transition-colors duration-300">
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
|
||||
+21
-16
@@ -1,6 +1,7 @@
|
||||
import Header from '@/components/Header';
|
||||
import HeroSection from '@/components/HeroSection';
|
||||
import HorizontalShowcase from '@/components/HorizontalShowcase';
|
||||
import TrustedBySection from '@/components/TrustedBySection';
|
||||
import FeaturesSection from '@/components/FeaturesSection';
|
||||
import AboutSection from '@/components/AboutSection';
|
||||
import CatalogSection from '@/components/CatalogSection';
|
||||
@@ -12,36 +13,40 @@ import Footer from '@/components/Footer';
|
||||
import CartDrawer from '@/components/CartDrawer';
|
||||
import ChatWidget from '@/components/ChatWidget';
|
||||
|
||||
// چیدمان کامل صفحه اصلی. ترتیب سکشنها از بالا به پایین:
|
||||
// هیرو (اسلایدر) -> ویترین محصولات (اسکرول افقی + ویدیو) -> مشتریان ما ->
|
||||
// مزایا -> درباره ما -> کاتالوگ -> مکانهای نصب -> ویدیوی معرفی -> نقشه ->
|
||||
// فرم تیکت -> فوتر. همهی سکشنها بهجز هیرو و ویترین محصولات، رنگ زمینهشان
|
||||
// بهترتیب بین سه تن روشن/متوسط/برجسته (bg-page / bg-surface / bg-elevated)
|
||||
// میچرخد تا هنگام اسکرول یک تغییر رنگ ظریف حس شود.
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="relative bg-page">
|
||||
<Header />
|
||||
<CartDrawer />
|
||||
<ChatWidget />
|
||||
|
||||
<Header/>
|
||||
<HeroSection />
|
||||
|
||||
<CartDrawer/>
|
||||
<HorizontalShowcase />
|
||||
|
||||
<ChatWidget/>
|
||||
<TrustedBySection />
|
||||
|
||||
<HeroSection/>
|
||||
<FeaturesSection />
|
||||
|
||||
<HorizontalShowcase/>
|
||||
<AboutSection />
|
||||
|
||||
<FeaturesSection/>
|
||||
<CatalogSection />
|
||||
|
||||
<AboutSection/>
|
||||
<LocationsSection />
|
||||
|
||||
<CatalogSection/>
|
||||
<IntroVideoSection />
|
||||
|
||||
<LocationsSection/>
|
||||
<MapSection />
|
||||
|
||||
<IntroVideoSection/>
|
||||
|
||||
<MapSection/>
|
||||
|
||||
<TicketForm/>
|
||||
|
||||
<Footer/>
|
||||
<TicketForm />
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// پل ارتباطی بین Redux، حالت تیره/روشن، و زبان فا/en با تنظیمات ConfigProvider آنتدیزاین.
|
||||
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { ConfigProvider, theme as antdThemeApi } from 'antd';
|
||||
import faIR from 'antd/locale/fa_IR';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// سکشن معرفی شرکت با متن بلند؛ بهخاطر طولانی بودن متن، یک دکمه «ادامه مطلب» برای باز/بسته کردن دارد.
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
|
||||
@@ -9,7 +11,7 @@ export default function AboutSection() {
|
||||
const visibleParagraphs = expanded ? t.about.paragraphs : t.about.paragraphs.slice(0, 2);
|
||||
|
||||
return (
|
||||
<section id="about" className="relative px-6 md:px-16 py-20 md:py-28 bg-surface border-t border-subtle">
|
||||
<section id="about" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<span className="text-brand-400 text-sm font-semibold tracking-wide">{t.about.eyebrow}</span>
|
||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text mt-3 mb-8">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// کشوی (Drawer) سبد خرید: نمایش آیتمهای انتخابی، تغییر تعداد، جمع کل، و دکمه پرداخت (placeholder).
|
||||
|
||||
import { Drawer, Button, Empty, InputNumber, message } from 'antd';
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client';
|
||||
|
||||
// کاتالوگ محصولات جانبی (زعفران، تنقلات، نوشیدنی و ...) که مشتریان دستگاه با قیمت کمتر از بازار
|
||||
// میتوانند سفارش دهند. دکمه «افزودن به سفارش» از همان سبد خرید Redux دستگاهها استفاده میکند.
|
||||
|
||||
import { Button, message } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -29,7 +32,7 @@ export default function CatalogSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="catalog" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// آیکون شناور چت گوشه صفحه؛ با کلیک یک مودال گفتگوی ساده باز میشود (فعلاً بدون اتصال به بکاند واقعی).
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Modal, Input, Button, Tooltip } from 'antd';
|
||||
import { MessageOutlined, SendOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// نوار مزایا: ضمانت کالا، مشاوره رایگان، خرید اقساطی، بستهبندی ایمن.
|
||||
|
||||
import {
|
||||
SafetyCertificateOutlined,
|
||||
CustomerServiceOutlined,
|
||||
@@ -19,7 +21,7 @@ export default function FeaturesSection() {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<section className="relative px-6 md:px-16 py-16 md:py-20 bg-page border-t border-subtle">
|
||||
<section className="relative px-6 md:px-16 py-16 md:py-20 bg-surface border-t border-subtle">
|
||||
<div className="max-w-6xl mx-auto grid sm:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||
{t.features.map((feature) => {
|
||||
const Icon = ICONS[feature.icon];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// رندر آیتمهای شناور (چیپس/نوشابه) دور دستگاه با حرکت تصادفی و ملایم GSAP.
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// فوتر سایت: درباره ما کوتاه، لینکهای شبکه اجتماعی، دسترسی سریع، و اطلاعات تماس/آدرس.
|
||||
|
||||
import { PhoneOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
|
||||
@@ -8,7 +10,7 @@ export default function Footer() {
|
||||
const c = t.contactInfo;
|
||||
|
||||
return (
|
||||
<footer id="contact" className="relative border-t border-subtle bg-surface">
|
||||
<footer id="contact" className="relative border-t border-subtle bg-elevated">
|
||||
<div className="max-w-6xl mx-auto px-6 md:px-16 py-14 grid md:grid-cols-3 gap-10 text-center md:text-start">
|
||||
<div>
|
||||
<h3 className="text-primary font-bold text-lg mb-3">{t.footer.aboutLabel}</h3>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// هدر ثابت بالای صفحه: لوگو، منوی ناوبری، سوییچ زبان/تم، دکمه ورود به پنل کاربری و آیکون سبد خرید.
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { Badge, Button } from 'antd';
|
||||
@@ -45,7 +47,7 @@ export default function Header() {
|
||||
<Image src="/images/logo.svg" alt="روبات مارکت" width={150} height={42} priority />
|
||||
</a>
|
||||
|
||||
<nav className="hidden lg:flex items-center gap-6 xl:gap-8">
|
||||
<nav className="hidden xl:flex items-center gap-5 2xl:gap-7">
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
@@ -84,7 +86,7 @@ export default function Header() {
|
||||
</Badge>
|
||||
|
||||
<button
|
||||
className="lg:hidden text-brand-400 text-2xl"
|
||||
className="xl:hidden text-brand-400 text-2xl"
|
||||
onClick={() => setMobileOpen((v) => !v)}
|
||||
aria-label="menu"
|
||||
>
|
||||
@@ -94,7 +96,7 @@ export default function Header() {
|
||||
</div>
|
||||
|
||||
{mobileOpen && (
|
||||
<nav className="lg:hidden bg-elevated-95 backdrop-blur-md border-t border-subtle px-5 py-4 flex flex-col gap-4">
|
||||
<nav className="xl:hidden bg-elevated-95 backdrop-blur-md border-t border-subtle px-5 py-4 flex flex-col gap-4 max-h-[calc(100vh-5rem)] overflow-y-auto">
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
|
||||
+134
-147
@@ -1,174 +1,161 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
/**
|
||||
* سکشن اول سایت (Hero) — ایده کلی از صفحه وندینگ سایت Sielaff گرفته شده:
|
||||
* یک اسلایدر تمامعرض که هر ۵ ثانیه بهصورت خودکار به اسلاید بعدی میرود،
|
||||
* همراه با یک شکل مورب رنگی (بهجای قرمز آنها، رنگ سبز برند خودمان #00a693)،
|
||||
* عنوان بزرگ دو خطی، و دکمههای فلش قبلی/بعدی برای ناوبری دستی.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
import { useTheme } from '@/app/context/ThemeContext';
|
||||
|
||||
// Simple typewriter: types each phrase, pauses, deletes, moves to next phrase, loops.
|
||||
function useTypewriter(phrases, { typeSpeed = 55, deleteSpeed = 30, holdTime = 1600 } = {}) {
|
||||
const [text, setText] = useState('');
|
||||
const [phraseIndex, setPhraseIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!phrases?.length) return undefined;
|
||||
let charIndex = 0;
|
||||
let deleting = false;
|
||||
let timeoutId;
|
||||
|
||||
const tick = () => {
|
||||
const current = phrases[phraseIndex % phrases.length];
|
||||
|
||||
if (!deleting) {
|
||||
charIndex += 1;
|
||||
setText(current.slice(0, charIndex));
|
||||
if (charIndex >= current.length) {
|
||||
deleting = true;
|
||||
timeoutId = setTimeout(tick, holdTime);
|
||||
return;
|
||||
}
|
||||
timeoutId = setTimeout(tick, typeSpeed);
|
||||
} else {
|
||||
charIndex -= 1;
|
||||
setText(current.slice(0, charIndex));
|
||||
if (charIndex <= 0) {
|
||||
deleting = false;
|
||||
setPhraseIndex((i) => (i + 1) % phrases.length);
|
||||
timeoutId = setTimeout(tick, 300);
|
||||
return;
|
||||
}
|
||||
timeoutId = setTimeout(tick, deleteSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
timeoutId = setTimeout(tick, 400);
|
||||
return () => clearTimeout(timeoutId);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [phraseIndex, phrases]);
|
||||
|
||||
return text;
|
||||
}
|
||||
const AUTOPLAY_DELAY = 5000; // هر ۵ ثانیه اسلاید عوض میشود
|
||||
|
||||
export default function HeroSection() {
|
||||
const { t } = useLanguage();
|
||||
const { theme } = useTheme();
|
||||
const introRef = useRef(null);
|
||||
const logoRef = useRef(null);
|
||||
const bgColorRef = useRef(null);
|
||||
const subRef = useRef(null);
|
||||
const ctaRef = useRef(null);
|
||||
const { t, dir } = useLanguage();
|
||||
const slides = t.hero.slides;
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const timerRef = useRef(null);
|
||||
|
||||
const typedHeadline = useTypewriter(t.hero.typingPhrases);
|
||||
const isDark = theme === 'dark';
|
||||
// رفتن به اسلاید بعدی (با چرخش به اول لیست وقتی به آخر رسید)
|
||||
const goToNext = useCallback(() => {
|
||||
setActiveIndex((prev) => (prev + 1) % slides.length);
|
||||
}, [slides.length]);
|
||||
|
||||
const goToPrev = useCallback(() => {
|
||||
setActiveIndex((prev) => (prev - 1 + slides.length) % slides.length);
|
||||
}, [slides.length]);
|
||||
|
||||
// تایمر پخش خودکار: هر بار که اسلاید عوض میشود، تایمر قبلی پاک و یک تایمر
|
||||
// جدید ۵ ثانیهای ست میشود. با کلیک دستی روی فلشها هم همین تابع دوباره
|
||||
// صدا زده میشود تا شمارش از نو شروع شود (تجربه کاربری بهتر).
|
||||
const restartAutoplay = useCallback(() => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
timerRef.current = setInterval(goToNext, AUTOPLAY_DELAY);
|
||||
}, [goToNext]);
|
||||
|
||||
// Intro loader fade-in / fade-out.
|
||||
useEffect(() => {
|
||||
const ctx = gsap.context(() => {
|
||||
const tl = gsap.timeline();
|
||||
tl.fromTo(logoRef.current, { opacity: 0, scale: 0.85 }, { opacity: 1, scale: 1, duration: 0.9, ease: 'power2.out' })
|
||||
.to(logoRef.current, { opacity: 0, duration: 0.5, delay: 0.4, ease: 'power1.in' })
|
||||
.to(introRef.current, { opacity: 0, duration: 0.7, ease: 'power2.inOut', pointerEvents: 'none' }, '-=0.1')
|
||||
.fromTo(subRef.current, { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.8, ease: 'power3.out' }, '-=0.5')
|
||||
.fromTo(ctaRef.current, { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.7, ease: 'power3.out' }, '-=0.4');
|
||||
});
|
||||
return () => ctx.revert();
|
||||
}, []);
|
||||
restartAutoplay();
|
||||
return () => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
};
|
||||
}, [restartAutoplay]);
|
||||
|
||||
// Background behavior differs by theme:
|
||||
// - Dark mode: looping color cycle (brand green -> black -> white -> green),
|
||||
// kept readable by the dark scrim below.
|
||||
// - Light mode: a calm, static light background (no cycling), paired with
|
||||
// dark text and a light scrim instead.
|
||||
useEffect(() => {
|
||||
const el = bgColorRef.current;
|
||||
if (!el) return undefined;
|
||||
|
||||
if (isDark) {
|
||||
const tl = gsap.timeline({ repeat: -1 });
|
||||
tl.to(el, { backgroundColor: '#04120f', duration: 5, ease: 'sine.inOut' })
|
||||
.to(el, { backgroundColor: '#ffffff', duration: 5, ease: 'sine.inOut' })
|
||||
.to(el, { backgroundColor: '#00a693', duration: 5, ease: 'sine.inOut' });
|
||||
return () => tl.kill();
|
||||
}
|
||||
|
||||
gsap.set(el, { backgroundColor: '#f5fbf9' });
|
||||
return undefined;
|
||||
}, [isDark]);
|
||||
|
||||
const scrimStyle = isDark
|
||||
? {
|
||||
background:
|
||||
'radial-gradient(circle at 50% 40%, rgba(4,18,15,0.35), rgba(4,18,15,0.75) 70%), linear-gradient(180deg, rgba(4,18,15,0.55) 0%, rgba(4,18,15,0.35) 45%, rgba(4,18,15,0.7) 100%)',
|
||||
}
|
||||
: {
|
||||
background:
|
||||
'radial-gradient(circle at 50% 40%, rgba(255,255,255,0.15), rgba(255,255,255,0.55) 70%), linear-gradient(180deg, rgba(255,255,255,0.45) 0%, rgba(255,255,255,0.25) 45%, rgba(255,255,255,0.55) 100%)',
|
||||
const handleManualNav = (direction) => {
|
||||
if (direction === 'next') goToNext();
|
||||
else goToPrev();
|
||||
restartAutoplay(); // شمارش ۵ ثانیه بعد از کلیک دستی از نو شروع شود
|
||||
};
|
||||
|
||||
const slide = slides[activeIndex];
|
||||
|
||||
return (
|
||||
<section id="hero" className="relative h-screen w-full overflow-hidden">
|
||||
{/* Intro loader overlay */}
|
||||
<div ref={introRef} className="fixed inset-0 z-[100] bg-page flex items-center justify-center">
|
||||
<div ref={logoRef} className="flex flex-col items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl bg-brand flex items-center justify-center shadow-brandGlow">
|
||||
<span className="text-2xl font-bold text-ink-950">R</span>
|
||||
</div>
|
||||
<p className="text-brand-400 tracking-widest text-sm">{t.hero.loading}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background: cycling colors in dark mode, static light tone in light mode */}
|
||||
<div ref={bgColorRef} className="absolute inset-0" style={{ backgroundColor: '#00a693' }} />
|
||||
|
||||
{/*
|
||||
TODO: swap the background above for a video once the asset is ready:
|
||||
|
||||
<video
|
||||
autoPlay muted loop playsInline
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
src="/video/hero-background.mp4"
|
||||
/>
|
||||
|
||||
Keep the scrim div below on top of it so the headline stays readable.
|
||||
*/}
|
||||
|
||||
{/* Scrim to keep text legible — tuned per theme so it never washes out the copy */}
|
||||
<div className="absolute inset-0" style={scrimStyle} />
|
||||
|
||||
{/* Decorative dot grid — dark dots on light backgrounds, light dots on dark */}
|
||||
<section id="hero" className="relative h-screen w-full overflow-hidden bg-page">
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* شکل مورب تزئینی سمت راست/چپ — نسخه سبز برند بهجای قرمز Sielaff */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<div
|
||||
className={`absolute inset-0 opacity-[0.08] [background-size:26px_26px] ${
|
||||
isDark
|
||||
? '[background-image:radial-gradient(#ffffff_1px,transparent_1px)]'
|
||||
: '[background-image:radial-gradient(#04231f_1px,transparent_1px)]'
|
||||
}`}
|
||||
className="absolute inset-y-0 w-1/3 hidden md:block"
|
||||
style={{
|
||||
[dir === 'rtl' ? 'left' : 'right']: '-6%',
|
||||
background: 'linear-gradient(135deg, #00a693 0%, #017065 60%, #0b3d38 100%)',
|
||||
clipPath:
|
||||
dir === 'rtl'
|
||||
? 'polygon(30% 0, 100% 0, 100% 100%, 0% 100%)'
|
||||
: 'polygon(0 0, 70% 0, 100% 100%, 0% 100%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 h-full flex flex-col items-center justify-center text-center px-6">
|
||||
<h1 className="text-3xl md:text-6xl font-extrabold leading-tight max-w-4xl text-primary min-h-[1.4em]">
|
||||
{typedHeadline}
|
||||
<span className="typewriter-caret h-[0.9em] align-middle" />
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* اسلایدهای عکس — همه اسلایدها روی هم قرار دارند و فقط با opacity */}
|
||||
{/* محو/نمایان میشوند (transition نرم بهجای پرش ناگهانی) */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<div className="absolute inset-0">
|
||||
{slides.map((s, i) => (
|
||||
<div
|
||||
key={s.id}
|
||||
className="absolute inset-0 flex items-center justify-center transition-opacity duration-1000 ease-in-out"
|
||||
style={{ opacity: i === activeIndex ? 1 : 0, pointerEvents: i === activeIndex ? 'auto' : 'none' }}
|
||||
aria-hidden={i !== activeIndex}
|
||||
>
|
||||
<img
|
||||
src={s.image}
|
||||
alt={s.titleLines.join(' ')}
|
||||
className="h-[55%] md:h-[70%] w-auto object-contain drop-shadow-2xl"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* لایهی نیمهشفاف پشت متن، برای خوانایی روی هر عکسی */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(4,18,15,0.05) 0%, rgba(4,18,15,0.15) 60%, rgba(4,18,15,0.35) 100%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* متن روی اسلاید: کیکر کوچک + عنوان بزرگ دو خطی + توضیح + دکمه CTA */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<div className="relative z-10 h-full flex flex-col justify-end md:justify-center px-6 md:px-16 pb-28 md:pb-0">
|
||||
<div className="max-w-2xl">
|
||||
<span className="text-brand-400 font-semibold tracking-widest text-sm uppercase">
|
||||
{slide.kicker}
|
||||
</span>
|
||||
<h1 className="mt-3 text-3xl sm:text-4xl md:text-6xl font-extrabold leading-tight text-primary">
|
||||
{slide.titleLines.map((line, i) => (
|
||||
<span key={i} className="block">
|
||||
{line}
|
||||
</span>
|
||||
))}
|
||||
</h1>
|
||||
<p ref={subRef} className="mt-6 max-w-2xl text-secondary text-base md:text-xl opacity-0">
|
||||
{t.hero.sub}
|
||||
</p>
|
||||
<p className="mt-5 text-secondary text-base md:text-lg max-w-xl">{slide.sub}</p>
|
||||
<a
|
||||
ref={ctaRef}
|
||||
href="#showcase"
|
||||
className="opacity-0 mt-10 inline-flex items-center gap-2 bg-brand hover:bg-brand-400 text-ink-950 font-bold px-8 py-3.5 rounded-full transition-colors shadow-brandGlow"
|
||||
className="mt-8 inline-flex items-center gap-2 bg-brand hover:bg-brand-400 text-ink-950 font-bold px-8 py-3.5 rounded-full transition-colors shadow-brandGlow"
|
||||
>
|
||||
{t.hero.cta}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-8 inset-x-0 flex justify-center z-10">
|
||||
<div
|
||||
className={`w-6 h-10 rounded-full border-2 flex items-start justify-center p-1.5 ${
|
||||
isDark ? 'border-white/50' : 'border-ink-900/30'
|
||||
}`}
|
||||
>
|
||||
<span className={`w-1.5 h-1.5 rounded-full animate-pulseSoft ${isDark ? 'bg-white' : 'bg-ink-900'}`} />
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* فلشهای ناوبری دستی (قبلی / بعدی) — شبیه سایت Sielaff، وسط دو طرف */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<button
|
||||
onClick={() => handleManualNav('prev')}
|
||||
aria-label="previous slide"
|
||||
className="hidden sm:flex absolute top-1/2 -translate-y-1/2 start-4 z-20 w-11 h-11 rounded-full bg-page-70 border border-subtle items-center justify-center text-primary hover:bg-brand hover:text-ink-950 transition-colors"
|
||||
>
|
||||
<LeftOutlined />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleManualNav('next')}
|
||||
aria-label="next slide"
|
||||
className="hidden sm:flex absolute top-1/2 -translate-y-1/2 end-4 z-20 w-11 h-11 rounded-full bg-page-70 border border-subtle items-center justify-center text-primary hover:bg-brand hover:text-ink-950 transition-colors"
|
||||
>
|
||||
<RightOutlined />
|
||||
</button>
|
||||
|
||||
{/* نقطههای شمارشگر اسلاید پایین صفحه — کلیک روی هرکدام مستقیم به همان اسلاید میرود */}
|
||||
<div className="absolute bottom-8 inset-x-0 z-20 flex justify-center gap-2">
|
||||
{slides.map((s, i) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => {
|
||||
setActiveIndex(i);
|
||||
restartAutoplay();
|
||||
}}
|
||||
aria-label={`slide ${i + 1}`}
|
||||
className={`h-2 rounded-full transition-all ${
|
||||
i === activeIndex ? 'w-8 bg-brand' : 'w-2 bg-brand/30 hover:bg-brand/60'
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {gsap} from 'gsap';
|
||||
import {ScrollTrigger} from 'gsap/ScrollTrigger';
|
||||
import {Modal, Button, message} from 'antd';
|
||||
import {ShoppingCartOutlined} from '@ant-design/icons';
|
||||
import {useDispatch} from 'react-redux';
|
||||
// سکشن ویترین محصولات: با اسکرول عمودی به اسکرول افقی پینشده تبدیل میشود (GSAP ScrollTrigger)
|
||||
// و یک ویدیوی پسزمینه دارد که با پیشرفت اسکرول جلو/عقب میرود. طبق درخواست کاربر، این سکشن
|
||||
// و هیرو از چرخهی رنگبندی متناوب بقیه سکشنها مستثنا هستند.
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { Modal, Button, message } from 'antd';
|
||||
import { ShoppingCartOutlined } from '@ant-design/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import VendingCard from './VendingCard';
|
||||
import {showcaseVideoSrc} from '@/data/content';
|
||||
import {useLanguage} from '@/app/context/LanguageContext';
|
||||
import {addToCart} from '@/store/cartSlice';
|
||||
import { showcaseVideoSrc } from '@/data/content';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
import { addToCart } from '@/store/cartSlice';
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
@@ -18,7 +22,7 @@ export default function HorizontalShowcase() {
|
||||
const trackRef = useRef(null);
|
||||
const videoRef = useRef(null);
|
||||
const dispatch = useDispatch();
|
||||
const {t} = useLanguage();
|
||||
const { t } = useLanguage();
|
||||
const [quickViewMachine, setQuickViewMachine] = useState(null);
|
||||
const [videoAvailable, setVideoAvailable] = useState(true);
|
||||
|
||||
@@ -74,7 +78,7 @@ export default function HorizontalShowcase() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="showcase" ref={sectionRef} className="relative overflow-hidden">
|
||||
<section id="showcase" ref={sectionRef} className="relative w-full overflow-hidden">
|
||||
<div className="absolute inset-0 z-0">
|
||||
{videoAvailable && (
|
||||
<video
|
||||
@@ -98,7 +102,7 @@ export default function HorizontalShowcase() {
|
||||
|
||||
<div ref={trackRef} className="horizontal-track relative z-10" dir="ltr">
|
||||
{t.machines.map((machine) => (
|
||||
<VendingCard key={machine.id} machine={machine} onQuickView={setQuickViewMachine}/>
|
||||
<VendingCard key={machine.id} machine={machine} onQuickView={setQuickViewMachine} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -111,12 +115,10 @@ export default function HorizontalShowcase() {
|
||||
>
|
||||
{quickViewMachine && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<img src={quickViewMachine.image} alt={quickViewMachine.name}
|
||||
className="w-full h-64 object-contain"/>
|
||||
<img src={quickViewMachine.image} alt={quickViewMachine.name} className="w-full h-64 object-contain" />
|
||||
<p className="text-secondary leading-7">{quickViewMachine.description}</p>
|
||||
<p className="text-xl font-bold text-primary">{quickViewMachine.priceLabel}</p>
|
||||
<Button type="primary" size="large" icon={<ShoppingCartOutlined/>}
|
||||
onClick={handleModalAddToCart}>
|
||||
<Button type="primary" size="large" icon={<ShoppingCartOutlined />} onClick={handleModalAddToCart}>
|
||||
{t.machineActions.addToCart}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// سکشن مستقل ویدیوی معرفی شرکت (جدا از ویدیوی پسزمینه ویترین محصولات).
|
||||
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
import { introVideoSrc, introVideoPoster } from '@/data/content';
|
||||
|
||||
@@ -7,7 +9,7 @@ export default function IntroVideoSection() {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<section id="video" className="relative px-6 md:px-16 py-20 md:py-28 bg-surface border-t border-subtle">
|
||||
<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">
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// دکمه سوییچ زبان فارسی/انگلیسی.
|
||||
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// گرید «مکان مناسب برای نصب» — هتلها، مدارس، داروخانهها، بیمارستانها، شیفت شب، محصولات تخصصی.
|
||||
|
||||
import {
|
||||
HomeOutlined,
|
||||
ReadOutlined,
|
||||
@@ -23,7 +25,7 @@ export default function LocationsSection() {
|
||||
const { t } = useLanguage();
|
||||
|
||||
return (
|
||||
<section id="locations" className="relative px-6 md:px-16 py-20 md:py-28 bg-page border-t border-subtle">
|
||||
<section id="locations" className="relative px-6 md:px-16 py-20 md:py-28 bg-surface border-t border-subtle">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-2xl md:text-4xl font-extrabold brand-gradient-text">{t.locations.title}</h2>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client';
|
||||
|
||||
// نقشه نشان (Neshan) — از Static Map API استفاده میکند. تا وقتی کلید API خالی باشد،
|
||||
// بهجای نقشه یک پیام راهنما نشان داده میشود (نه تصویر خراب).
|
||||
|
||||
import { useState } from 'react';
|
||||
import { EnvironmentOutlined } from '@ant-design/icons';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
@@ -71,7 +74,7 @@ export default function MapSection() {
|
||||
];
|
||||
|
||||
return (
|
||||
<section id="map" className="relative px-6 md:px-16 py-20 md:py-28 bg-elevated border-t border-subtle">
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// دکمه سوییچ حالت روشن/تیره.
|
||||
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { SunOutlined, MoonOutlined } from '@ant-design/icons';
|
||||
import { useTheme } from '@/app/context/ThemeContext';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client';
|
||||
|
||||
// فرم ثبت تیکت پشتیبانی با اعتبارسنجی antd؛ ارسال واقعی به بکاند در بلوک TODO داخل کد مشخص شده.
|
||||
|
||||
import { Form, Input, Select, Button, message } from 'antd';
|
||||
import { useLanguage } from '@/app/context/LanguageContext';
|
||||
|
||||
@@ -28,7 +30,7 @@ export default function TicketForm() {
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="support" className="relative px-6 md:px-16 py-20 md:py-28 bg-page border-t border-subtle">
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
'use client';
|
||||
|
||||
// کارت نمایش هر دستگاه داخل ویترین: شامل جلوه اسپاتلایت (هاور روی عکس دستگاه رنگ جایگزین را نشان میدهد)،
|
||||
// آیتمهای شناور، گیف+توضیحات، و دکمههای افزودن به سبد / مشاهده سریع / علاقهمندی.
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Tooltip, message } from 'antd';
|
||||
import { ShoppingCartOutlined, EyeOutlined, HeartOutlined, HeartFilled } from '@ant-design/icons';
|
||||
|
||||
+74
-12
@@ -105,13 +105,32 @@ export const content = {
|
||||
},
|
||||
hero: {
|
||||
loading: 'در حال بارگذاری روبات مارکت…',
|
||||
typingPhrases: [
|
||||
'به دنیای خودکار و هوشمند خوش آمدید!',
|
||||
'تجربهای نوین از خرید آسان و سریع.',
|
||||
'روبات مارکت؛ فروشنده هوشمند شما، ۲۴ ساعته.',
|
||||
],
|
||||
sub: 'تجربهای نوین از خرید آسان و سریع با دستگاههای وندینگ روبات مارکت.',
|
||||
cta: 'مشاهده دستگاهها',
|
||||
// هر اسلاید یک تصویر دستگاه + یک عنوان دو خطی بزرگ (شبیه سایت Sielaff) دارد.
|
||||
// برای افزودن اسلاید جدید، فقط یک آیتم دیگر به این آرایه اضافه کنید.
|
||||
slides: [
|
||||
{
|
||||
id: 'slide-1',
|
||||
kicker: 'روبات مارکت',
|
||||
titleLines: ['فروش خودکار،', 'هوشمند و بیوقفه'],
|
||||
sub: 'تجربهای نوین از خرید آسان و سریع با دستگاههای وندینگ روبات مارکت.',
|
||||
image: '/images/machine-35-base.svg',
|
||||
},
|
||||
{
|
||||
id: 'slide-2',
|
||||
kicker: 'یخچالدار',
|
||||
titleLines: ['خنککننده قدرتمند،', 'کیفیت تضمینی'],
|
||||
sub: 'نگهداری اصولی تنقلات و نوشیدنی سرد، ۲۴ ساعته و بدون نیاز به نیروی انسانی.',
|
||||
image: '/images/machine-48-base.svg',
|
||||
},
|
||||
{
|
||||
id: 'slide-3',
|
||||
kicker: 'مدیریت آنلاین',
|
||||
titleLines: ['کنترل کامل کسبوکار', 'از راه دور'],
|
||||
sub: 'مدیریت موجودی، فروش و گزارشگیری آنلاین، هر ساعت از شبانهروز.',
|
||||
image: '/images/machine-60-base.svg',
|
||||
},
|
||||
],
|
||||
},
|
||||
cart: {
|
||||
title: 'سبد خرید',
|
||||
@@ -131,6 +150,19 @@ export const content = {
|
||||
gifAlt: (name) => `نمایش عملکرد ${name}`,
|
||||
},
|
||||
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: [
|
||||
{ id: 'warranty', icon: 'safety', title: 'ضمانت کالا', description: 'کلیه محصولات شرکت دارای یک سال خدمات پس از فروش میباشند.' },
|
||||
{ id: 'consulting', icon: 'consulting', title: 'مشاوره تخصصی و رایگان', description: 'خدمات مشاوره رایگان در مورد نوع دستگاه و چیدمان محصولات.' },
|
||||
@@ -274,13 +306,30 @@ export const content = {
|
||||
},
|
||||
hero: {
|
||||
loading: 'Loading Robot Market…',
|
||||
typingPhrases: [
|
||||
'Welcome to a smart, automated world!',
|
||||
'A new experience of fast, easy shopping.',
|
||||
'Robot Market — your smart seller, around the clock.',
|
||||
],
|
||||
sub: 'A new experience of fast, easy shopping with Robot Market vending machines.',
|
||||
cta: 'View Machines',
|
||||
slides: [
|
||||
{
|
||||
id: 'slide-1',
|
||||
kicker: 'Robot Market',
|
||||
titleLines: ['Automated selling,', 'smart & non-stop'],
|
||||
sub: 'A new experience of fast, easy shopping with Robot Market vending machines.',
|
||||
image: '/images/machine-35-base.svg',
|
||||
},
|
||||
{
|
||||
id: 'slide-2',
|
||||
kicker: 'Refrigerated',
|
||||
titleLines: ['Powerful cooling,', 'guaranteed quality'],
|
||||
sub: 'Proper storage for snacks and cold drinks, 24/7, with no staff required.',
|
||||
image: '/images/machine-48-base.svg',
|
||||
},
|
||||
{
|
||||
id: 'slide-3',
|
||||
kicker: 'Online Management',
|
||||
titleLines: ['Full control of your', 'business, remotely'],
|
||||
sub: 'Manage inventory, sales, and reports online, any hour of the day.',
|
||||
image: '/images/machine-60-base.svg',
|
||||
},
|
||||
],
|
||||
},
|
||||
cart: {
|
||||
title: 'Cart',
|
||||
@@ -300,6 +349,19 @@ export const content = {
|
||||
gifAlt: (name) => `${name} in action`,
|
||||
},
|
||||
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: [
|
||||
{ id: 'warranty', icon: 'safety', title: 'Product Warranty', description: 'All our products come with one year of after-sales service.' },
|
||||
{ id: 'consulting', icon: 'consulting', title: 'Free Expert Consulting', description: 'Free consulting on the right machine type and product layout.' },
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+3
-3
@@ -736,9 +736,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.11.9",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.9.tgz",
|
||||
"integrity": "sha512-cp447VUsGS07+n1Dqf7YSQ8maeJrjEhaDxTm1ZefbqDtypHBC5GzGMQbklR6IPR13Y8OAJRHZWEMtZipJLCttg==",
|
||||
"version": "2.11.10",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.10.tgz",
|
||||
"integrity": "sha512-35JEvJ5/KKlbCHjMCsONI2w6HE88STjVdHk+C7d8LtcFxUjZR1KeLP9izofn2qs0KUxX5r4z73bwH/rd+JHacw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
Reference in New Issue
Block a user