Initial Mashinify homepage rebuild in Next.js.

FA/RTL marketplace home with search-first hero, categories, and stock data from mashinify.com.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 01:03:30 +03:30
co-authored by Cursor
commit e74e5ca45f
67 changed files with 7931 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
import Image from "next/image";
import { site, stats } from "@/data/home";
export function AboutCta() {
return (
<section id="about" className="relative overflow-hidden">
<div className="grid lg:grid-cols-2">
<div className="relative min-h-[22rem] lg:min-h-[28rem]">
<Image
src="/images/cta/about.jpg"
alt="درباره ماشینیفای"
fill
className="object-cover"
sizes="(max-width: 1024px) 100vw, 50vw"
/>
</div>
<div className="relative flex items-center bg-brand-ink px-6 py-14 text-white md:px-12">
<div className="absolute inset-0 bg-[linear-gradient(135deg,rgba(0,109,212,0.22),transparent_55%)]" />
<div className="relative max-w-xl">
<p className="section-kicker !text-brand">About Us</p>
<h2 className="mt-2 text-[clamp(1.6rem,2.5vw,2.2rem)] font-extrabold leading-snug">
همین حالا با ما تماس بگیرید
</h2>
<p className="mt-4 text-white/75">
{site.description} تیم پشتیبانی ما آماده پاسخگویی به پرسشهای شما
درباره انتخاب، مقایسه و استعلام ماشینآلات است.
</p>
<div className="mt-8 grid grid-cols-3 gap-3 border-y border-white/15 py-5">
{stats.map((item) => (
<div key={item.label}>
<div className="font-industrial text-xl font-bold tracking-wide text-brand md:text-2xl">
{item.value}
</div>
<div className="mt-1 text-xs text-white/65 md:text-sm">
{item.label}
</div>
</div>
))}
</div>
<div className="mt-8 flex flex-wrap gap-3">
<a href="#contact" className="btn-primary">
تماس بگیرید
</a>
<a href="#categories" className="btn-ghost">
بیشتر بدانید
</a>
</div>
</div>
</div>
</div>
</section>
);
}
+47
View File
@@ -0,0 +1,47 @@
import Image from "next/image";
import { articles } from "@/data/home";
export function Articles() {
return (
<section id="articles" className="section bg-surface">
<div className="container-page">
<div className="section-head">
<p className="section-kicker">Insights</p>
<h2 className="section-title">مقالات</h2>
<p className="section-desc">
نکات تخصصی برای خرید هوشمندانه و ارتقاء خطوط تولید صنعتی.
</p>
</div>
<div className="grid gap-5 md:grid-cols-3">
{articles.map((article) => (
<a
key={article.id}
href={article.href}
className="group block overflow-hidden border border-line transition hover:border-brand/40"
>
<div className="relative aspect-[16/10] overflow-hidden bg-[#e8eef4]">
<Image
src={article.image}
alt={article.title}
fill
className="object-cover transition duration-500 group-hover:scale-[1.04]"
sizes="(max-width: 768px) 100vw, 33vw"
/>
</div>
<div className="p-5">
<h3 className="text-base font-extrabold leading-7 text-brand-ink transition group-hover:text-brand">
{article.title}
</h3>
<span className="mt-4 inline-flex items-center gap-2 text-sm font-bold text-brand">
بیشتر بخوانید
<span aria-hidden></span>
</span>
</div>
</a>
))}
</div>
</div>
</section>
);
}
+53
View File
@@ -0,0 +1,53 @@
import Image from "next/image";
import { categories } from "@/data/home";
export function Categories() {
return (
<section id="categories" className="section bg-surface">
<div className="container-page">
<div className="section-head flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div>
<p className="section-kicker">Categories</p>
<h2 className="section-title">دستهبندیهای اصلی</h2>
<p className="section-desc">
مسیر سریع به موجودی نو، کارکرده، خطوط سیم و کابل و محتوای آموزشی.
</p>
</div>
<a
href="#products"
className="font-industrial text-sm tracking-[0.12em] text-brand transition hover:text-brand-deep"
>
VIEW STOCK
</a>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{categories.map((cat, index) => (
<a
key={cat.id}
href={cat.href}
className="group relative block min-h-[22rem] overflow-hidden bg-brand-ink"
style={{ animationDelay: `${index * 0.08}s` }}
>
<Image
src={cat.image}
alt={cat.title}
fill
className="object-cover transition duration-700 ease-out group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 25vw"
/>
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(6,35,63,0.15)_0%,rgba(6,35,63,0.88)_100%)]" />
<div className="absolute inset-x-0 bottom-0 p-5 text-white">
<p className="font-industrial text-[0.7rem] tracking-[0.16em] text-brand">
{cat.titleEn}
</p>
<h3 className="mt-1 text-xl font-extrabold">{cat.title}</h3>
<p className="mt-2 text-sm text-white/75">{cat.description}</p>
</div>
</a>
))}
</div>
</div>
</section>
);
}
+68
View File
@@ -0,0 +1,68 @@
import { footerLinks, site } from "@/data/home";
export function Footer() {
return (
<footer id="contact" className="bg-brand-ink text-white">
<div className="container-page grid gap-10 py-14 md:grid-cols-2 lg:grid-cols-4">
<div className="lg:col-span-2">
<p className="font-industrial text-3xl font-bold tracking-[0.12em]">
{site.nameEn}
</p>
<p className="mt-2 text-lg font-bold">{site.name}</p>
<p className="mt-4 max-w-md text-sm leading-7 text-white/70">
{site.description}
</p>
<div className="mt-6 space-y-2 text-sm text-white/80">
<p>{site.address}</p>
<a
href={`tel:${site.phone}`}
className="latin-digits inline-block text-brand"
dir="ltr"
>
{site.phoneDisplay}
</a>
</div>
</div>
<div>
<h4 className="font-industrial mb-4 text-sm tracking-[0.14em] text-brand">
PAGES
</h4>
<ul className="space-y-2 text-sm text-white/75">
{footerLinks.pages.map((item) => (
<li key={item.label}>
<a href={item.href} className="transition hover:text-white">
{item.label}
</a>
</li>
))}
</ul>
</div>
<div>
<h4 className="font-industrial mb-4 text-sm tracking-[0.14em] text-brand">
CATEGORIES
</h4>
<ul className="space-y-2 text-sm text-white/75">
{footerLinks.categories.map((item) => (
<li key={item.label}>
<a href={item.href} className="transition hover:text-white">
{item.label}
</a>
</li>
))}
</ul>
</div>
</div>
<div className="border-t border-white/10">
<div className="container-page flex flex-col items-start justify-between gap-3 py-5 text-xs text-white/50 sm:flex-row sm:items-center">
<p>
© {new Date().getFullYear()} {site.name} {site.nameEn}
</p>
<p>پاسخگوی پرسشهای شما هستیم</p>
</div>
</div>
</footer>
);
}
+125
View File
@@ -0,0 +1,125 @@
"use client";
import { useEffect, useState } from "react";
import { navItems, site } from "@/data/home";
export function Header() {
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
useEffect(() => {
document.body.style.overflow = open ? "hidden" : "";
return () => {
document.body.style.overflow = "";
};
}, [open]);
return (
<header
className={`fixed inset-x-0 top-0 z-50 transition-colors duration-300 ${
scrolled || open
? "border-b border-white/10 bg-brand-ink/95 backdrop-blur-md"
: "bg-transparent"
}`}
>
<div className="container-page flex items-center justify-between gap-4 py-4 text-white">
<a href="/" className="group flex items-center gap-3 !text-white">
<span className="grid h-10 w-10 place-items-center border border-brand bg-brand/20 font-industrial text-lg font-bold tracking-wide text-brand transition group-hover:bg-brand group-hover:!text-white">
M
</span>
<span className="leading-none">
<span className="font-industrial block text-xl font-bold tracking-[0.14em] !text-white">
{site.nameEn}
</span>
<span className="mt-1 block text-xs !text-white/70">{site.name}</span>
</span>
</a>
<nav className="hidden items-center gap-1 lg:flex">
{navItems.map((item) => (
<a
key={item.label}
href={item.href}
className="px-3 py-2 text-sm !text-white/85 transition hover:!text-white"
>
{item.label}
</a>
))}
</nav>
<div className="hidden items-center gap-4 lg:flex">
<a
href={`tel:${site.phone}`}
className="latin-digits text-sm !text-white/85 transition hover:!text-white"
dir="ltr"
>
{site.phoneDisplay}
</a>
<a
href={site.loginUrl}
className="btn-primary !min-h-10 !px-4 !text-sm"
target="_blank"
rel="noopener noreferrer"
>
ورود فروشندگان
</a>
</div>
<button
type="button"
aria-label="منو"
className="grid h-10 w-10 place-items-center border border-white/25 !text-white lg:hidden"
onClick={() => setOpen((v) => !v)}
>
<span className="sr-only">منو</span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
{open ? (
<path
d="M6 6l12 12M18 6 6 18"
stroke="currentColor"
strokeWidth="1.8"
/>
) : (
<path
d="M4 7h16M4 12h16M4 17h16"
stroke="currentColor"
strokeWidth="1.8"
/>
)}
</svg>
</button>
</div>
{open ? (
<div className="border-t border-white/10 bg-brand-ink lg:hidden">
<nav className="container-page flex flex-col gap-1 py-4">
{navItems.map((item) => (
<a
key={item.label}
href={item.href}
className="px-2 py-3 !text-white/90"
onClick={() => setOpen(false)}
>
{item.label}
</a>
))}
<a
href={`tel:${site.phone}`}
className="latin-digits mt-2 px-2 py-3 !text-brand"
dir="ltr"
>
{site.phoneDisplay}
</a>
</nav>
</div>
) : null}
</header>
);
}
+89
View File
@@ -0,0 +1,89 @@
"use client";
import Image from "next/image";
import { FormEvent, useState } from "react";
import { searchSuggestions, site } from "@/data/home";
export function Hero() {
const [query, setQuery] = useState("");
const onSubmit = (e: FormEvent) => {
e.preventDefault();
const q = query.trim();
if (!q) return;
window.location.hash = `products`;
};
return (
<section className="relative min-h-[100svh] overflow-hidden bg-brand-ink text-white">
<Image
src="/images/hero/search-bg.jpg"
alt="ماشین‌آلات صنعتی"
fill
priority
className="object-cover object-[center_35%] scale-105"
sizes="100vw"
/>
<div className="absolute inset-0 bg-[linear-gradient(105deg,rgba(6,35,63,0.94)_0%,rgba(6,35,63,0.78)_52%,rgba(0,109,212,0.5)_100%)]" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_18%_18%,rgba(0,109,212,0.32),transparent_40%)]" />
<div className="relative z-10 flex min-h-[100svh] flex-col justify-end pb-16 pt-28 md:justify-center md:pb-24 md:pt-32">
<div className="container-page">
<p className="animate-rise section-kicker !text-[#7ec2ff]">
Heavy Machinery Marketplace
</p>
<h1 className="animate-rise animate-rise-delay-1 mt-3 max-w-4xl">
<span className="font-industrial block text-[clamp(3.2rem,11vw,7rem)] font-bold leading-[0.9] tracking-[0.06em]">
{site.nameEn}
</span>
<span className="mt-4 block max-w-2xl text-[clamp(1.2rem,2.6vw,1.75rem)] font-extrabold leading-snug text-white">
{site.tagline}
</span>
</h1>
<p className="animate-rise animate-rise-delay-2 mt-5 max-w-xl text-base text-white/80 md:text-lg">
{site.name}؛ جستجوی سریع ماشینآلات صنعتی نو و کارکرده برای
تولیدکنندگان و فروشندگان.
</p>
<form
onSubmit={onSubmit}
className="animate-rise animate-rise-delay-3 search-shell mt-8 max-w-3xl border border-white/15 bg-white/95 p-2 shadow-[0_20px_60px_rgba(0,0,0,0.28)] backdrop-blur-sm"
>
<div className="flex flex-col gap-2 sm:flex-row sm:items-stretch">
<label className="sr-only" htmlFor="machine-search">
جستجوی ماشینآلات
</label>
<input
id="machine-search"
name="q"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="نام کالای مورد نظر خود را جستجو کنید"
className="min-h-14 flex-1 bg-transparent px-4 text-base text-brand-ink outline-none placeholder:text-[#8a97a6]"
required
/>
<button type="submit" className="btn-primary min-h-14 px-8">
جستجو
</button>
</div>
</form>
<div className="mt-4 flex flex-wrap gap-2">
{searchSuggestions.map((item) => (
<button
key={item}
type="button"
onClick={() => setQuery(item)}
className="border border-white/20 bg-white/5 px-3 py-1.5 text-xs text-white/80 transition hover:border-brand hover:bg-brand/20 hover:text-white"
>
{item}
</button>
))}
</div>
</div>
</div>
</section>
);
}
+65
View File
@@ -0,0 +1,65 @@
import Image from "next/image";
import { products } from "@/data/home";
export function Products() {
return (
<section id="products" className="section">
<div className="container-page">
<div className="section-head">
<p className="section-kicker">Stock Machines</p>
<h2 className="section-title">محصولات موجود</h2>
<p className="section-desc">
منتخب ماشینآلات سنگین و صنعتی از فروشگاه ماشینیفای؛ برای جزئیات و
قیمت، استعلام بگیرید.
</p>
</div>
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
{products.map((product) => (
<article
key={product.id}
className="group flex h-full flex-col overflow-hidden border border-line bg-surface transition duration-300 hover:-translate-y-1 hover:border-brand/40"
>
<a href={product.href} className="relative block aspect-[4/3] overflow-hidden bg-[#e8eef4]">
<Image
src={product.image}
alt={product.title}
fill
className="object-cover transition duration-500 group-hover:scale-[1.04]"
sizes="(max-width: 768px) 100vw, 25vw"
/>
<span className="absolute left-3 top-3 bg-brand-ink/90 px-2.5 py-1 text-xs text-white">
{product.condition}
</span>
</a>
<div className="flex flex-1 flex-col p-4">
<p className="font-industrial text-[0.68rem] tracking-[0.14em] text-brand">
{product.titleEn}
</p>
<h3 className="mt-1 text-base font-extrabold leading-7 text-brand-ink">
<a href={product.href}>{product.title}</a>
</h3>
<p className="mt-2 text-sm text-muted">{product.category}</p>
<div className="mt-auto pt-4">
<a
href="#contact"
className="inline-flex w-full items-center justify-center border border-brand px-3 py-2.5 text-sm font-bold text-brand transition hover:bg-brand hover:text-white"
>
استعلام از فروشنده
</a>
</div>
</div>
</article>
))}
</div>
<div className="mt-8 text-center">
<a href="#contact" className="btn-primary">
محصولات بیشتر
</a>
</div>
</div>
</section>
);
}