Add Next.js RTL clone of sohansalehnia.com.
Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -0,0 +1,39 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||||
|
||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
@@ -0,0 +1,51 @@
|
||||
# Sohan Salehnia Website Context
|
||||
|
||||
## Project
|
||||
|
||||
- Persian RTL storefront clone of `sohansalehnia.com` (سوهان صالح نیا, traditional Qom sohan sweets).
|
||||
- Next.js 16 App Router, React 19, TypeScript, Tailwind 4, `src/app`.
|
||||
- Language: `fa`, direction: `rtl`.
|
||||
- Local images live in `public/images`. Fonts (Iran Yekan, Montserrat ExtraLight, Fontello/WMi icons) live in `public/fonts`.
|
||||
|
||||
## Scope (this clone)
|
||||
|
||||
Implemented pages:
|
||||
|
||||
- `/` homepage
|
||||
- `/aboutus` about
|
||||
- `/contactus` contact (form is client-side only; no backend submit yet)
|
||||
- `/products` and `/articles` are placeholders. Products, workshops, and blog will be read from Meshkee later.
|
||||
|
||||
## Brand
|
||||
|
||||
| Token | Value | Use |
|
||||
|---|---|---|
|
||||
| Primary | `#706C01` | links, buttons, product titles, branch details |
|
||||
| Gold | `#868215` | section accents, counters, English labels |
|
||||
| Page | `#F9F9F9` | page background, header, footer |
|
||||
| Heading | `#4B4B4B` | titles |
|
||||
| Muted | `#5E5E5E` | labels, footer copy |
|
||||
| Icon gray | `#939393` | social icons |
|
||||
|
||||
Logo: `public/images/logo.png` (from the live Meshkee theme). Hero, banners, product icons, pattern, and sohan photos are downloaded from the original theme CDN.
|
||||
|
||||
## Content source
|
||||
|
||||
Copy, addresses, phones, map embed, and section structure are taken from the live Meshkee site. Do not invent replacement marketing copy unless asked.
|
||||
|
||||
Branches:
|
||||
|
||||
- شعبه یکم: ميدان جهاد،باجك اول،نبش كوي ٤٤ — `02537728181`
|
||||
- شعبه دوم: قم،بلوار الغدیر،روبروی دانشگاه قم — `02537728181`
|
||||
|
||||
Login still points to `https://app.sohansalehnia.com/auth/login`.
|
||||
|
||||
## Footer credit
|
||||
|
||||
Bottom row follows the SafeTeb Meshkee credit pattern (`Designed And Developed By / Meshkee E-Commerce Team` with animated frame + white Meshkee logo), recolored to the olive/gold theme. Component: `src/components/MeshkeeCredit.tsx`.
|
||||
|
||||
## Next work
|
||||
|
||||
- Wire `/products` and `/articles` (and any workshops) to Meshkee public API.
|
||||
- Replace contact form local success state with the real endpoint.
|
||||
- Keep `CONTEXT.md` updated when routes, brand tokens, or data sources change.
|
||||
@@ -0,0 +1,11 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
images: {
|
||||
formats: ["image/avif", "image/webp"],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "sohan-salehnia",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
"build": "next build",
|
||||
"start": "next start --port 3001",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.3.0",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.3.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 294 KiB |
@@ -0,0 +1,26 @@
|
||||
import type { Metadata } from "next";
|
||||
import AboutPreview from "@/components/AboutPreview";
|
||||
import PageHeader from "@/components/PageHeader";
|
||||
import PromoBanner from "@/components/PromoBanner";
|
||||
import SohanFeature from "@/components/SohanFeature";
|
||||
import Stats from "@/components/Stats";
|
||||
import WhyUs from "@/components/WhyUs";
|
||||
import { ABOUT_PAGE, SITE } from "@/lib/site";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "درباره ما",
|
||||
description: SITE.description,
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader {...ABOUT_PAGE} />
|
||||
<SohanFeature />
|
||||
<Stats />
|
||||
<AboutPreview />
|
||||
<WhyUs />
|
||||
<PromoBanner />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Metadata } from "next";
|
||||
import Container from "@/components/Container";
|
||||
import PageHeader from "@/components/PageHeader";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "مقالات",
|
||||
};
|
||||
|
||||
export default function ArticlesPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader kicker="مقالات" title="سوهان صالح نیا" english="Articles" />
|
||||
<section className="bg-page py-20">
|
||||
<Container className="text-center">
|
||||
<p className="text-lg text-muted">
|
||||
مقالات بهزودی از طریق بکاند مشکی بارگذاری میشود.
|
||||
</p>
|
||||
</Container>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { Metadata } from "next";
|
||||
import ContactForm from "@/components/ContactForm";
|
||||
import Container from "@/components/Container";
|
||||
import PageHeader from "@/components/PageHeader";
|
||||
import { CONTACT_PAGE, CONTACT_SIDEBAR, MAP_EMBED, SITE } from "@/lib/site";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "تماس با ما",
|
||||
description: SITE.description,
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader {...CONTACT_PAGE} />
|
||||
<section className="bg-white py-16">
|
||||
<Container>
|
||||
<div className="flex flex-col items-start gap-10 lg:flex-row lg:items-center">
|
||||
<div className="w-full lg:w-2/3">
|
||||
<div className="mb-8">
|
||||
<h3 className="text-2xl text-heading">{CONTACT_PAGE.kicker}</h3>
|
||||
<p className="text-2xl text-gold">{CONTACT_PAGE.title}</p>
|
||||
<p className="mt-2 text-muted">{CONTACT_PAGE.intro}</p>
|
||||
</div>
|
||||
<ContactForm />
|
||||
</div>
|
||||
<div className="w-full lg:w-1/3">
|
||||
{CONTACT_SIDEBAR.map((item, index) => (
|
||||
<div key={`${item.title}-${index}`} className="mb-5 flex items-center gap-2">
|
||||
<i
|
||||
className={`${item.icon} text-[44px]`}
|
||||
style={{ color: "rgb(185, 185, 185)" }}
|
||||
/>
|
||||
<div>
|
||||
<h5>{item.title}</h5>
|
||||
<a href={item.href} dir="ltr" className="text-primary">
|
||||
{item.value}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
<section className="bg-page py-16">
|
||||
<Container>
|
||||
<iframe
|
||||
className="min-h-[500px] w-full rounded-[20px]"
|
||||
src={MAP_EMBED}
|
||||
width="100%"
|
||||
height="500"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
title="موقعیت سوهان صالح نیا"
|
||||
/>
|
||||
</Container>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@font-face {
|
||||
font-family: "iran-yekan";
|
||||
src: url("/fonts/iran-yekan-light.ttf") format("truetype");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "iran-yekan";
|
||||
src: url("/fonts/iran-yekan-regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "iran-yekan";
|
||||
src: url("/fonts/iran-yekan-medium.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "iran-yekan";
|
||||
src: url("/fonts/iran-yekan-bold.ttf") format("truetype");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "montserrat-extralight";
|
||||
src: url("/fonts/montserrat-extralight.ttf") format("truetype");
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "fontello";
|
||||
src: url("/fonts/fontello.woff2") format("woff2"),
|
||||
url("/fonts/fontello.woff") format("woff"),
|
||||
url("/fonts/fontello.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--wb-primary-color: #706c01;
|
||||
--wb-gold: #868215;
|
||||
--wb-muted: #5e5e5e;
|
||||
--wb-heading: #4b4b4b;
|
||||
--wb-page: #f9f9f9;
|
||||
--wb-icon: #939393;
|
||||
--background: #f9f9f9;
|
||||
--foreground: #000000;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-primary: var(--wb-primary-color);
|
||||
--color-gold: var(--wb-gold);
|
||||
--color-muted: var(--wb-muted);
|
||||
--color-heading: var(--wb-heading);
|
||||
--color-page: var(--wb-page);
|
||||
--font-sans: "iran-yekan", Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--wb-page);
|
||||
color: var(--foreground);
|
||||
font-family: "iran-yekan", Tahoma, sans-serif;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[class^="WMi-"]:before,
|
||||
[class*=" WMi-"]:before {
|
||||
font-family: "fontello";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: never;
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin: 0 !important;
|
||||
text-align: center;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.WMi-login:before {
|
||||
content: "\e877";
|
||||
}
|
||||
.WMi-instagram:before {
|
||||
content: "\f16d";
|
||||
}
|
||||
.WMi-telegram-1:before {
|
||||
content: "\f1d9";
|
||||
}
|
||||
.WMi-whatsapp:before {
|
||||
content: "\f613";
|
||||
}
|
||||
.WMi-cancel-linear:before {
|
||||
content: "\f61d";
|
||||
}
|
||||
.WMi-left-chevron:before {
|
||||
content: "\f626";
|
||||
}
|
||||
.WMi-menu-5:before {
|
||||
content: "\f693";
|
||||
}
|
||||
|
||||
.button-light {
|
||||
background-color: color-mix(in srgb, var(--wb-primary-color), white 90%);
|
||||
padding: 10px 17px;
|
||||
color: var(--wb-primary-color);
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.button-light:hover {
|
||||
background-color: var(--wb-primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button-style-2 {
|
||||
color: var(--wb-primary-color);
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.button-style-2:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -7px;
|
||||
right: 0;
|
||||
width: 50px;
|
||||
height: 1px;
|
||||
background-color: var(--wb-primary-color);
|
||||
transition: 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
.button-style-2:hover:after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.soft-shadow {
|
||||
box-shadow: 0 25px 35px -4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header-nav {
|
||||
padding-bottom: 10px;
|
||||
z-index: 1000;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
@media (min-width: 320px) {
|
||||
.header-nav {
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.header-nav {
|
||||
padding-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-nav.fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.04);
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@media (min-width: 320px) {
|
||||
.header-nav.fixed {
|
||||
margin: 10px;
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.header-nav.fixed {
|
||||
padding: 15px 30px;
|
||||
margin: 30px 70px;
|
||||
width: calc(100% - 140px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(-20px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.nav-items {
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 70%;
|
||||
padding: 60px 25px;
|
||||
box-shadow: -7px 0 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 20;
|
||||
transition: 0.4s ease-in-out;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.nav-items.open-menu {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.nav-items li:not(:last-child) {
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.overlay.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-banner img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 320px) {
|
||||
.hero-banner img {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.hero-banner img {
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.english-label {
|
||||
font-family: "montserrat-extralight", Montserrat, sans-serif;
|
||||
}
|
||||
|
||||
.form-1 input,
|
||||
.form-1 textarea {
|
||||
border: 1px solid #e6e6e6 !important;
|
||||
padding: 12px 20px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.form-1 input:focus,
|
||||
.form-1 textarea:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.form-1 label {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 30px;
|
||||
background: #fff;
|
||||
padding-inline: 10px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 20px 0 19px;
|
||||
background-color: #2a290f;
|
||||
}
|
||||
|
||||
.footer-bottom-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.footer-bottom-site {
|
||||
color: #c4bf8a;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.meshkee-credit {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 10px;
|
||||
color: #b8b48a;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.meshkee-credit:hover {
|
||||
color: #efe9b8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.meshkee-credit__frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.meshkee-credit__line {
|
||||
position: absolute;
|
||||
background: #868215;
|
||||
transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.meshkee-credit__line--l {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
transform: scaleY(0);
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.meshkee-credit__line--b {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition-delay: 0.14s;
|
||||
}
|
||||
|
||||
.meshkee-credit__line--r {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
transform: scaleY(0);
|
||||
transform-origin: bottom;
|
||||
transition-delay: 0.28s;
|
||||
}
|
||||
|
||||
.meshkee-credit__line--t {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
transform: scaleX(0);
|
||||
transform-origin: right;
|
||||
transition-delay: 0.42s;
|
||||
}
|
||||
|
||||
.meshkee-credit:hover .meshkee-credit__line--l,
|
||||
.meshkee-credit:hover .meshkee-credit__line--r {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
.meshkee-credit:hover .meshkee-credit__line--b,
|
||||
.meshkee-credit:hover .meshkee-credit__line--t {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.meshkee-credit:not(:hover) .meshkee-credit__line--l {
|
||||
transition-delay: 0.42s;
|
||||
}
|
||||
|
||||
.meshkee-credit:not(:hover) .meshkee-credit__line--b {
|
||||
transition-delay: 0.28s;
|
||||
}
|
||||
|
||||
.meshkee-credit:not(:hover) .meshkee-credit__line--r {
|
||||
transition-delay: 0.14s;
|
||||
}
|
||||
|
||||
.meshkee-credit:not(:hover) .meshkee-credit__line--t {
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
.meshkee-credit__logo {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: auto;
|
||||
height: 42px;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: contain;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.meshkee-credit:hover .meshkee-credit__logo {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.meshkee-credit__text {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 42px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.meshkee-credit__label {
|
||||
font-size: 11px;
|
||||
font-family: "montserrat-extralight", Montserrat, sans-serif;
|
||||
}
|
||||
|
||||
.meshkee-credit__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #ddd6a4;
|
||||
transition: color 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.meshkee-credit:hover .meshkee-credit__name {
|
||||
color: #fff8c8;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 575px) {
|
||||
.footer-bottom-bar {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Metadata } from "next";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { SITE } from "@/lib/site";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(SITE.url),
|
||||
title: {
|
||||
default: SITE.name,
|
||||
template: `%s | ${SITE.name}`,
|
||||
},
|
||||
description: SITE.description,
|
||||
icons: {
|
||||
icon: "/images/logo.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="fa" dir="rtl">
|
||||
<body className="flex min-h-screen flex-col antialiased">
|
||||
<Header />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import AboutPreview from "@/components/AboutPreview";
|
||||
import Container from "@/components/Container";
|
||||
import PromoBanner from "@/components/PromoBanner";
|
||||
import SohanFeature from "@/components/SohanFeature";
|
||||
import { PRODUCT_TYPES, SITE } from "@/lib/site";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<section className="hero-banner relative">
|
||||
<Link href="/">
|
||||
<Image
|
||||
src="/images/hero.jpg"
|
||||
alt={SITE.name}
|
||||
width={1603}
|
||||
height={811}
|
||||
priority
|
||||
className="w-full"
|
||||
/>
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
<section className="bg-page py-16">
|
||||
<Container>
|
||||
<div className="soft-shadow flex flex-col flex-wrap items-center rounded-[30px] px-4 py-8 lg:px-8">
|
||||
<div className="mb-6 flex items-center gap-1">
|
||||
<span className="text-[20px] text-muted">انواع محصولات</span>
|
||||
<span className="text-[24px] text-primary">{SITE.name}</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-center justify-between gap-8 lg:flex-row">
|
||||
{PRODUCT_TYPES.map((item, index) => (
|
||||
<Link
|
||||
key={`${item.title}-${index}`}
|
||||
href={item.href}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Image
|
||||
src="/images/product-habe.png"
|
||||
alt={item.title}
|
||||
width={149}
|
||||
height={161}
|
||||
className="h-auto w-[90px]"
|
||||
/>
|
||||
<p className="text-[23px] text-primary">{item.title}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
<AboutPreview />
|
||||
<SohanFeature />
|
||||
<PromoBanner />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Metadata } from "next";
|
||||
import Container from "@/components/Container";
|
||||
import PageHeader from "@/components/PageHeader";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "محصولات",
|
||||
};
|
||||
|
||||
export default function ProductsPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader kicker="محصولات" title="سوهان صالح نیا" english="Products" />
|
||||
<section className="bg-page py-20">
|
||||
<Container className="text-center">
|
||||
<p className="text-lg text-muted">
|
||||
فهرست محصولات بهزودی از طریق بکاند مشکی بارگذاری میشود.
|
||||
</p>
|
||||
</Container>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Container from "./Container";
|
||||
import { ABOUT } from "@/lib/site";
|
||||
|
||||
export default function AboutPreview() {
|
||||
return (
|
||||
<section className="bg-page py-16">
|
||||
<Container>
|
||||
<div className="flex flex-col items-center gap-10 lg:flex-row">
|
||||
<div className="w-full lg:w-1/3">
|
||||
<Image
|
||||
src="/images/about.png"
|
||||
alt="درباره سوهان صالح نیا"
|
||||
width={584}
|
||||
height={372}
|
||||
className="h-auto w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full lg:w-2/3">
|
||||
<Image
|
||||
src="/images/logo.png"
|
||||
alt=""
|
||||
width={125}
|
||||
height={40}
|
||||
/>
|
||||
<p className="my-6 leading-8 text-black">{ABOUT.body}</p>
|
||||
<div className="text-start">
|
||||
<Link href="/contactus" className="button-style-2 pb-1">
|
||||
<span>{ABOUT.cta}</span>
|
||||
<i className="WMi-left-chevron" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { FormEvent, useState } from "react";
|
||||
|
||||
export default function ContactForm() {
|
||||
const [sent, setSent] = useState(false);
|
||||
|
||||
function onSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
setSent(true);
|
||||
event.currentTarget.reset();
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit} className="form-1 mb-3">
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
<div className="relative pt-2">
|
||||
<label htmlFor="name">نام</label>
|
||||
<input type="text" name="name" id="name" required />
|
||||
</div>
|
||||
<div className="relative pt-2">
|
||||
<label htmlFor="email">ایمیل</label>
|
||||
<input type="email" name="email" id="email" required />
|
||||
</div>
|
||||
<div className="relative pt-2">
|
||||
<label htmlFor="cell_number">شماره همراه</label>
|
||||
<input type="tel" name="cell_number" id="cell_number" required />
|
||||
</div>
|
||||
<div className="relative pt-2">
|
||||
<label htmlFor="subject">موضوع</label>
|
||||
<input type="text" name="title" id="subject" required />
|
||||
</div>
|
||||
<div className="relative pt-2 lg:col-span-2">
|
||||
<label htmlFor="message">متن پیام</label>
|
||||
<textarea name="message" id="message" rows={4} required />
|
||||
</div>
|
||||
<div className="text-start lg:col-span-2">
|
||||
<button type="submit" className="button-style-2 p-0 pb-1">
|
||||
ارسال پیام
|
||||
<i className="WMi-left-chevron" />
|
||||
</button>
|
||||
{sent && (
|
||||
<p className="mt-4 text-sm text-primary">پیام شما با موفقیت ثبت شد.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export default function Container({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`mx-auto w-full max-w-[1140px] px-4 ${className}`.trim()}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import Image from "next/image";
|
||||
import Container from "./Container";
|
||||
import MeshkeeCredit from "./MeshkeeCredit";
|
||||
import { FOOTER, SITE } from "@/lib/site";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-page">
|
||||
<Container className="py-16">
|
||||
<div className="mx-auto max-w-[760px]">
|
||||
<div className="mb-12 text-center">
|
||||
<Image
|
||||
src="/images/logo.png"
|
||||
alt={SITE.name}
|
||||
width={125}
|
||||
height={40}
|
||||
className="mx-auto"
|
||||
/>
|
||||
<p className="mt-3 leading-8 text-muted">{FOOTER.about}</p>
|
||||
</div>
|
||||
<div className="grid gap-8 sm:grid-cols-2">
|
||||
{FOOTER.branches.map((branch) => (
|
||||
<div key={branch.title}>
|
||||
<h5 className="font-black text-muted">{branch.title}</h5>
|
||||
<div className="mt-3 flex flex-wrap gap-6">
|
||||
<div>
|
||||
<p className="mb-2 text-muted">آدرس ما</p>
|
||||
<p className="mb-0 text-primary">{branch.address}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-2 text-muted">شماره تماس</p>
|
||||
<a href={`tel:${branch.tel}`} className="text-primary" dir="ltr">
|
||||
{branch.phone}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ul className="mt-12 flex items-center justify-center gap-3">
|
||||
<li>
|
||||
<a href="/" aria-label="اینستاگرام" className="text-[#939393]">
|
||||
<i className="WMi-instagram text-[32px]" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/" aria-label="تلگرام" className="text-[#939393]">
|
||||
<i className="WMi-telegram-1 text-[28px]" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Container>
|
||||
<div className="footer-bottom">
|
||||
<Container>
|
||||
<div className="footer-bottom-bar">
|
||||
<div className="footer-bottom-site">{SITE.name}</div>
|
||||
<MeshkeeCredit />
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { NAV_ITEMS, SITE } from "@/lib/site";
|
||||
|
||||
export default function Header() {
|
||||
const pathname = usePathname();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [fixed, setFixed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setFixed(window.scrollY > 100);
|
||||
onScroll();
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", onScroll);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = open ? "hidden" : "";
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<header>
|
||||
{fixed ? <div className="h-[72px] lg:h-[118px]" aria-hidden /> : null}
|
||||
<nav className={`header-nav ${fixed ? "fixed" : ""}`}>
|
||||
<div className="mx-auto w-full max-w-[1140px] px-4">
|
||||
<div className="flex w-full items-center">
|
||||
<div className="flex w-full items-center justify-between lg:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="hamburger border-0 bg-transparent p-0"
|
||||
aria-label="باز کردن منو"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<i className="WMi-menu-5 text-[1.8rem] text-heading" />
|
||||
</button>
|
||||
<Link href="/">
|
||||
<Image
|
||||
src="/images/logo.png"
|
||||
alt={SITE.name}
|
||||
width={125}
|
||||
height={40}
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="hidden w-full items-center lg:flex">
|
||||
<div className="w-[41.66%]">
|
||||
<ul className="flex items-center gap-8">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={`py-2 text-[15px] transition-colors ${
|
||||
pathname === item.href
|
||||
? "text-primary"
|
||||
: "text-black hover:text-primary"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-[16.66%] text-center">
|
||||
<Link href="/" className="inline-block">
|
||||
<Image
|
||||
src="/images/logo.png"
|
||||
alt={SITE.name}
|
||||
width={125}
|
||||
height={40}
|
||||
priority
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex w-[41.66%] justify-end">
|
||||
<a
|
||||
href={SITE.loginUrl}
|
||||
className="button-light px-3 py-1 text-[1.2rem] leading-none"
|
||||
>
|
||||
ورود
|
||||
<i className="WMi-login" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!fixed && (
|
||||
<hr className="mt-4 hidden w-full border-neutral-200 lg:block" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul className={`nav-items flex flex-col lg:hidden ${open ? "open-menu" : ""}`}>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute top-[3%] left-[5%] border-0 bg-transparent"
|
||||
aria-label="بستن منو"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<i className="WMi-cancel-linear text-xl" />
|
||||
</button>
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<li key={item.href}>
|
||||
<Link href={item.href} className="flex py-2">
|
||||
{item.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<a
|
||||
href={SITE.loginUrl}
|
||||
className="button-light mt-2 px-3 py-1 text-[1.2rem] leading-none"
|
||||
>
|
||||
ورود
|
||||
<i className="WMi-login" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div
|
||||
className={`overlay ${open ? "show" : ""}`}
|
||||
onClick={() => setOpen(false)}
|
||||
/>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function MeshkeeCredit() {
|
||||
return (
|
||||
<a
|
||||
href="https://meshkee.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
dir="ltr"
|
||||
className="meshkee-credit"
|
||||
>
|
||||
<span className="meshkee-credit__frame" aria-hidden="true">
|
||||
<span className="meshkee-credit__line meshkee-credit__line--l" />
|
||||
<span className="meshkee-credit__line meshkee-credit__line--t" />
|
||||
<span className="meshkee-credit__line meshkee-credit__line--r" />
|
||||
<span className="meshkee-credit__line meshkee-credit__line--b" />
|
||||
</span>
|
||||
<Image
|
||||
src="/images/MeshkeeLogo-White.png"
|
||||
alt="Meshkee"
|
||||
width={42}
|
||||
height={42}
|
||||
className="meshkee-credit__logo"
|
||||
/>
|
||||
<span className="meshkee-credit__text">
|
||||
<span className="meshkee-credit__label">Designed And Developed By</span>
|
||||
<span className="meshkee-credit__name">Meshkee E-Commerce Team</span>
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function PageHeader({
|
||||
kicker,
|
||||
title,
|
||||
english,
|
||||
}: {
|
||||
kicker: string;
|
||||
title: string;
|
||||
english: string;
|
||||
}) {
|
||||
return (
|
||||
<section className="bg-page overflow-hidden py-12">
|
||||
<div className="flex justify-end">
|
||||
<div className="relative w-full lg:w-10/12">
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 z-0 flex items-center"
|
||||
dir="ltr"
|
||||
>
|
||||
<Image
|
||||
src="/images/pattern.png"
|
||||
alt=""
|
||||
width={1064}
|
||||
height={89}
|
||||
className="h-[89px] w-[200px] shrink-0 object-cover object-left sm:w-[260px] lg:w-[320px]"
|
||||
/>
|
||||
<span className="english-label hidden shrink-0 ps-3 text-[52px] font-thin leading-none text-gold opacity-25 sm:inline">
|
||||
{english}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative z-10 w-fit bg-page ps-3">
|
||||
<h3 className="text-[25px] font-normal text-heading">{kicker}</h3>
|
||||
<p className="text-[30px] leading-tight text-gold">{title}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="sr-only">{kicker}</h1>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Container from "./Container";
|
||||
|
||||
export default function PromoBanner() {
|
||||
return (
|
||||
<section className="bg-page">
|
||||
<Container>
|
||||
<Link href="/products" className="hidden p-12 lg:block">
|
||||
<Image
|
||||
src="/images/promo-banner.png"
|
||||
alt="سوهان صالح نیا"
|
||||
width={904}
|
||||
height={234}
|
||||
className="my-3 w-full rounded-[20px]"
|
||||
/>
|
||||
</Link>
|
||||
<Link href="/products" className="lg:hidden">
|
||||
<Image
|
||||
src="/images/promo-banner.png"
|
||||
alt="سوهان صالح نیا"
|
||||
width={904}
|
||||
height={234}
|
||||
className="my-3 w-full rounded-[10px]"
|
||||
/>
|
||||
</Link>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import Image from "next/image";
|
||||
import Container from "./Container";
|
||||
import { SOHAN_FEATURE } from "@/lib/site";
|
||||
|
||||
export default function SohanFeature() {
|
||||
return (
|
||||
<section
|
||||
className="bg-cover bg-page py-16"
|
||||
style={{ backgroundImage: "url(/images/sohan-bg.png)" }}
|
||||
>
|
||||
<Container>
|
||||
<div className="flex justify-end">
|
||||
<div className="w-full lg:w-9/12">
|
||||
<div className="mb-10 flex items-end gap-3">
|
||||
<Image
|
||||
src="/images/sohan.png"
|
||||
alt="سوهان"
|
||||
width={338}
|
||||
height={335}
|
||||
className="w-[180px] rounded-[20px] sm:w-[250px] lg:w-[350px]"
|
||||
style={{ filter: "drop-shadow(17px 31px 20px rgb(0, 0, 0, .5))" }}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-[27px] text-heading">{SOHAN_FEATURE.title}</h3>
|
||||
<p className="text-[30px] text-gold">{SOHAN_FEATURE.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="leading-8">{SOHAN_FEATURE.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Container from "./Container";
|
||||
import { STATS } from "@/lib/site";
|
||||
|
||||
function useCountUp(target: number, active: boolean) {
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
const duration = 1400;
|
||||
const start = performance.now();
|
||||
let frame = 0;
|
||||
|
||||
const tick = (now: number) => {
|
||||
const progress = Math.min((now - start) / duration, 1);
|
||||
setValue(Math.round(target * progress));
|
||||
if (progress < 1) frame = requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
frame = requestAnimationFrame(tick);
|
||||
return () => cancelAnimationFrame(frame);
|
||||
}, [active, target]);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function StatItem({
|
||||
value,
|
||||
label,
|
||||
active,
|
||||
}: {
|
||||
value: number;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}) {
|
||||
const count = useCountUp(value, active);
|
||||
return (
|
||||
<div className="text-center">
|
||||
<span className="mb-1 block text-[36px] leading-none text-gold lg:text-[49px]">
|
||||
{count}
|
||||
</span>
|
||||
<p className="text-[16px] text-heading lg:text-[20px]">{label}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Stats() {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const node = ref.current;
|
||||
if (!node) return;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActive(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.4 },
|
||||
);
|
||||
observer.observe(node);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="bg-page py-16" ref={ref}>
|
||||
<Container>
|
||||
<div className="flex flex-wrap items-center justify-around gap-8">
|
||||
{STATS.map((stat) => (
|
||||
<StatItem
|
||||
key={stat.label}
|
||||
value={stat.value}
|
||||
label={stat.label}
|
||||
active={active}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import Image from "next/image";
|
||||
import Container from "./Container";
|
||||
import { WHY_US } from "@/lib/site";
|
||||
|
||||
function WhyItem({
|
||||
title,
|
||||
subtitle,
|
||||
bordered,
|
||||
}: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
bordered?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-2 ${
|
||||
bordered ? "mb-4 border-b border-neutral-200 pb-4" : ""
|
||||
}`}
|
||||
>
|
||||
<Image src="/images/icon-1.png" alt="" width={52} height={57} />
|
||||
<div>
|
||||
<p className="text-[22px] text-heading">{title}</p>
|
||||
<p className="text-[22px] text-gold">{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WhyUs() {
|
||||
const [leftTop, leftBottom, rightTop, rightBottom] = WHY_US.items;
|
||||
|
||||
return (
|
||||
<section className="bg-page py-16">
|
||||
<Container>
|
||||
<div className="mb-8 flex flex-col items-center p-1">
|
||||
<h3 className="text-[25px] text-heading">{WHY_US.kicker}</h3>
|
||||
<span className="inline-block text-[25px] text-gold">{WHY_US.title}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-around lg:flex-row">
|
||||
<div className="w-full lg:w-1/4">
|
||||
<WhyItem {...leftTop} bordered />
|
||||
<WhyItem {...leftBottom} />
|
||||
</div>
|
||||
<div className="col-10 mx-auto my-8 w-10/12 lg:w-1/4">
|
||||
<Image
|
||||
src="/images/sohan.png"
|
||||
alt="سوهان"
|
||||
width={338}
|
||||
height={335}
|
||||
className="h-auto w-full"
|
||||
style={{ filter: "drop-shadow(17px 31px 20px rgb(0, 0, 0, .5))" }}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full lg:w-1/4">
|
||||
<WhyItem {...rightTop} bordered />
|
||||
<WhyItem {...rightBottom} />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
export const SITE = {
|
||||
name: "سوهان صالح نیا",
|
||||
nameEn: "Sohan Salehnia",
|
||||
url: "https://www.sohansalehnia.com",
|
||||
loginUrl:
|
||||
"https://app.sohansalehnia.com/auth/login?redirect=http://www.sohansalehnia.com",
|
||||
description:
|
||||
"سوهان صالح نیا، تولید و عرضه انواع سوهان سنتی قم با طعم تازه و اصیل.",
|
||||
primary: "#706C01",
|
||||
gold: "#868215",
|
||||
};
|
||||
|
||||
export const NAV_ITEMS = [
|
||||
{ href: "/", label: "صفحه اصلی" },
|
||||
{ href: "/products", label: "محصولات" },
|
||||
{ href: "/articles", label: "مقالات" },
|
||||
{ href: "/contactus", label: "تماس با ما" },
|
||||
{ href: "/aboutus", label: "درباره ما" },
|
||||
] as const;
|
||||
|
||||
export const PRODUCT_TYPES = [
|
||||
{ title: "حبه سوهان", href: "/products" },
|
||||
{ title: "حبه سوهان", href: "/products" },
|
||||
{ title: "حبه سوهان", href: "/products" },
|
||||
] as const;
|
||||
|
||||
export const ABOUT = {
|
||||
heading: "درباره ما",
|
||||
subheading: "از ما بیشتر بدانید ...",
|
||||
body: "لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان کـــــــه لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه است.",
|
||||
cta: "همین حالا با ما تماس بگیرید",
|
||||
};
|
||||
|
||||
export const SOHAN_FEATURE = {
|
||||
title: "سوهان",
|
||||
subtitle: "لذیذ و تازه بلوط",
|
||||
body: "لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبــود ابزارهای کاربردی می باشد.",
|
||||
};
|
||||
|
||||
export const ABOUT_PAGE = {
|
||||
kicker: "درباره سوهان",
|
||||
title: "صالح نیا بیشتــــر بدانیــــــد...",
|
||||
english: "About us",
|
||||
};
|
||||
|
||||
export const CONTACT_PAGE = {
|
||||
kicker: "با ما",
|
||||
title: "تماس بگیرید",
|
||||
english: "Contact us",
|
||||
intro: "همیشه در دسترس هستیم، با ما در ارتباط باشید",
|
||||
};
|
||||
|
||||
export const STATS = [
|
||||
{ value: 230, label: "ارسال های هفتگی" },
|
||||
{ value: 5, label: "پشتیبانی" },
|
||||
{ value: 1000, label: "کاربران خوشحال" },
|
||||
{ value: 430, label: "تنوع محصولات" },
|
||||
] as const;
|
||||
|
||||
export const WHY_US = {
|
||||
kicker: "چرا باید",
|
||||
title: "ما را انتخاب کنید",
|
||||
items: [
|
||||
{ title: "ارسال", subtitle: "رایگان در شهر قم" },
|
||||
{ title: "ارسال", subtitle: "رایگان در شهر قم" },
|
||||
{ title: "ارسال", subtitle: "رایگان در شهر قم" },
|
||||
{ title: "ارسال", subtitle: "رایگان در شهر قم" },
|
||||
],
|
||||
} as const;
|
||||
|
||||
export const FOOTER = {
|
||||
about:
|
||||
"لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است.ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک",
|
||||
branches: [
|
||||
{
|
||||
title: "اطلاعات تماس شعبه یکم",
|
||||
address: "ميدان جهاد،باجك اول،نبش كوي ٤٤",
|
||||
phone: "37728181 - (025)",
|
||||
tel: "02537728181",
|
||||
},
|
||||
{
|
||||
title: "اطلاعات تماس شعبه دوم",
|
||||
address: "قم،بلوار الغدیر،روبروی دانشگاه قم",
|
||||
phone: "37728181 - (025)",
|
||||
tel: "02537728181",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const CONTACT_SIDEBAR = [
|
||||
{
|
||||
icon: "WMi-instagram",
|
||||
title: "شماره تماس (شعبه یک)",
|
||||
value: "(025) 37728181",
|
||||
href: "tel:02537728181",
|
||||
},
|
||||
{
|
||||
icon: "WMi-whatsapp",
|
||||
title: "شماره تماس (شعبه یک)",
|
||||
value: "(025) 37728181",
|
||||
href: "tel:02537728181",
|
||||
},
|
||||
{
|
||||
icon: "WMi-whatsapp",
|
||||
title: "شماره تماس (شعبه یک)",
|
||||
value: "(025) 37728181",
|
||||
href: "tel:02537728181",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const MAP_EMBED =
|
||||
"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3282.0930113137806!2d50.8950383!3d34.65235390000001!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3f93bb9b1d8a57f5%3A0x27365b97ef84d3db!2z2LTbjNix24zZhtuMINio2YTZiNi3!5e0!3m2!1sen!2s!4v1767243250550!5m2!1sen!2s";
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||