initial project
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
NEXT_PUBLIC_API_BASE_URL=https://api.robotmarket.ir/v1
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.env*.local
|
||||||
|
.DS_Store
|
||||||
|
out
|
||||||
|
build
|
||||||
|
*.log
|
||||||
Generated
+70
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="01b90d18-2bd3-488f-bee5-27ff6a73414d" name="Changes" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"associatedIndex": 2,
|
||||||
|
"fromUser": false
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="3H7KB12mCznfw4hRrFCPWKMzxsB" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">{
|
||||||
|
"keyToString": {
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
|
"js.debugger.nextJs.config.created.client": "true",
|
||||||
|
"js.debugger.nextJs.config.created.server": "true",
|
||||||
|
"last_opened_file_path": "D:/Project/robot-market-landing/robot-market",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}</component>
|
||||||
|
<component name="RunManager" selected="npm.Next.js: server-side">
|
||||||
|
<configuration name="Next.js: debug client-side" type="JavascriptDebugType" uri="http://localhost:3000/">
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="Next.js: server-side" type="js.build_tools.npm">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="dev" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-WS-261.23567.141" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="01b90d18-2bd3-488f-bee5-27ff6a73414d" name="Changes" comment="" />
|
||||||
|
<created>1785213528048</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1785213528048</updated>
|
||||||
|
<workItem from="1785213529281" duration="2289000" />
|
||||||
|
<workItem from="1785300366173" duration="6178000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,2 +1,29 @@
|
|||||||
# robot-market-landing
|
# روبات مارکت — سایت نمایش محصولات
|
||||||
robot-market-landing
|
|
||||||
|
## نصب و اجرا
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
سپس آدرس `http://localhost:3000` را در مرورگر باز کنید.
|
||||||
|
|
||||||
|
## ساختار پروژه
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
app/ # layout.js و page.js (App Router)
|
||||||
|
components/ # کامپوننتهای بخشهای سایت
|
||||||
|
data/ # محتوای متنی و محصولات (content.js)
|
||||||
|
utils/api.js # نمونه Axios برای اتصال به API در آینده
|
||||||
|
```
|
||||||
|
|
||||||
|
## نکات
|
||||||
|
|
||||||
|
- رنگ اصلی برند در `tailwind.config.js` روی `#00a693` تنظیم شده است.
|
||||||
|
- فونت وزیرمتن بهصورت خودکار از `next/font/google` بارگذاری میشود.
|
||||||
|
- برای جایگزینی عکس واقعی دستگاه بهجای طرح SVG، فایل
|
||||||
|
`src/components/VendingMachineParts.js` را ویرایش کنید.
|
||||||
|
- برای اتصال فرم "سفارش و مشاوره" یا دکمه "افزودن به سبد" به بکاند واقعی،
|
||||||
|
از توابع آماده در `src/utils/api.js` استفاده کنید.
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"jsx": "react"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "**",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
Generated
+1883
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "robot-market",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "14.2.5",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"axios": "^1.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
|
"tailwindcss": "^3.4.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #f2fffd;
|
||||||
|
color: #04211d;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: #00a693;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thin scrollbar, brand-tinted */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f2fffd;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #00a693;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blueprint-grid {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(0, 166, 147, 0.09) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(0, 166, 147, 0.09) 1px, transparent 1px);
|
||||||
|
background-size: 42px 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner-brackets {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.corner-brackets::before,
|
||||||
|
.corner-brackets::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-color: #00a693;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.corner-brackets::before {
|
||||||
|
top: -6px;
|
||||||
|
right: -6px;
|
||||||
|
border-top: 2px solid;
|
||||||
|
border-right: 2px solid;
|
||||||
|
}
|
||||||
|
.corner-brackets::after {
|
||||||
|
bottom: -6px;
|
||||||
|
left: -6px;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
border-left: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
* {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Vazirmatn } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const vazir = Vazirmatn({
|
||||||
|
subsets: ["arabic"],
|
||||||
|
weight: ["300", "400", "500", "600", "700", "800", "900"],
|
||||||
|
variable: "--font-vazir",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "روبات مارکت | تولیدکننده دستگاههای وندینگ ماشین صنعتی",
|
||||||
|
description:
|
||||||
|
"روبات مارکت، تولیدکننده تخصصی دستگاههای خودپرداز (وندینگ ماشین) خنککننده با استانداردهای مهندسی روز و بازگشت سرمایه مطمئن. سفارش و مشاوره رایگان.",
|
||||||
|
keywords: [
|
||||||
|
"وندینگ ماشین",
|
||||||
|
"دستگاه خودپرداز",
|
||||||
|
"روبات مارکت",
|
||||||
|
"دستگاه فروش خودکار",
|
||||||
|
"vending machine",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="fa" dir="rtl" className={vazir.variable}>
|
||||||
|
<body className="font-vazir antialiased">{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Header from "@/components/Header";
|
||||||
|
import Hero from "@/components/Hero";
|
||||||
|
import About from "@/components/About";
|
||||||
|
import Locations from "@/components/Locations";
|
||||||
|
import Products from "@/components/Products";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
import ScrollRefresh from "@/components/ScrollRefresh";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<ScrollRefresh />
|
||||||
|
<Header />
|
||||||
|
<Hero />
|
||||||
|
<About />
|
||||||
|
<Locations />
|
||||||
|
<Products />
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {useEffect, useRef} from "react";
|
||||||
|
import {gsap} from "@/utils/gsap";
|
||||||
|
|
||||||
|
const TIMELINE = [
|
||||||
|
{
|
||||||
|
year: "۱۳۹۶",
|
||||||
|
title: "آغاز فعالیت در تجهیزات زعفرانکوبی",
|
||||||
|
desc: "شروع کار با طراحی و تولید ماشینآلات فرآوری زعفران با تمرکز بر دقت مهندسی و کیفیت ساخت داخلی.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: "۱۴۰۰",
|
||||||
|
title: "ورود به صنعت اتوماسیون فروش",
|
||||||
|
desc: "توسعه دانش فنی تیم مهندسی به سمت سامانههای خردهفروشی خودکار و مکانیزمهای دیسپنس هوشمند.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: "۱۴۰۲",
|
||||||
|
title: "تولید نخستین وندینگ ماشین خنککننده",
|
||||||
|
desc: "معرفی نسل اول دستگاههای خودپرداز خنککننده روبات مارکت با استاندارد سرمایش صنعتی.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: "۱۴۰۳",
|
||||||
|
title: "توسعه خط تولید و شبکه فروش سراسری",
|
||||||
|
desc: "افزایش ظرفیت تولید، تنوع مدلها (۳۵ تا ۶۰ کانال) و پشتیبانی فنی در سراسر کشور.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const STANDARDS = [
|
||||||
|
{label: "استاندارد مهندسی برق و ایمنی صنعتی"},
|
||||||
|
{label: "بدنه فولادی ضدضربه با پوشش ضدزنگ"},
|
||||||
|
{label: "سیستم سرمایش کمپرسوری استاندارد"},
|
||||||
|
{label: "بازگشت سرمایه (ROI) مطمئن و قابل پیشبینی"},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
gsap.utils.toArray(".timeline-item").forEach((item, i) => {
|
||||||
|
gsap.fromTo(
|
||||||
|
item,
|
||||||
|
{opacity: 0, y: 30},
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.7,
|
||||||
|
ease: "power2.out",
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: item,
|
||||||
|
start: "top 85%",
|
||||||
|
},
|
||||||
|
delay: i * 0.05,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.fromTo(
|
||||||
|
".timeline-line",
|
||||||
|
{scaleY: 0},
|
||||||
|
{
|
||||||
|
scaleY: 1,
|
||||||
|
transformOrigin: "top",
|
||||||
|
ease: "none",
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: ".timeline-wrap",
|
||||||
|
start: "top 70%",
|
||||||
|
end: "bottom 80%",
|
||||||
|
scrub: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, rootRef);
|
||||||
|
|
||||||
|
return () => ctx.revert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="about" ref={rootRef} className="relative py-24 md:py-32 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-5 md:px-8">
|
||||||
|
<div className="grid lg:grid-cols-[0.9fr_1.1fr] gap-16">
|
||||||
|
{/* Left: intro + standards */}
|
||||||
|
<div>
|
||||||
|
<span className="text-xs font-bold text-primary tracking-wide">معرفی شرکت</span>
|
||||||
|
<h2 className="mt-3 text-3xl md:text-4xl font-black text-ink-900 leading-snug">
|
||||||
|
مسیری مهندسیشده از صنعت زعفران تا اتوماسیون فروش
|
||||||
|
</h2>
|
||||||
|
<p className="mt-5 text-ink-500 leading-8">
|
||||||
|
روبات مارکت با پیشینهای در طراحی ماشینآلات دقیق صنعتی، امروز یکی از تولیدکنندگان
|
||||||
|
تخصصی وندینگ ماشینهای خنککننده در ایران است. تیم مهندسی ما هر دستگاه را با
|
||||||
|
استانداردهای برق صنعتی، ایمنی مصرفکننده و دوام بلندمدت طراحی میکند تا سرمایهگذاری
|
||||||
|
شما با کمترین ریسک و بیشترین بازده همراه باشد.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul className="mt-8 space-y-4">
|
||||||
|
{STANDARDS.map((s) => (
|
||||||
|
<li key={s.label} className="flex items-center gap-3">
|
||||||
|
<span
|
||||||
|
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary-light text-primary">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
|
||||||
|
<path d="M5 13l4 4L19 7" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span className="text-sm md:text-base text-ink-700 font-medium">{s.label}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: timeline */}
|
||||||
|
<div className="timeline-wrap relative pr-8">
|
||||||
|
<div className="timeline-line absolute right-[7px] top-1 bottom-1 w-[2px] bg-primary/25"/>
|
||||||
|
<div className="space-y-10">
|
||||||
|
{TIMELINE.map((t) => (
|
||||||
|
<div key={t.year} className="timeline-item relative opacity-0">
|
||||||
|
<span
|
||||||
|
className="absolute right-[-25px] top-1 h-4 w-4 rounded-full bg-primary ring-4 ring-primary-light"/>
|
||||||
|
<span className="inline-block text-primary font-black text-lg mb-1">
|
||||||
|
{t.year}
|
||||||
|
</span>
|
||||||
|
<h3 className="font-bold text-ink-900 text-base md:text-lg">{t.title}</h3>
|
||||||
|
<p className="text-sm text-ink-500 mt-1.5 leading-7">{t.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { CONTACT, NAV_LINKS } from "@/data/content";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer id="contact" className="relative bg-ink-900 text-white pt-20 pb-8">
|
||||||
|
<div className="max-w-7xl mx-auto px-5 md:px-8">
|
||||||
|
<div className="grid md:grid-cols-[1.2fr_1fr_1fr] gap-12 pb-14 border-b border-white/10">
|
||||||
|
{/* Brand + CTA */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary text-white font-black text-sm">
|
||||||
|
RM
|
||||||
|
</span>
|
||||||
|
<span className="font-extrabold text-lg">روبات مارکت</span>
|
||||||
|
</div>
|
||||||
|
<p className="mt-4 text-sm text-white/60 leading-7 max-w-sm">
|
||||||
|
تولیدکننده تخصصی دستگاههای خودپرداز خنککننده با استانداردهای مهندسی روز، برای
|
||||||
|
فروش هوشمند و بیوقفه در هر مکان.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href={`tel:${CONTACT.phoneHref}`}
|
||||||
|
className="mt-6 inline-flex items-center gap-2 rounded-full bg-primary px-6 py-3 text-sm font-bold hover:bg-primary-hover transition-colors"
|
||||||
|
>
|
||||||
|
سفارش و مشاوره رایگان
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nav */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-sm text-white/80 mb-4">دسترسی سریع</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{NAV_LINKS.map((l) => (
|
||||||
|
<li key={l.href}>
|
||||||
|
<a href={l.href} className="text-sm text-white/60 hover:text-primary transition-colors">
|
||||||
|
{l.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-sm text-white/80 mb-4">اطلاعات تماس</h4>
|
||||||
|
<ul className="space-y-3 text-sm text-white/60 leading-7">
|
||||||
|
<li>{CONTACT.factory}</li>
|
||||||
|
<li>{CONTACT.office}</li>
|
||||||
|
<li dir="ltr" className="text-right">
|
||||||
|
<a href={`tel:${CONTACT.phoneHref}`} className="hover:text-primary transition-colors">
|
||||||
|
{CONTACT.phone}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="mt-5 flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href={CONTACT.instagram}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="اینستاگرام روبات مارکت"
|
||||||
|
className="flex h-10 w-10 items-center justify-center rounded-full bg-white/10 hover:bg-primary transition-colors"
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="5" />
|
||||||
|
<circle cx="12" cy="12" r="4" />
|
||||||
|
<circle cx="17.5" cy="6.5" r="1" fill="currentColor" stroke="none" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={CONTACT.bale}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="بله روبات مارکت"
|
||||||
|
className="flex h-10 w-10 items-center justify-center rounded-full bg-white/10 hover:bg-primary transition-colors text-xs font-bold"
|
||||||
|
>
|
||||||
|
بله
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6 flex flex-col md:flex-row items-center justify-between gap-3 text-xs text-white/40">
|
||||||
|
<span>© {new Date().getFullYear()} روبات مارکت. تمامی حقوق محفوظ است.</span>
|
||||||
|
<span>طراحی و توسعه با ❤ در مشهد</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { NAV_LINKS, CONTACT } from "@/data/content";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setScrolled(window.scrollY > 24);
|
||||||
|
onScroll();
|
||||||
|
window.addEventListener("scroll", onScroll, { passive: true });
|
||||||
|
return () => window.removeEventListener("scroll", onScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`fixed top-0 inset-x-0 z-50 transition-all duration-300 ${
|
||||||
|
scrolled
|
||||||
|
? "bg-white/90 backdrop-blur-md shadow-[0_1px_0_0_rgba(0,166,147,0.15)]"
|
||||||
|
: "bg-transparent"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto px-4 md:px-8 flex items-center justify-between h-14 md:h-20">
|
||||||
|
{/* Logo */}
|
||||||
|
<a href="#top" className="flex items-center gap-2 group min-w-0">
|
||||||
|
<span className="relative flex h-7 w-7 md:h-9 md:w-9 shrink-0 items-center justify-center rounded-lg bg-primary text-white font-black text-[10px] md:text-sm">
|
||||||
|
RM
|
||||||
|
<span className="absolute inset-0 rounded-lg ring-2 ring-primary/30 scale-110 group-hover:scale-125 transition-transform" />
|
||||||
|
</span>
|
||||||
|
<span className="flex flex-col leading-tight min-w-0">
|
||||||
|
<span className="font-extrabold text-ink-900 text-xs sm:text-sm md:text-lg truncate">
|
||||||
|
روبات مارکت
|
||||||
|
</span>
|
||||||
|
<span className="hidden sm:block text-[10px] md:text-xs text-ink-500 tracking-wide truncate">
|
||||||
|
تولیدکننده وندینگ ماشین صنعتی
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Desktop nav */}
|
||||||
|
<nav className="hidden lg:flex items-center gap-8">
|
||||||
|
{NAV_LINKS.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="text-sm font-medium text-ink-700 hover:text-primary transition-colors relative after:absolute after:-bottom-1 after:right-0 after:h-[2px] after:w-0 after:bg-primary after:transition-all hover:after:w-full"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="hidden lg:flex items-center gap-4">
|
||||||
|
<a
|
||||||
|
href={`tel:${CONTACT.phoneHref}`}
|
||||||
|
dir="ltr"
|
||||||
|
className="text-sm font-semibold text-ink-700 hover:text-primary transition-colors"
|
||||||
|
>
|
||||||
|
{CONTACT.phone}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#contact"
|
||||||
|
className="rounded-full bg-primary px-5 py-2.5 text-sm font-bold text-white shadow-lg shadow-primary/20 hover:bg-primary-hover hover:-translate-y-0.5 transition-all"
|
||||||
|
>
|
||||||
|
سفارش و مشاوره
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile toggle */}
|
||||||
|
<button
|
||||||
|
onClick={() => setOpen((o) => !o)}
|
||||||
|
className="lg:hidden flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-primary/20 text-primary"
|
||||||
|
aria-label="باز کردن منو"
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
{open ? (
|
||||||
|
<path d="M6 6l12 12M18 6L6 18" strokeLinecap="round" />
|
||||||
|
) : (
|
||||||
|
<path d="M3 6h18M3 12h18M3 18h18" strokeLinecap="round" />
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile menu */}
|
||||||
|
{open && (
|
||||||
|
<div className="lg:hidden bg-white border-t border-primary/10 px-4 py-4 flex flex-col gap-3">
|
||||||
|
{NAV_LINKS.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className="text-sm font-medium text-ink-700"
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
<a
|
||||||
|
href="#contact"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className="rounded-full bg-primary px-5 py-2.5 text-sm font-bold text-white text-center"
|
||||||
|
>
|
||||||
|
سفارش و مشاوره
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {useEffect, useRef} from "react";
|
||||||
|
import {VMTop, VMMiddle, VMBottom} from "./VendingMachineParts";
|
||||||
|
import {gsap, ScrollTrigger} from "@/utils/gsap";
|
||||||
|
|
||||||
|
const FEATURES = [
|
||||||
|
{
|
||||||
|
id: "ui",
|
||||||
|
label: "نمایشگر لمسی هوشمند",
|
||||||
|
desc: "رابط کاربری فارسی و انتخاب سریع کالا",
|
||||||
|
top: "8%",
|
||||||
|
side: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cooling",
|
||||||
|
label: "سیستم خنککننده صنعتی",
|
||||||
|
desc: "کمپرسور مستقل با کنترل دمای دقیق",
|
||||||
|
top: "42%",
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "payment",
|
||||||
|
label: "ترمینال پرداخت هوشمند",
|
||||||
|
desc: "کارتخوان بانکی و پرداخت آنلاین",
|
||||||
|
top: "78%",
|
||||||
|
side: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "body",
|
||||||
|
label: "بدنه فولادی ضدضربه",
|
||||||
|
desc: "مقاوم در برابر سرقت و ضربه",
|
||||||
|
top: "60%",
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Hero() {
|
||||||
|
const sectionRef = useRef(null);
|
||||||
|
const topRef = useRef(null);
|
||||||
|
const midRef = useRef(null);
|
||||||
|
const botRef = useRef(null);
|
||||||
|
const calloutsRef = useRef([]);
|
||||||
|
const eyebrowRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mm = gsap.matchMedia();
|
||||||
|
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
mm.add(
|
||||||
|
{
|
||||||
|
isDesktop: "(min-width: 1024px)",
|
||||||
|
isMobile: "(max-width: 1023px)",
|
||||||
|
},
|
||||||
|
(context) => {
|
||||||
|
const {isMobile} = context.conditions;
|
||||||
|
const distance = isMobile ? 120 : 220;
|
||||||
|
const scrollLength = isMobile ? 1200 : 1800;
|
||||||
|
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: sectionRef.current,
|
||||||
|
start: "top top",
|
||||||
|
end: `+=${scrollLength}`,
|
||||||
|
scrub: 0.8,
|
||||||
|
pin: true,
|
||||||
|
anticipatePin: 1,
|
||||||
|
invalidateOnRefresh: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
tl.fromTo(
|
||||||
|
topRef.current,
|
||||||
|
{y: -distance, opacity: 0.4},
|
||||||
|
{y: 0, opacity: 1, duration: 1, ease: "power2.out"},
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.fromTo(
|
||||||
|
botRef.current,
|
||||||
|
{y: distance, opacity: 0.4},
|
||||||
|
{y: 0, opacity: 1, duration: 1, ease: "power2.out"},
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.fromTo(
|
||||||
|
midRef.current,
|
||||||
|
{opacity: 0, scale: 0.92},
|
||||||
|
{opacity: 1, scale: 1, duration: 1, ease: "power2.out"},
|
||||||
|
0.15
|
||||||
|
)
|
||||||
|
.to({}, {duration: 0.35}) // brief hold once assembled (pin breathing room)
|
||||||
|
.fromTo(
|
||||||
|
calloutsRef.current,
|
||||||
|
{opacity: 0, x: (i) => (i % 2 === 0 ? -24 : 24)},
|
||||||
|
{opacity: 1, x: 0, stagger: 0.15, duration: 0.6, ease: "power2.out"},
|
||||||
|
">-0.1"
|
||||||
|
)
|
||||||
|
.fromTo(
|
||||||
|
eyebrowRef.current,
|
||||||
|
{opacity: 1},
|
||||||
|
{opacity: 0.5, duration: 0.3},
|
||||||
|
"<"
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => tl.scrollTrigger?.kill();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, sectionRef);
|
||||||
|
|
||||||
|
// Fonts/SVG layout can settle a beat after this effect runs — refresh
|
||||||
|
// once more on the next frame so the pin/scrub distances are accurate
|
||||||
|
// on the very first load, not just after a manual scroll-refresh.
|
||||||
|
const raf = requestAnimationFrame(() => ScrollTrigger.refresh());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(raf);
|
||||||
|
ctx.revert();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="top"
|
||||||
|
ref={sectionRef}
|
||||||
|
className="relative min-h-screen bg-primary-light blueprint-grid pt-16 sm:pt-20 md:pt-28"
|
||||||
|
>
|
||||||
|
<div className="max-w-7xl mx-auto px-5 md:px-8 grid lg:grid-cols-2 gap-10 items-center h-full">
|
||||||
|
{/* Copy */}
|
||||||
|
<div className="order-2 lg:order-none relative z-10 pb-10 lg:pb-0 text-center lg:text-right">
|
||||||
|
<span
|
||||||
|
ref={eyebrowRef}
|
||||||
|
className="inline-flex items-center gap-2 rounded-full border border-primary/30 bg-white px-3 md:px-4 py-1.5 text-[11px] md:text-xs font-bold text-primary"
|
||||||
|
>
|
||||||
|
<span className="h-1.5 w-1.5 rounded-full bg-primary animate-pulse"/>
|
||||||
|
از تجهیزات زعفرانکوبی ۱۳۹۶ تا وندینگ ماشین هوشمند
|
||||||
|
</span>
|
||||||
|
<h1 className="mt-6 text-3xl sm:text-4xl md:text-6xl font-black text-ink-900 leading-[1.2] text-balance">
|
||||||
|
روبات مارکت،
|
||||||
|
<span className="text-primary"> مهندسی فروش خودکار</span> برای کسبوکار شما
|
||||||
|
</h1>
|
||||||
|
<p className="mt-5 text-sm sm:text-base md:text-lg text-ink-500 leading-7 md:leading-8 max-w-xl mx-auto lg:mx-0">
|
||||||
|
تولیدکننده وندینگ ماشینهای خنککننده صنعتی با استانداردهای مهندسی روز، طراحیشده
|
||||||
|
برای فروش بدونوقفه، ۲۴ ساعته و بازگشت سرمایه مطمئن.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex flex-wrap justify-center lg:justify-start gap-4">
|
||||||
|
<a
|
||||||
|
href="#products"
|
||||||
|
className="rounded-full bg-primary px-6 md:px-7 py-3 md:py-3.5 text-sm font-bold text-white shadow-lg shadow-primary/25 hover:bg-primary-hover hover:-translate-y-0.5 transition-all"
|
||||||
|
>
|
||||||
|
مشاهده محصولات
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#contact"
|
||||||
|
className="rounded-full border-2 border-primary/30 px-6 md:px-7 py-3 md:py-3.5 text-sm font-bold text-ink-900 hover:border-primary hover:text-primary transition-all"
|
||||||
|
>
|
||||||
|
سفارش و مشاوره رایگان
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p className="mt-6 text-xs text-ink-500">
|
||||||
|
برای مشاهده اسکرول کنید — دستگاه بهصورت زنده در برابر چشمان شما مونتاژ میشود
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* GSAP Assembly Machine */}
|
||||||
|
<div
|
||||||
|
className="order-1 lg:order-none relative h-[46vh] sm:h-[55vh] lg:h-[80vh] flex items-center justify-center">
|
||||||
|
<div className="relative w-[170px] sm:w-[220px] md:w-[260px] lg:w-[300px]">
|
||||||
|
<div ref={topRef} className="relative z-30 will-change-transform">
|
||||||
|
<VMTop/>
|
||||||
|
</div>
|
||||||
|
<div ref={midRef} className="relative z-20 -mt-px will-change-transform">
|
||||||
|
<VMMiddle/>
|
||||||
|
</div>
|
||||||
|
<div ref={botRef} className="relative z-30 -mt-px will-change-transform">
|
||||||
|
<VMBottom/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Feature callouts */}
|
||||||
|
{FEATURES.map((f, i) => (
|
||||||
|
<div
|
||||||
|
key={f.id}
|
||||||
|
ref={(el) => (calloutsRef.current[i] = el)}
|
||||||
|
className={`hidden sm:flex absolute items-center gap-2 w-40 md:w-48 opacity-0 ${
|
||||||
|
f.side === "left" ? "right-full ml-3 flex-row-reverse text-right" : "left-full mr-3 text-right"
|
||||||
|
}`}
|
||||||
|
style={{top: f.top}}
|
||||||
|
>
|
||||||
|
{f.side === "left" && (
|
||||||
|
<span className="h-px w-6 md:w-10 bg-primary/60 shrink-0"/>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
<span className="block text-xs md:text-sm font-bold text-ink-900">
|
||||||
|
{f.label}
|
||||||
|
</span>
|
||||||
|
<span className="block text-[10px] md:text-xs text-ink-500">{f.desc}</span>
|
||||||
|
</span>
|
||||||
|
{f.side === "right" && (
|
||||||
|
<span className="h-px w-6 md:w-10 bg-primary/60 shrink-0"/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile feature list (callout lines are desktop-only) */}
|
||||||
|
<div className="sm:hidden max-w-md mx-auto px-5 pb-10 grid grid-cols-2 gap-3">
|
||||||
|
{FEATURES.map((f) => (
|
||||||
|
<div key={f.id} className="rounded-xl bg-white/70 border border-primary/15 p-3">
|
||||||
|
<p className="text-xs font-bold text-ink-900">{f.label}</p>
|
||||||
|
<p className="text-[10px] text-ink-500 mt-1">{f.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
export function Icon({ name, className = "h-6 w-6" }) {
|
||||||
|
const common = {
|
||||||
|
className,
|
||||||
|
viewBox: "0 0 24 24",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
strokeWidth: 1.8,
|
||||||
|
strokeLinecap: "round",
|
||||||
|
strokeLinejoin: "round",
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "hotel":
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M3 21V7a1 1 0 0 1 1-1h4v15" />
|
||||||
|
<path d="M8 21V3h9a1 1 0 0 1 1 1v17" />
|
||||||
|
<path d="M3 21h18" />
|
||||||
|
<path d="M11 7h4M11 11h4M11 15h4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case "school":
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M2 9l10-5 10 5-10 5-10-5z" />
|
||||||
|
<path d="M6 11v5c0 1.5 2.5 3 6 3s6-1.5 6-3v-5" />
|
||||||
|
<path d="M22 9v6" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case "pharmacy":
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<rect x="3" y="7" width="18" height="14" rx="2" />
|
||||||
|
<path d="M12 11v6M9 14h6" />
|
||||||
|
<path d="M8 7V5a4 4 0 0 1 8 0v2" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case "hospital":
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<rect x="4" y="3" width="16" height="18" rx="2" />
|
||||||
|
<path d="M12 8v6M9 11h6" />
|
||||||
|
<path d="M9 21v-3M15 21v-3" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case "moon":
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case "brand":
|
||||||
|
return (
|
||||||
|
<svg {...common}>
|
||||||
|
<path d="M12 2l2.4 6.6L21 11l-6.6 2.4L12 20l-2.4-6.6L3 11l6.6-2.4z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import { LOCATIONS } from "@/data/content";
|
||||||
|
import { Icon } from "./Icons";
|
||||||
|
import { gsap } from "@/utils/gsap";
|
||||||
|
|
||||||
|
export default function Locations() {
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
gsap.fromTo(
|
||||||
|
".location-card",
|
||||||
|
{ opacity: 0, y: 36 },
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.6,
|
||||||
|
ease: "power2.out",
|
||||||
|
stagger: 0.08,
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: ".location-grid",
|
||||||
|
start: "top 82%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, rootRef);
|
||||||
|
|
||||||
|
return () => ctx.revert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="locations" ref={rootRef} className="relative py-24 md:py-32 bg-primary-light">
|
||||||
|
<div className="max-w-7xl mx-auto px-5 md:px-8">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<span className="text-xs font-bold text-primary tracking-wide">مکانهای مناسب نصب</span>
|
||||||
|
<h2 className="mt-3 text-3xl md:text-4xl font-black text-ink-900">
|
||||||
|
هرجا نیاز به فروش بیوقفه باشد، جای وندینگ ماشین است
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-ink-500 leading-7">
|
||||||
|
دستگاههای روبات مارکت برای انطباق با فضاهای مختلف طراحی شدهاند؛ از فضاهای پرتردد تا
|
||||||
|
نصب پشت ویترین برای فروش شبانه.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="location-grid mt-14 grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
|
{LOCATIONS.map((loc) => (
|
||||||
|
<div
|
||||||
|
key={loc.id}
|
||||||
|
className="location-card group relative rounded-2xl bg-white p-6 border border-primary/10 shadow-sm hover:shadow-xl hover:shadow-primary/10 hover:-translate-y-1 transition-all duration-300 opacity-0"
|
||||||
|
>
|
||||||
|
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary-light text-primary group-hover:bg-primary group-hover:text-white transition-colors">
|
||||||
|
<Icon name={loc.icon} />
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-5 font-bold text-ink-900 text-lg">{loc.title}</h3>
|
||||||
|
<p className="mt-2 text-sm text-ink-500 leading-7">{loc.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { PRODUCTS, formatRial } from "@/data/content";
|
||||||
|
import { gsap } from "@/utils/gsap";
|
||||||
|
|
||||||
|
export default function Products() {
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
const [added, setAdded] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
gsap.fromTo(
|
||||||
|
".product-card",
|
||||||
|
{ opacity: 0, y: 40 },
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.7,
|
||||||
|
ease: "power2.out",
|
||||||
|
stagger: 0.12,
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: ".product-grid",
|
||||||
|
start: "top 80%",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, rootRef);
|
||||||
|
|
||||||
|
return () => ctx.revert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAdd = (id) => {
|
||||||
|
setAdded((prev) => ({ ...prev, [id]: true }));
|
||||||
|
// Static demo action — wire this to `submitConsultationRequest` in
|
||||||
|
// src/utils/api.js once a cart/order API is available.
|
||||||
|
setTimeout(() => setAdded((prev) => ({ ...prev, [id]: false })), 2200);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="products" ref={rootRef} className="relative py-24 md:py-32 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-5 md:px-8">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<span className="text-xs font-bold text-primary tracking-wide">محصولات تخصصی</span>
|
||||||
|
<h2 className="mt-3 text-3xl md:text-4xl font-black text-ink-900">
|
||||||
|
مدلهای وندینگ ماشین روبات مارکت
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-ink-500 leading-7">
|
||||||
|
سه مدل با ظرفیتهای مختلف، متناسب با ترافیک و فضای محل نصب شما.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="product-grid mt-14 grid md:grid-cols-3 gap-6">
|
||||||
|
{PRODUCTS.map((p) => (
|
||||||
|
<div
|
||||||
|
key={p.id}
|
||||||
|
className={`product-card opacity-0 relative rounded-3xl p-7 flex flex-col border transition-all duration-300 ${
|
||||||
|
p.highlight
|
||||||
|
? "bg-ink-900 border-primary text-white lg:-translate-y-4 shadow-2xl shadow-primary/20"
|
||||||
|
: "bg-primary-light border-primary/10 text-ink-900 hover:-translate-y-2 hover:shadow-xl"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{p.highlight && (
|
||||||
|
<span className="absolute -top-3 right-7 rounded-full bg-primary px-4 py-1 text-[11px] font-bold text-white">
|
||||||
|
پرفروشترین
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={`text-xs font-bold tracking-wide ${
|
||||||
|
p.highlight ? "text-info" : "text-primary"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{p.channels} کانال
|
||||||
|
</span>
|
||||||
|
<h3 className="mt-2 text-xl font-black">{p.name}</h3>
|
||||||
|
<p className={`mt-1 text-sm ${p.highlight ? "text-white/60" : "text-ink-500"}`}>
|
||||||
|
{p.tagline}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<span className="text-2xl md:text-3xl font-black" dir="ltr">
|
||||||
|
{formatRial(p.price)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="mt-6 space-y-3 flex-1">
|
||||||
|
{p.features.map((f) => (
|
||||||
|
<li key={f} className="flex items-start gap-2.5 text-sm">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="3"
|
||||||
|
className={`mt-0.5 shrink-0 ${p.highlight ? "text-info" : "text-primary"}`}
|
||||||
|
>
|
||||||
|
<path d="M5 13l4 4L19 7" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
<span className={p.highlight ? "text-white/85" : "text-ink-700"}>{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => handleAdd(p.id)}
|
||||||
|
className={`mt-8 w-full rounded-full py-3.5 text-sm font-bold transition-all ${
|
||||||
|
p.highlight
|
||||||
|
? "bg-primary text-white hover:bg-primary-hover"
|
||||||
|
: "bg-ink-900 text-white hover:bg-primary"
|
||||||
|
} ${added[p.id] ? "!bg-success" : ""}`}
|
||||||
|
>
|
||||||
|
{added[p.id] ? "به سبد سفارش اضافه شد ✓" : "افزودن به سبد سفارش"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-8 text-center text-xs text-ink-500">
|
||||||
|
قیمتها بهروزرسانی دورهای دارند؛ برای استعلام نهایی و امکان پرداخت اقساطی با کارشناسان
|
||||||
|
فروش تماس بگیرید.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { ScrollTrigger } from "@/utils/gsap";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mounted once near the root of the page. Fonts (Vazirmatn) and the
|
||||||
|
* inline SVG artwork can finish loading/laying out slightly after
|
||||||
|
* ScrollTrigger has already taken its initial measurements, which makes
|
||||||
|
* pinned sections (like the Hero assembly) start blank until the user
|
||||||
|
* scrolls back up and down again. Forcing a refresh once everything has
|
||||||
|
* settled fixes that on the very first load.
|
||||||
|
*/
|
||||||
|
export default function ScrollRefresh() {
|
||||||
|
useEffect(() => {
|
||||||
|
const refresh = () => ScrollTrigger.refresh();
|
||||||
|
|
||||||
|
if (document.fonts && document.fonts.ready) {
|
||||||
|
document.fonts.ready.then(refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", refresh);
|
||||||
|
// Fallback in case fonts/images settle without firing the above events.
|
||||||
|
const t1 = setTimeout(refresh, 300);
|
||||||
|
const t2 = setTimeout(refresh, 1000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("load", refresh);
|
||||||
|
clearTimeout(t1);
|
||||||
|
clearTimeout(t2);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// Schematic / blueprint-style artwork for the vending machine, split into
|
||||||
|
// three physically-accurate parts so the Hero section can animate them
|
||||||
|
// together with GSAP. Replace with real product photography later by
|
||||||
|
// swapping the <svg> content inside each part — the wrapper sizes/ids stay.
|
||||||
|
|
||||||
|
export function VMTop() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 150" className="w-full block" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 150 V40 Q20 10 50 10 H350 Q380 10 380 40 V150 Z" fill="#0c3a34" />
|
||||||
|
<path d="M20 150 V40 Q20 10 50 10 H350 Q380 10 380 40 V150 Z" fill="url(#topGrad)" opacity="0.9" />
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="topGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stopColor="#0f4a42" />
|
||||||
|
<stop offset="1" stopColor="#0c3a34" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
{/* brand plate */}
|
||||||
|
<rect x="150" y="28" width="100" height="16" rx="4" fill="#00a693" />
|
||||||
|
<text x="200" y="40" textAnchor="middle" fontSize="10" fill="#f2fffd" fontFamily="sans-serif" fontWeight="700">
|
||||||
|
ROBOT MARKET
|
||||||
|
</text>
|
||||||
|
{/* smart UI screen */}
|
||||||
|
<rect x="130" y="58" width="140" height="80" rx="8" fill="#04211d" stroke="#00bba6" strokeWidth="2" />
|
||||||
|
<rect x="140" y="68" width="120" height="46" rx="4" fill="#00a693" opacity="0.25" />
|
||||||
|
<rect x="140" y="68" width="70" height="6" rx="3" fill="#00bba6" />
|
||||||
|
<rect x="140" y="80" width="90" height="4" rx="2" fill="#00bba6" opacity="0.6" />
|
||||||
|
<rect x="140" y="90" width="60" height="4" rx="2" fill="#00bba6" opacity="0.6" />
|
||||||
|
<circle cx="250" cy="126" r="6" fill="#00bba6" />
|
||||||
|
{/* cooling vents */}
|
||||||
|
{[0, 1, 2, 3, 4].map((i) => (
|
||||||
|
<rect key={i} x={36} y={34 + i * 8} width="60" height="3" rx="1.5" fill="#00bba6" opacity="0.5" />
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VMMiddle() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 300" className="w-full block" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="20" y="0" width="360" height="300" fill="#0e4841" />
|
||||||
|
{/* glass front */}
|
||||||
|
<rect x="42" y="18" width="316" height="264" rx="6" fill="#04211d" stroke="#00a693" strokeWidth="2" />
|
||||||
|
{/* product coils - 3 rows x 4 columns */}
|
||||||
|
{[0, 1, 2].map((row) => (
|
||||||
|
<g key={row}>
|
||||||
|
{[0, 1, 2, 3].map((col) => (
|
||||||
|
<g key={col}>
|
||||||
|
<rect
|
||||||
|
x={58 + col * 74}
|
||||||
|
y={34 + row * 84}
|
||||||
|
width="60"
|
||||||
|
height="70"
|
||||||
|
rx="4"
|
||||||
|
fill="#0c3a34"
|
||||||
|
stroke="#00bba6"
|
||||||
|
strokeOpacity="0.5"
|
||||||
|
/>
|
||||||
|
{[0, 1, 2, 3].map((c) => (
|
||||||
|
<circle
|
||||||
|
key={c}
|
||||||
|
cx={58 + col * 74 + 12 + c * 12}
|
||||||
|
cy={34 + row * 84 + 35}
|
||||||
|
r="9"
|
||||||
|
fill="#00a693"
|
||||||
|
opacity="0.65"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
|
{/* LED strip */}
|
||||||
|
<rect x="20" y="0" width="8" height="300" fill="#00bba6" opacity="0.8" />
|
||||||
|
<rect x="372" y="0" width="8" height="300" fill="#00bba6" opacity="0.8" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VMBottom() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 150" className="w-full block" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 0 H380 V110 Q380 140 350 140 H50 Q20 140 20 110 Z" fill="#0c3a34" />
|
||||||
|
{/* payment terminal */}
|
||||||
|
<rect x="70" y="20" width="90" height="60" rx="6" fill="#04211d" stroke="#00a693" strokeWidth="2" />
|
||||||
|
<rect x="80" y="30" width="70" height="16" rx="3" fill="#00a693" opacity="0.4" />
|
||||||
|
<rect x="80" y="52" width="30" height="18" rx="3" fill="#00bba6" />
|
||||||
|
<text x="95" y="65" textAnchor="middle" fontSize="9" fill="#04211d" fontWeight="700">
|
||||||
|
POS
|
||||||
|
</text>
|
||||||
|
{/* coin slot + card reader */}
|
||||||
|
<rect x="150" y="52" width="4" height="16" rx="2" fill="#00bba6" />
|
||||||
|
{/* dispense flap */}
|
||||||
|
<rect x="240" y="30" width="110" height="60" rx="8" fill="#04211d" stroke="#00bba6" strokeWidth="2" />
|
||||||
|
<rect x="255" y="70" width="80" height="8" rx="4" fill="#00a693" />
|
||||||
|
<text x="295" y="52" textAnchor="middle" fontSize="9" fill="#00bba6" fontFamily="sans-serif">
|
||||||
|
محل تحویل کالا
|
||||||
|
</text>
|
||||||
|
{/* legs */}
|
||||||
|
<rect x="60" y="132" width="16" height="16" rx="2" fill="#0c3a34" stroke="#00a693" />
|
||||||
|
<rect x="324" y="132" width="16" height="16" rx="2" fill="#0c3a34" stroke="#00a693" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
export const NAV_LINKS = [
|
||||||
|
{ label: "معرفی شرکت", href: "#about" },
|
||||||
|
{ label: "مکانهای نصب", href: "#locations" },
|
||||||
|
{ label: "محصولات", href: "#products" },
|
||||||
|
{ label: "تماس با ما", href: "#contact" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOCATIONS = [
|
||||||
|
{
|
||||||
|
id: "hotels",
|
||||||
|
title: "هتلها و اقامتگاهها",
|
||||||
|
desc: "دسترسی شبانهروزی مهمانان به نوشیدنی و تنقلات، بدون نیاز به نیروی انسانی اضافه در لابی.",
|
||||||
|
icon: "hotel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "education",
|
||||||
|
title: "مراکز آموزشی و دانشگاهها",
|
||||||
|
desc: "پاسخگویی سریع به تراکم بالای دانشآموزان و دانشجویان در زنگهای تفریح و فاصله کلاسها.",
|
||||||
|
icon: "school",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pharmacy",
|
||||||
|
title: "داروخانهها و سوپرمارکتها",
|
||||||
|
desc: "افزایش ساعات فروش فعال بدون افزایش هزینه پرسنل، مکمل فروش حضوری فروشگاه.",
|
||||||
|
icon: "pharmacy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "hospital",
|
||||||
|
title: "بیمارستانها و مراکز درمانی",
|
||||||
|
desc: "تامین نیاز همراهان بیمار در تمام ساعات شبانهروز در راهروها و بخشهای انتظار.",
|
||||||
|
icon: "hospital",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "night-shift",
|
||||||
|
title: "فروش شبانه پشت ویترین",
|
||||||
|
desc: "نصب پشت شیشه مغازه برای فروش ۲۴ ساعته حتی در زمان تعطیلی فروشگاه، بدون ریسک امنیتی.",
|
||||||
|
icon: "moon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "branding",
|
||||||
|
title: "برندسازی اختصاصی",
|
||||||
|
desc: "امکان طراحی نما و رابط کاربری دستگاه مطابق هویت بصری برند شما برای فضاهای تجاری.",
|
||||||
|
icon: "brand",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PRODUCTS = [
|
||||||
|
{
|
||||||
|
id: "vm-35",
|
||||||
|
channels: 35,
|
||||||
|
name: "وندینگ ماشین ۳۵ کانال",
|
||||||
|
tagline: "مناسب فضاهای کوچک و متوسط",
|
||||||
|
price: 3860000000,
|
||||||
|
features: [
|
||||||
|
"ظرفیت ۳۵ کانال فروش مستقل",
|
||||||
|
"سیستم خنککننده کمپرسوری",
|
||||||
|
"نمایشگر لمسی هوشمند",
|
||||||
|
"ترمینال پرداخت کارتخوان و آنلاین",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vm-48",
|
||||||
|
channels: 48,
|
||||||
|
name: "وندینگ ماشین ۴۸ کانال",
|
||||||
|
tagline: "پرفروشترین مدل، تعادل ظرفیت و ابعاد",
|
||||||
|
price: 4480000000,
|
||||||
|
features: [
|
||||||
|
"ظرفیت ۴۸ کانال فروش مستقل",
|
||||||
|
"کنترل دما دیجیتال دقیق",
|
||||||
|
"پنل کاربری فارسی با گزارش فروش",
|
||||||
|
"بدنه فولادی ضدضربه و ضدسرقت",
|
||||||
|
],
|
||||||
|
highlight: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vm-60",
|
||||||
|
channels: 60,
|
||||||
|
name: "وندینگ ماشین ۶۰ کانال",
|
||||||
|
tagline: "بیشترین ظرفیت، مناسب ترافیک بالا",
|
||||||
|
price: 4980000000,
|
||||||
|
features: [
|
||||||
|
"ظرفیت ۶۰ کانال فروش مستقل",
|
||||||
|
"دو کمپرسور مستقل برای پایداری دما",
|
||||||
|
"اتصال اینترنتی و مانیتورینگ از راه دور",
|
||||||
|
"طراحی مدولار برای شارژ سریع کالا",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CONTACT = {
|
||||||
|
factory: "کارخانه: مشهد، شهرک صنعتی توس",
|
||||||
|
office: "دفتر مرکزی: مشهد، برج نخل",
|
||||||
|
phone: "۰۹۱۵۱۶۰۱۸۴۷",
|
||||||
|
phoneHref: "+989151601847",
|
||||||
|
instagram: "https://instagram.com/robot.market",
|
||||||
|
bale: "https://ble.ir/robot.market",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatRial(amount) {
|
||||||
|
return amount.toLocaleString("fa-IR") + " ریال";
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central Axios instance for Robot Market.
|
||||||
|
* Every future API call (orders, consultation requests, product sync, ...)
|
||||||
|
* should go through this instance so headers, base URL and error
|
||||||
|
* handling only need to be configured in one place.
|
||||||
|
*/
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || "https://api.robotmarket.ir/v1",
|
||||||
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach an auth token automatically, if one is available client-side.
|
||||||
|
api.interceptors.request.use((config) => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const token = window.localStorage.getItem("rm_token");
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Normalize errors so components can rely on a consistent shape.
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
const message =
|
||||||
|
error?.response?.data?.message || error?.message || "خطای ناشناخته در ارتباط با سرور";
|
||||||
|
return Promise.reject({ ...error, message });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example future usage:
|
||||||
|
* submitConsultationRequest({ name, phone, city })
|
||||||
|
*/
|
||||||
|
export async function submitConsultationRequest(payload) {
|
||||||
|
const { data } = await api.post("/leads/consultation", payload);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example future usage:
|
||||||
|
* fetchProducts()
|
||||||
|
*/
|
||||||
|
export async function fetchProducts() {
|
||||||
|
const { data } = await api.get("/products");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default api;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import gsap from "gsap";
|
||||||
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||||
|
|
||||||
|
// Registering the plugin synchronously at module scope (guarded for SSR)
|
||||||
|
// avoids the race condition where ScrollTrigger measures the page before
|
||||||
|
// fonts/images have finished loading, which is what caused the "blank on
|
||||||
|
// first load, fixed after scrolling back up" bug.
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
// Mobile browsers fire resize events when the address bar shows/hides
|
||||||
|
// while scrolling. ScrollTrigger would otherwise treat that as a real
|
||||||
|
// viewport change and re-run its (expensive, sometimes wrong) pin
|
||||||
|
// calculations mid-scroll, which is what made the assembled machine
|
||||||
|
// disappear or freeze on phones.
|
||||||
|
ScrollTrigger.config({ ignoreMobileResize: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
export { gsap, ScrollTrigger };
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/app/**/*.{js,jsx}",
|
||||||
|
"./src/components/**/*.{js,jsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "#00a693",
|
||||||
|
hover: "#009b89",
|
||||||
|
light: "#f2fffd",
|
||||||
|
},
|
||||||
|
success: "#00ab97",
|
||||||
|
error: "#005148",
|
||||||
|
info: "#00bba6",
|
||||||
|
ink: {
|
||||||
|
900: "#04211d",
|
||||||
|
700: "#0c3a34",
|
||||||
|
500: "#3a615c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
vazir: ["var(--font-vazir)", "Tahoma", "sans-serif"],
|
||||||
|
},
|
||||||
|
backgroundImage: {
|
||||||
|
"grid-pattern":
|
||||||
|
"linear-gradient(rgba(0,166,147,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(0,166,147,0.08) 1px, transparent 1px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
floatY: {
|
||||||
|
"0%, 100%": { transform: "translateY(0px)" },
|
||||||
|
"50%": { transform: "translateY(-12px)" },
|
||||||
|
},
|
||||||
|
pulseRing: {
|
||||||
|
"0%": { transform: "scale(0.9)", opacity: "0.8" },
|
||||||
|
"100%": { transform: "scale(1.6)", opacity: "0" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
floatY: "floatY 4s ease-in-out infinite",
|
||||||
|
pulseRing: "pulseRing 2s ease-out infinite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user