Dejband.com clone — Next.js, RTL Persian
- Homepage faithfully matching dejband.com (hero, services, consulting,
stats, about block, timeline, portfolio, articles) + About/Contact/
Portfolios/Articles pages under source URLs
- Meshkee API integration: live blog content, favicon, user menu,
sitemap/robots proxy, canonical /blog/{id}/{slug}
- SEO pass: one H1 per page, unique metadata + canonicals, alt text
- Contact form wired to /api/contact with toast + reset
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "dejband-dev",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "dev"],
|
||||
"port": 3000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/node_modules
|
||||
/.next
|
||||
/out
|
||||
/build
|
||||
.DS_Store
|
||||
*.pem
|
||||
npm-debug.log*
|
||||
.env*
|
||||
@@ -0,0 +1,33 @@
|
||||
# Dejband.com — Next.js clone
|
||||
|
||||
Marketing-site replica of [dejband.com](https://dejband.com) (مهندسین مشاور دژبند قم) —
|
||||
Persian, RTL, dark theme. Homepage, About Us, and Contact Us pages.
|
||||
|
||||
See [`context/CONTEXT.md`](context/CONTEXT.md) for the full project context, design
|
||||
tokens, component map, asset inventory, and the running prompt-iteration log.
|
||||
|
||||
## Getting started
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # http://localhost:3000
|
||||
```
|
||||
|
||||
Production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
- `src/app/` — `layout.js`, `globals.css`, `page.js` (home), `about/`, `contact/`
|
||||
- `src/components/` — Header, Footer, Hero, Services, Consulting, Stats, Timeline, etc.
|
||||
- `src/lib/site.js` — all page content/data in one place
|
||||
- `public/images`, `public/fonts` — assets pulled from the source site
|
||||
|
||||
## Not in scope (yet)
|
||||
|
||||
Blog / articles and portfolios are deferred to a later backend-integration phase.
|
||||
The contact form is client-only with no submit endpoint.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Dejband.com — Clone Project Context
|
||||
|
||||
> Living document. Update on **every** prompt iteration so other AI tools / developers
|
||||
> can pick up work without re-discovering decisions.
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Rebuild a marketing-site replica of **dejband.com** ("مهندسین مشاور دژبند قم" — Dejband
|
||||
Consulting Engineers, Qom) using **Next.js (App Router)**, in **Persian (`fa`)** with
|
||||
**RTL** layout.
|
||||
|
||||
Scope for this phase — **3 pages only**:
|
||||
|
||||
| Page | Route | Source |
|
||||
| --- | --- | --- |
|
||||
| Homepage | `/` | https://dejband.com |
|
||||
| About Us | `/about` | https://www.dejband.com/aboutus |
|
||||
| Contact Us | `/contact` | https://www.dejband.com/contactus |
|
||||
|
||||
Blog / articles are **out of scope** — they will be wired to a backend later.
|
||||
`/#articles` and "مقالات" nav link are placeholders.
|
||||
|
||||
## 2. Prompt iteration log
|
||||
|
||||
| # | Date | Summary |
|
||||
| --- | --- | --- |
|
||||
| 1 | 2026-09-04 | Initial build: scaffolded Next.js project, downloaded assets, built shared Header/Footer + Home/About/Contact pages, RTL + Persian, palette from source, footer bottom-bar modeled on safeteb.com. |
|
||||
| 2 | 2026-09-04 | **Faithful homepage rebuild to match the original exactly.** Re-inspected every dejband.com homepage section. New/rewritten sections: Hero (2-col — blurred cover bg + framed foreground photo + centred heading + circled arrow, 6 slides), Services (black bg, 150px metallic PNG line icons, 3-col, no cards), Consulting (6 rows, fixed parallax strip), Stats (#0F0F0F, vertical counters 70/531/217/25), AboutBlock (blue lead + h5 + 2 ruled sub-columns), Timeline (vertical #083578 numbered nodes + centre line, alternating labels), Portfolio (4-col image grid, `rgba(8,53,120,.9)` hover overlay + fa/en captions), Articles (3-col blog grid). Downloaded: 6 service icon PNGs (`icons/svc-*.png`), 8 portfolio images (`portfolio/p-*.jpg`), 3 article thumbs (`articles/a-*.jpg`), hero foreground photos (`hero/fg-*.jpg`), `consult-parallax.jpg`. Added Montserrat (next/font/google) for Latin kickers. `next.config.mjs`: `images.unoptimized = true` (assets are pre-sized local copies). Removed `AboutPreview`, `ServiceIcon`. |
|
||||
|
||||
### Homepage section order (matches source DOM)
|
||||
`Hero → Services → Consulting(×6) → Stats → AboutBlock → Timeline → Portfolio → CtaBand → Articles`
|
||||
|
||||
| 3 | 2026-09-04 | **All header pages created + routes aligned to source URLs.** Renamed `/about`→`/aboutus`, `/contact`→`/contactus` (matching dejband.com). New pages: `/portfolios` (blue `#083578` `PageTitle` bar + breadcrumb + light-gray 3-col project card grid — 4 real projects with images from `meshkee-storage`: چسب و رنگ، نیروموتور، هتل استقلال، مجتمع زمزم → `public/images/projects/proj-*.jpg`) and `/articles` (blue title bar + 4-col blog grid, same 3 `ARTICLES`). Rebuilt `/aboutus` and `/contactus` using shared `PageTitle`. Nav items آموزش / مشاوره رایگان / مجوزها point to `/` (source does the same). New component `PageTitle.js`. Consulting section: removed bg/parallax images — now a subtle `#0e0e0e` half-panel (`.consult-row::before`), alternating with `.flip`. AboutBlock two columns moved to 50/50. |
|
||||
| 4 | 2026-09-04 | **`/aboutus` faithful rebuild (8 sections, dark theme)** mirroring source `/aboutus`: intro badge, هیئت مدیره (3 members `BOARD_MEMBERS` + `about/team/m-*.jpg`), ماموریت/چشمانداز interactive tabs (`MissionTabs.js`), طراحی صنعتی تخصص ماست, سازه صنعتی, رزومه و کاتالوگ (icons `about/ic-*.png`), blue CTA bar. 10 assets → `public/images/about/`. |
|
||||
| 5 | 2026-09-04 | Hero heading: smaller (`clamp(20px,2.2vw,30px)`) + tighter `line-height:1.45`. "مشاهده بیشتر" button switched to Persian font (`--font-fa`). |
|
||||
| 8 | 2026-09-04 | **Contact form wired to an API.** New route `app/api/contact/route.js` (POST) — validates `{name,phone,email,message}` (Persian-digit aware, `0?\d{10,11}` phone), returns `200 {ok,message,delivered}` or `422 {ok:false,errors:{...}}`. When `MESHKEE_SMS_API_KEY` + `MESHKEE_CONTACT_NOTIFY_TO` env vars are set it notifies the business via `POST /api/v1/public/sms/send` (`X-Api-Key`, `{domain,to,message}`); without them it still accepts (`delivered:false`). No public contact endpoint exists in the Meshkee docs — this route is the integration point. `ContactForm.js` rewritten: `fetch('/api/contact')`, loading state ("در حال ارسال…", disabled inputs), **success → toast (auto-dismiss 5s) + inline `.form-success-text` + `form.reset()`**, error → red toast + inline `.field-err` per field. New CSS: `.toast*`, `.field-err`, `.form-success-text`. |
|
||||
| 7 | 2026-09-04 | **SEO & sitemap phase.** Sitemap/robots are **proxied** through the Meshkee API, no static files: route handlers `app/{robots.txt,sitemap.xml,sitemap-main.xml,sitemap-products.xml,sitemap-blogs.xml}/route.js` → `GET https://api.meshkee.com/api/v1/tenants/dejband.com/<same>` via `proxyText()` in `lib/api.js` (each has an inline fallback; static routes exclude auth/checkout/cart/admin). **Canonical blog URL is `/blog/{id}/{titleSlug}`** (`app/blog/[id]/[slug]/page.js`, SSG from `getBlogById`, `slugify()` keeps Farsi & converts spaces→`-`); `app/articles/[slug]` is now a 307 redirect to the canonical path (`encodeURI` — Location must be ByteString); `/articles` listing links via `blogPath()`. **One H1 per page:** homepage has a single `.sr-only` `<h1>` + hero titles demoted to `<h2 class="hero-title">`; `PageTitle` gained `as` prop — detail pages pass `as="div"` so the post/project `<h1>` is the only one. **Per-page metadata:** unique `title`+`description`+`alternates.canonical` on `/`, `/aboutus`, `/contactus`, `/portfolios`, `/articles`, and both detail routes. **Alt text:** hero fg imgs → slide title, service icons → `آیکون {title}`, portfolio imgs → `{cat} — {en}`. `SITE_URL` const added. Build: 20 routes. |
|
||||
| 6 | 2026-09-04 | **Backend integration (Meshkee public API).** New `src/lib/api.js` — `API_BASE=https://api.meshkee.com/api/v1`, `TENANT_BASE=/tenants/dejband.com`, `CUSTOMER_URL=https://customer.dejband.com`. Endpoints used: `GET /tenants/dejband.com/website/favicon` (favicon w/ fallback to bundled `public/favicon.png`), `GET /tenants/dejband.com/blogs` + `/blogs/{slug}` (live blog content), `GET /tenants/dejband.com/portfolios` (returns empty → portfolio uses local `PROJECTS`). **Auth/nav:** `UserMenu.js` — icon → `customer.dejband.com` when signed out; when signed in (reads `localStorage.meshkee_auth` / JWT `meshkee_token`) shows "{name} عزیز" dropdown → پروفایل من / مدیریت وبسایت (admin only) / خروج. **Favicon:** `layout.js` now `generateMetadata()` → `icons` from backend. **Blog module:** `/articles` async-fetches list; `/articles/[slug]` renders `mainTextHtml`, author, cover, Persian date (`formatFaDate` via `Intl` persian calendar) + "مقالات مرتبط" row. **Portfolio module:** `/portfolios/[slug]` — hero w/ CSS hover-zoom (`.pf-zoom-frame`), meta, `Lightbox.js` gallery (keyboard nav, counter, hover-zoom thumbs), "پروژههای مرتبط" row. `PROJECTS` in `site.js` extended with `slug`/`category`/`location`/`year`/`description`/`gallery`. New CSS: user-menu, `.pf-detail*`, `.pf-gallery*`, `.lb-*` lightbox, `.post*` blog, `.related-section`. Build: 15 routes (3 blog + 4 portfolio SSG, `revalidate: 300`). |
|
||||
|
||||
## 3. Tech stack
|
||||
|
||||
- **Next.js 15** (App Router, JS — no TypeScript), React 19.
|
||||
- No CSS framework. One global stylesheet: `src/app/globals.css` (CSS custom properties
|
||||
for the palette; component styles co-located by section).
|
||||
- Fonts self-hosted via `next/font/local` — **Yekan Bakh** (primary, matches source
|
||||
`--wb-default-font: yekan-bakh`) + **IranYekan** (numerals / fallback). Files in
|
||||
`public/fonts/`, originals from `cdn.meshkee.com/web-builder/fonts/...`.
|
||||
- Icons: inline stroke SVGs in `src/components/Icons.js` (no icon-font dependency).
|
||||
- Images: local under `public/images/`, rendered with `next/image`.
|
||||
|
||||
## 4. Brand / design tokens (from source web-builder theme vars)
|
||||
|
||||
| Token | Value | Origin |
|
||||
| --- | --- | --- |
|
||||
| `--brand-primary` | `#2991d6` | `--wb-primary-color` |
|
||||
| `--brand-primary-dark` | `#1875bb` | `--color-blue` |
|
||||
| `--brand-navy` | `#04314b` | `--color-dark-blue` |
|
||||
| `--brand-cyan` | `#32c5d2` | `--color-invert` |
|
||||
| body background | `#101010` | source `body` bg |
|
||||
| body text (on dark) | `~#9aa6b2` | derived from source `#5c6873` |
|
||||
| default font | Yekan Bakh | `--wb-default-font` |
|
||||
|
||||
Overall look: **dark theme** (near-black sections), blue accent, generous vertical
|
||||
rhythm, uppercase Latin kicker above each Persian section title (mirrors source's
|
||||
"خدمات ما / OUR SERVICES" pattern).
|
||||
|
||||
## 5. Asset inventory (`public/`)
|
||||
|
||||
```
|
||||
images/logo.png # white wordmark, source: assets/files/A3IeR4aVLO.png
|
||||
images/meshkee-white.png # footer credit logo, source: safeteb.com/images/footer/MeshkeeLogo-White.png
|
||||
images/hero/slide-1..7.jpg # homepage slider backgrounds
|
||||
images/services/service-1..6.jpg # "مشاوره تخصصی" alternating rows
|
||||
images/about/about-1..3.jpg # about-preview + about page splits
|
||||
fonts/YekanBakh-Regular.ttf, YekanBakh-Light.ttf
|
||||
fonts/IranYekan-Regular/Medium/Light/Bold.ttf
|
||||
```
|
||||
|
||||
All content + media taken from the current live site — treated as valid for use per
|
||||
the project brief.
|
||||
|
||||
## 6. Component map
|
||||
|
||||
| File | Role |
|
||||
| --- | --- |
|
||||
| `src/app/layout.js` | `<html lang="fa" dir="rtl">`, font vars, metadata, Header + Footer shell |
|
||||
| `src/app/globals.css` | all styling |
|
||||
| `src/lib/site.js` | **single source of content data** — nav, contact info, hero slides, services, consulting rows, stats, timeline, socials |
|
||||
| `components/Header.js` | sticky dark header, logo, nav, mobile drawer, social + login (`client`) |
|
||||
| `components/Footer.js` | 4-column footer **+ bottom bar modeled on safeteb.com** (`.footer-bottom` → `.footer-bottom-bar` → `.footer-bottom-site` + `.meshkee-credit` animated frame) |
|
||||
| `components/Hero.js` | auto-rotating 7-slide hero, dots, "مشاهده بیشتر" round arrow button (`client`) |
|
||||
| `components/Services.js` | "خدمات ما / Our Services" icon grid |
|
||||
| `components/Consulting.js` | alternating image/text rows (6 home consulting blurbs) |
|
||||
| `components/Stats.js` | 4 animated counters, IntersectionObserver (`client`) |
|
||||
| `components/AboutPreview.js` | home "درباره ما" split section |
|
||||
| `components/Timeline.js` | 5-step company history |
|
||||
| `components/CtaBand.js` | reusable blue CTA strip |
|
||||
| `components/ContactForm.js` | contact form, client-side only (no backend yet) (`client`) |
|
||||
| `components/Icons.js`, `components/ServiceIcon.js` | inline SVG icon set + name→icon map |
|
||||
|
||||
## 7. Footer bottom-row spec (safeteb.com model)
|
||||
|
||||
Exact structure reproduced (class names + animated corner-draw frame on hover):
|
||||
|
||||
```
|
||||
.footer-bottom (bg #1d1d1d, padding 20px 0 19px)
|
||||
.container
|
||||
.footer-bottom-content
|
||||
.footer-bottom-bar (flex, space-between, wrap, gap 12px)
|
||||
.footer-bottom-site -> "مهندسین مشاور دژبند قم"
|
||||
a.meshkee-credit[href=meshkee.com, dir=ltr]
|
||||
.meshkee-credit__frame > 4x .meshkee-credit__line--(l|t|r|b)
|
||||
img.meshkee-credit__logo (meshkee-white.png, 42x42)
|
||||
.meshkee-credit__text
|
||||
.meshkee-credit__label -> "Designed And Developed By"
|
||||
.meshkee-credit__name -> "Meshkee E-Commerce Team"
|
||||
```
|
||||
|
||||
CSS for `.meshkee-credit*` copied verbatim from safeteb.com computed rules.
|
||||
|
||||
## 8. Known gaps / TODO (next iterations)
|
||||
|
||||
- Blog / articles listing + detail — pending backend.
|
||||
- "پروژه ها" (portfolios) page — not in current scope, nav points to `/#services`.
|
||||
- Contact form has **no submit endpoint** — shows a success state only.
|
||||
- Map is an OpenStreetMap embed with approximate Qom coordinates; swap for exact
|
||||
location / Google Maps when available.
|
||||
- Board-of-directors ("هیئت مدیره") photos not provided on source in a usable form —
|
||||
section summarized as text on `/about`.
|
||||
- Real favicon / OG image not yet added.
|
||||
|
||||
## 9. Run
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run dev # http://localhost:3000
|
||||
npm run build && npm start
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
// Assets are local, pre-sized copies of the source site's media — serve as-is.
|
||||
unoptimized: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,999 @@
|
||||
{
|
||||
"name": "dejband-clone",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dejband-clone",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"next": "^15.5.25",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-freebsd-wasm32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
|
||||
"integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
|
||||
"integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
|
||||
"integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
|
||||
"integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
|
||||
"integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
|
||||
"integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-riscv64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
|
||||
"integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
|
||||
"integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
|
||||
"integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
|
||||
"integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
|
||||
"integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
|
||||
"integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
|
||||
"integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-riscv64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
|
||||
"integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
|
||||
"integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
|
||||
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.11.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-webcontainers-wasm32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
|
||||
"integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/sharp-wasm32": "0.35.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
|
||||
"integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
|
||||
"integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
|
||||
"integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.25.tgz",
|
||||
"integrity": "sha512-42h1lLr07vl4gawALP1hsgRZjHB1xYa58JfUfHwr0f7jG/zhPakh5GHkADHXOC9ZxUvlQFOPIrp7s6qX4DezPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.25.tgz",
|
||||
"integrity": "sha512-w+RR0v/QuApnWEjRGm1z6gcObKwGMb5YPA7V3bzBEVSBpMFUXprer0tS27UxjUcEnqbhL7Zuzohej79B6rYmBg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.25.tgz",
|
||||
"integrity": "sha512-QiGGBUSakt8S1H4Lt9Ehsh6Ja87axiBnQQgysOObvCbI7iUfJnRGntF1P64S4/ijuHFnSB8KLsEddkY3nN26uw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.25.tgz",
|
||||
"integrity": "sha512-ehLos/66zo0d/mJCU5u96a/VDcr01aaUrX0o/i16UdInxz8qPTCDSxGtjk/Lps1sIr1RJFdiX3hxc0fxJo+cPA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.25.tgz",
|
||||
"integrity": "sha512-ZVMrqLiJ7DiChgmbkQwFtdhAnUkSH/4p7tB29QY+giATb0Q/XGHNRSKAb/B8XGDHRUaA67NepOW5W8u3ZRJBAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.25.tgz",
|
||||
"integrity": "sha512-UOewtDGkTMJTiODrEdeLZ50yGb59xCZSriNpXkfPMxRRgwDkGc7i8mLWqV5076wEdb+Ca/XN7MhJyM3CupyNyQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.25.tgz",
|
||||
"integrity": "sha512-UBHwA8AhkCZgtRfU1aJpunuAJe/6gZv6jDESQe4p5MjTb5V0YEeJBWCdNqx15Vj3x+5jmauRfeMJSjfQj9HGFQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.25.tgz",
|
||||
"integrity": "sha512-QcFcPRr16djk5IqK5+e8O80eZfgWzIvVBXfitIq0tQ/uc+eyfdoZ0NmKc0cnbIJyfVwREapKuG97YcxWA9gcpA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.25.tgz",
|
||||
"integrity": "sha512-zREeykps3ndWr9egJgvJKqVkkDuaw6Zrrg23cYBos0ygydFkAWYU4+PaPVwXzP1eAYQJe53ShSK45iDM529BOg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001810",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
|
||||
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.18",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.5.25",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.25.tgz",
|
||||
"integrity": "sha512-OMWNulIIqKM2ykvC2qMjIt0IoavB4UB2SCs4iXJ6z6847FvyH8jBmBWcvrF5iuhTu8Przh20Fo/aoszIdqx4PA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.5.25",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
"styled-jsx": "5.1.6"
|
||||
},
|
||||
"bin": {
|
||||
"next": "dist/bin/next"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.5.25",
|
||||
"@next/swc-darwin-x64": "15.5.25",
|
||||
"@next/swc-linux-arm64-gnu": "15.5.25",
|
||||
"@next/swc-linux-arm64-musl": "15.5.25",
|
||||
"@next/swc-linux-x64-gnu": "15.5.25",
|
||||
"@next/swc-linux-x64-musl": "15.5.25",
|
||||
"@next/swc-win32-arm64-msvc": "15.5.25",
|
||||
"@next/swc-win32-x64-msvc": "15.5.25",
|
||||
"sharp": "^0.34.3 || ^0.35.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"babel-plugin-react-compiler": "*",
|
||||
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"sass": "^1.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@playwright/test": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.35.4",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
|
||||
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@img/colour": "^1.1.0",
|
||||
"detect-libc": "^2.1.2",
|
||||
"semver": "^7.8.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.35.4",
|
||||
"@img/sharp-darwin-x64": "0.35.4",
|
||||
"@img/sharp-freebsd-wasm32": "0.35.4",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.3.3",
|
||||
"@img/sharp-libvips-darwin-x64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-arm": "1.3.3",
|
||||
"@img/sharp-libvips-linux-arm64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-riscv64": "1.3.3",
|
||||
"@img/sharp-libvips-linux-s390x": "1.3.3",
|
||||
"@img/sharp-libvips-linux-x64": "1.3.3",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.3.3",
|
||||
"@img/sharp-linux-arm": "0.35.4",
|
||||
"@img/sharp-linux-arm64": "0.35.4",
|
||||
"@img/sharp-linux-ppc64": "0.35.4",
|
||||
"@img/sharp-linux-riscv64": "0.35.4",
|
||||
"@img/sharp-linux-s390x": "0.35.4",
|
||||
"@img/sharp-linux-x64": "0.35.4",
|
||||
"@img/sharp-linuxmusl-arm64": "0.35.4",
|
||||
"@img/sharp-linuxmusl-x64": "0.35.4",
|
||||
"@img/sharp-webcontainers-wasm32": "0.35.4",
|
||||
"@img/sharp-win32-arm64": "0.35.4",
|
||||
"@img/sharp-win32-ia32": "0.35.4",
|
||||
"@img/sharp-win32-x64": "0.35.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"client-only": "0.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-macros": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "dejband-clone",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^15.5.25",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 225 B |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 226 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,141 @@
|
||||
import Image from "next/image";
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import MissionTabs from "@/components/MissionTabs";
|
||||
import { BOARD_MEMBERS } from "@/lib/site";
|
||||
import { SITE_URL } from "@/lib/api";
|
||||
|
||||
export const metadata = {
|
||||
title: "درباره ما و هیئت مدیره",
|
||||
description:
|
||||
"شرکت مهندسین مشاور دژبند قم از سال ۱۳۷۶؛ آشنایی با هیئت مدیره، ماموریت و چشمانداز شرکت — طراحی صنعتی تخصص ماست، با بیش از ۳۰۰ پروژه موفق در طراحی سوله و سازههای فلزی.",
|
||||
alternates: { canonical: `${SITE_URL}/aboutus` },
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="درباره ما" en="ABOUT US" />
|
||||
|
||||
{/* 1. Intro */}
|
||||
<section className="ab-intro">
|
||||
<div className="container">
|
||||
<div className="ab-split">
|
||||
<div className="ab-split-media">
|
||||
<Image src="/images/about/intro.png" alt="مهندسین مشاور دژبند" width={555} height={382} />
|
||||
</div>
|
||||
<div className="ab-split-body">
|
||||
<h3 className="ab-badge">ما دژبند هستیم؛ مهندسی که به ساخت، فکر میکنه!</h3>
|
||||
<p>
|
||||
شرکت مهندسین مشاور دژبند قم از سال ۱۳۷۶ با یه هدف ساده اما جدی شروع به کار کرد:
|
||||
<br />
|
||||
ساختن درست. اصولی. با دقت.
|
||||
<br />
|
||||
از اون روز تا الان، ما تو پروژههای صنعتی، اداری، مسکونی، طراحی نما و سولههای سنگین،
|
||||
کنار کارفرماها بودیم؛ نه فقط به عنوان طراح یا مشاور، بلکه به عنوان یه شریک فنی که پای
|
||||
کار وایمیسته.
|
||||
<br />
|
||||
تو این سالها یاد گرفتیم که پروژه خوب، فقط نقشه و اسکلت نیست؛ دقت، زمانبندی، تعامل و
|
||||
توجه به جزئیاته که فرق پروژه موفق و پروژه دردسرساز رو مشخص میکنه.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. Board of directors */}
|
||||
<section className="ab-board">
|
||||
<div className="container">
|
||||
<div className="text-center">
|
||||
<h4 className="ab-blue-head">هیئت مدیره</h4>
|
||||
<p className="ab-board-lead">با هیئت مدیره ما بیشتر آشنا شوید</p>
|
||||
</div>
|
||||
<div className="ab-board-grid">
|
||||
{BOARD_MEMBERS.map((m) => (
|
||||
<figure className="ab-member" key={m.first + m.last}>
|
||||
<div className="ab-member-photo">
|
||||
<Image src={m.img} alt={`${m.first} ${m.last}`} width={472} height={598} />
|
||||
</div>
|
||||
<figcaption>
|
||||
<span className="ab-member-first">{m.first}</span>
|
||||
<span className="ab-member-last">{m.last}</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. Mission / Vision tabs */}
|
||||
<section className="ab-mv">
|
||||
<div className="container">
|
||||
<MissionTabs />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 4. Industrial design is our specialty */}
|
||||
<section className="ab-specialty">
|
||||
<div className="container">
|
||||
<div className="ab-split ab-split-reverse">
|
||||
<div className="ab-split-media">
|
||||
<Image src="/images/about/specialty.jpg" alt="طراحی صنعتی تخصص ماست" width={600} height={500} />
|
||||
</div>
|
||||
<div className="ab-split-body">
|
||||
<h3 className="ab-blue-head lg">طراحی صنعتی تخصص ماست.</h3>
|
||||
<p className="ab-en">INDUSTRIAL DESIGN IS OUR SPECIALTY</p>
|
||||
<p>
|
||||
با افتخار و بیش از ۳۰۰ پروژه موفق در طراحی سوله و سازههای صنعتی در کنار شما هستیم تا
|
||||
بهترین خدمات را با بهرهگیری از دانش و تجربهی خود ارائه دهیم.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Industrial structure */}
|
||||
<section className="ab-structure">
|
||||
<div className="container">
|
||||
<div className="text-center mb-4">
|
||||
<h4 className="ab-blue-head">سازه صنعتی</h4>
|
||||
<span className="ab-muted">در این قسمت سازه صنعتی را مشاهده کنید.</span>
|
||||
</div>
|
||||
<div className="ab-structure-img">
|
||||
<Image src="/images/about/structure.jpg" alt="سازه صنعتی" width={700} height={500} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 6. Resume & catalog */}
|
||||
<section className="ab-downloads">
|
||||
<div className="container">
|
||||
<div className="text-center mb-4">
|
||||
<h4 className="ab-blue-head">رزومه و کاتالوگ</h4>
|
||||
<span className="ab-muted">رزومه و کاتالوگ ما را در این قسمت دانلود نمایید.</span>
|
||||
</div>
|
||||
<div className="ab-downloads-row">
|
||||
<a className="ab-download" href="/portfolios">
|
||||
<Image src="/images/about/ic-resume.png" alt="" width={64} height={64} />
|
||||
<span className="fa">رزومه</span>
|
||||
<span className="en">RESUME</span>
|
||||
</a>
|
||||
<a className="ab-download" href="/portfolios">
|
||||
<Image src="/images/about/ic-catalog.png" alt="" width={64} height={64} />
|
||||
<span className="fa">کاتالوگ</span>
|
||||
<span className="en">CATALOG</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 7. CTA bar */}
|
||||
<section className="ab-cta">
|
||||
<div className="container">
|
||||
<p>
|
||||
ما آمادهایم برای چالش پروژهی بعدی شما
|
||||
<br />
|
||||
اگه دنبال تیمی میگردید که دقیق، مسئولیتپذیر و همراه باشه، دژبند همینجاست
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { DOMAIN, API_BASE } from "@/lib/api";
|
||||
|
||||
// Contact form endpoint.
|
||||
// Validates the submission and — when an SMS key is configured — notifies the
|
||||
// business through the Meshkee SMS gateway. Without a key it still accepts the
|
||||
// message so the storefront keeps working; wire delivery by setting:
|
||||
// MESHKEE_SMS_API_KEY server-to-server key for /public/sms/send
|
||||
// MESHKEE_CONTACT_NOTIFY_TO phone number that should receive the alert
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const faDigits = (s) => String(s).replace(/[۰-۹]/g, (d) => "۰۱۲۳۴۵۶۷۸۹".indexOf(d));
|
||||
|
||||
function validate(body) {
|
||||
const name = String(body?.name || "").trim();
|
||||
const phone = faDigits(body?.phone || "").trim();
|
||||
const email = String(body?.email || "").trim();
|
||||
const message = String(body?.message || "").trim();
|
||||
|
||||
const errors = {};
|
||||
if (name.length < 2) errors.name = "نام را وارد کنید";
|
||||
if (!/^0?\d{10,11}$/.test(phone.replace(/[\s-]/g, "")))
|
||||
errors.phone = "شماره تماس معتبر نیست";
|
||||
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
|
||||
errors.email = "ایمیل معتبر نیست";
|
||||
if (message.length < 5) errors.message = "متن پیام را وارد کنید";
|
||||
|
||||
return { data: { name, phone, email, message }, errors };
|
||||
}
|
||||
|
||||
async function notifyBusiness({ name, phone, email, message }) {
|
||||
const key = process.env.MESHKEE_SMS_API_KEY;
|
||||
const to = process.env.MESHKEE_CONTACT_NOTIFY_TO;
|
||||
if (!key || !to) return { delivered: false, reason: "not-configured" };
|
||||
|
||||
const text = `پیام جدید از فرم تماس دژبند\nنام: ${name}\nتماس: ${phone}${
|
||||
email ? `\nایمیل: ${email}` : ""
|
||||
}\nپیام: ${message}`.slice(0, 600);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/public/sms/send`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Api-Key": key },
|
||||
body: JSON.stringify({ domain: DOMAIN, to, message: text }),
|
||||
});
|
||||
return { delivered: res.ok, reason: res.ok ? "sent" : `sms-${res.status}` };
|
||||
} catch {
|
||||
return { delivered: false, reason: "sms-error" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request) {
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return Response.json({ ok: false, message: "درخواست نامعتبر است" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { data, errors } = validate(body);
|
||||
if (Object.keys(errors).length) {
|
||||
return Response.json(
|
||||
{ ok: false, message: "لطفاً فیلدها را بررسی کنید", errors },
|
||||
{ status: 422 }
|
||||
);
|
||||
}
|
||||
|
||||
const notify = await notifyBusiness(data);
|
||||
|
||||
return Response.json({
|
||||
ok: true,
|
||||
message: "پیام شما با موفقیت ثبت شد. بهزودی همکاران ما با شما تماس میگیرند.",
|
||||
delivered: notify.delivered,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getBlog, blogPath } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
// Legacy path — 308-redirect to the canonical /blog/{id}/{slug}.
|
||||
export default async function LegacyArticleRedirect({ params }) {
|
||||
const { slug } = await params;
|
||||
const blog = await getBlog(slug);
|
||||
redirect(blog ? encodeURI(blogPath(blog)) : "/articles");
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import { getBlogs, formatFaDate, blogPath, SITE_URL } from "@/lib/api";
|
||||
import { ARTICLES as FALLBACK } from "@/lib/site";
|
||||
|
||||
export const metadata = {
|
||||
title: "مقالات و مطالب آموزشی",
|
||||
description:
|
||||
"مقالات تخصصی مهندسین مشاور دژبند قم درباره طراحی و ساخت سوله صنعتی، سازههای فلزی، نقشههای برق و مکانیک و استانداردهای ایمنی.",
|
||||
alternates: { canonical: `${SITE_URL}/articles` },
|
||||
};
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
export default async function ArticlesPage() {
|
||||
const blogs = await getBlogs();
|
||||
|
||||
const items = blogs.length
|
||||
? blogs.map((b) => ({
|
||||
href: blogPath(b),
|
||||
img: b.image || "/images/articles/a-1.jpg",
|
||||
category: b.category,
|
||||
date: formatFaDate(b.date),
|
||||
title: b.title,
|
||||
external: false,
|
||||
}))
|
||||
: FALLBACK.map((a) => ({ ...a, external: true }));
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="مقالات" en="ARTICLES" />
|
||||
|
||||
<section className="art-section" style={{ padding: "60px 0" }}>
|
||||
<div className="art-grid-wrap" style={{ marginTop: 0 }}>
|
||||
<ul className="art-grid art-grid-page">
|
||||
{items.map((a) => {
|
||||
const Card = (
|
||||
<>
|
||||
<div className="art-image">
|
||||
<Image src={a.img} alt={`تصویر مقاله: ${a.title}`} width={600} height={500} />
|
||||
<div className="art-image-overlay" />
|
||||
</div>
|
||||
<div className="art-details">
|
||||
<span className="art-category">{a.category}</span>
|
||||
<div className="art-bottom">
|
||||
<span className="art-date">{a.date}</span>
|
||||
<h2>{a.title}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<li className="art-item" key={a.title}>
|
||||
{a.external ? (
|
||||
<a href={a.href} target="_blank" rel="noopener noreferrer">
|
||||
{Card}
|
||||
</a>
|
||||
) : (
|
||||
<Link href={a.href}>{Card}</Link>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import { getBlogById, getBlogs, formatFaDate, slugify, blogPath, SITE_URL } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const blogs = await getBlogs();
|
||||
return blogs.map((b) => ({ id: String(b.id), slug: slugify(b.title) }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { id } = await params;
|
||||
const blog = await getBlogById(id);
|
||||
if (!blog) return { title: "مقاله" };
|
||||
return {
|
||||
title: blog.title,
|
||||
description: (blog.abstract || blog.title).replace(/\s+/g, " ").trim().slice(0, 160),
|
||||
alternates: { canonical: `${SITE_URL}${blogPath(blog)}` },
|
||||
openGraph: {
|
||||
title: blog.title,
|
||||
type: "article",
|
||||
images: blog.image ? [blog.image] : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function BlogDetailPage({ params }) {
|
||||
const { id, slug } = await params;
|
||||
const blog = await getBlogById(id);
|
||||
if (!blog) notFound();
|
||||
|
||||
// Keep the canonical slug in the URL (encode: Location must be a ByteString).
|
||||
const canonicalSlug = slugify(blog.title);
|
||||
if (decodeURIComponent(slug) !== canonicalSlug) {
|
||||
redirect(encodeURI(`/blog/${id}/${canonicalSlug}`));
|
||||
}
|
||||
|
||||
const all = await getBlogs();
|
||||
const related = all.filter((b) => String(b.id) !== String(id)).slice(0, 3);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="مقالات" en="ARTICLES" crumb={blog.title} as="div" />
|
||||
|
||||
<article className="post">
|
||||
<div className="container post-narrow">
|
||||
<div className="post-meta">
|
||||
<span className="art-category">{blog.category}</span>
|
||||
<span className="post-date">{formatFaDate(blog.date)}</span>
|
||||
{blog.author ? <span className="post-author">نویسنده: {blog.author}</span> : null}
|
||||
</div>
|
||||
<h1 className="post-title">{blog.title}</h1>
|
||||
|
||||
{blog.image ? (
|
||||
<div className="post-cover">
|
||||
<Image src={blog.image} alt={blog.title} width={1000} height={560} priority />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{blog.abstract ? <p className="post-abstract">{blog.abstract}</p> : null}
|
||||
|
||||
<div className="post-body" dangerouslySetInnerHTML={{ __html: blog.html }} />
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{related.length ? (
|
||||
<section className="related-section">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<h2>مقالات مرتبط</h2>
|
||||
<span className="en">RELATED ARTICLES</span>
|
||||
</div>
|
||||
<ul className="art-grid art-grid-page" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
|
||||
{related.map((r) => (
|
||||
<li className="art-item" key={r.id}>
|
||||
<Link href={blogPath(r)}>
|
||||
<div className="art-image">
|
||||
<Image
|
||||
src={r.image || "/images/articles/a-1.jpg"}
|
||||
alt={r.title}
|
||||
width={600}
|
||||
height={500}
|
||||
/>
|
||||
<div className="art-image-overlay" />
|
||||
</div>
|
||||
<div className="art-details">
|
||||
<span className="art-category">{r.category}</span>
|
||||
<div className="art-bottom">
|
||||
<span className="art-date">{formatFaDate(r.date)}</span>
|
||||
<h3>{r.title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import ContactForm from "@/components/ContactForm";
|
||||
import { CONTACT } from "@/lib/site";
|
||||
import { IconPhone, IconMail, IconMapPin, IconWhatsapp } from "@/components/Icons";
|
||||
import { SITE_URL } from "@/lib/api";
|
||||
|
||||
export const metadata = {
|
||||
title: "تماس با ما و مشاوره رایگان",
|
||||
description:
|
||||
"شماره تماس ۰۲۵-۳۷۷۰۰۲۲۲، ایمیل info@dejband.com و آدرس دفتر مهندسین مشاور دژبند قم؛ برای مشاوره، استعلام قیمت یا پرسش درباره خدمات با ما در ارتباط باشید.",
|
||||
alternates: { canonical: `${SITE_URL}/contactus` },
|
||||
};
|
||||
|
||||
const CARDS = [
|
||||
{ icon: IconPhone, title: "شماره تمــــــــاس", value: "025-37700222", href: "tel:02537700222" },
|
||||
{ icon: IconMail, title: "به ما ایمیل بزنید", value: "info@dejband.com", href: "mailto:info@dejband.com" },
|
||||
{
|
||||
icon: IconMapPin,
|
||||
title: "آدرس شرکت",
|
||||
value: "قم، 55 متری عماریاسر جنب هتل استقلال",
|
||||
},
|
||||
{
|
||||
icon: IconWhatsapp,
|
||||
title: "ارسال پیام در واتساپ",
|
||||
value: "09390333500",
|
||||
href: "https://wa.me/989390333500",
|
||||
},
|
||||
];
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="تماس با ما" en="CONTACT US" />
|
||||
|
||||
<section className="contact-intro">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<h4>سوالی دارید؟ ما اینجاییم تا کمک کنیم</h4>
|
||||
<span className="en">GET IN TOUCH</span>
|
||||
</div>
|
||||
<p style={{ textAlign: "center", maxWidth: 720, margin: "0 auto 45px" }}>
|
||||
برای مشاوره، استعلام قیمت، یا هرگونه پرسش درباره خدمات، تیم ما آماده پاسخگویی سریع و دقیق
|
||||
به شماست. میتونید از طریق تماس، ایمیل یا واتساپ با ما در ارتباط باشید.
|
||||
</p>
|
||||
|
||||
<div className="contact-cards">
|
||||
{CARDS.map((c) => {
|
||||
const Ic = c.icon;
|
||||
const body = (
|
||||
<>
|
||||
<div className="ic">
|
||||
<Ic width={22} height={22} />
|
||||
</div>
|
||||
<h3>{c.title}</h3>
|
||||
<p>{c.value}</p>
|
||||
</>
|
||||
);
|
||||
return c.href ? (
|
||||
<a className="contact-card" key={c.title} href={c.href} target="_blank" rel="noopener noreferrer">
|
||||
{body}
|
||||
</a>
|
||||
) : (
|
||||
<div className="contact-card" key={c.title}>
|
||||
{body}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="contact-form-section">
|
||||
<div className="container">
|
||||
<div className="contact-layout">
|
||||
<ContactForm />
|
||||
<iframe
|
||||
className="map-embed"
|
||||
title="نقشه محل شرکت مهندسین مشاور دژبند قم"
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
src="https://www.openstreetmap.org/export/embed.html?bbox=50.86%2C34.63%2C50.92%2C34.66&layer=mapnik&marker=34.645%2C50.889"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="cta-section">
|
||||
<div className="container text-center">
|
||||
<h5>
|
||||
<span>همراه شما هستیم، از اولین سوال تا آخرین مرحله اجرا </span>
|
||||
<a href={`tel:${CONTACT.mobile1}`}>{CONTACT.mobile1}</a>
|
||||
</h5>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import localFont from "next/font/local";
|
||||
import { Montserrat } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const montserrat = Montserrat({
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500"],
|
||||
variable: "--font-montserrat",
|
||||
display: "swap",
|
||||
});
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { getBranding } from "@/lib/api";
|
||||
|
||||
const yekanBakh = localFont({
|
||||
src: [
|
||||
{ path: "../../public/fonts/YekanBakh-Light.ttf", weight: "300", style: "normal" },
|
||||
{ path: "../../public/fonts/YekanBakh-Regular.ttf", weight: "400", style: "normal" },
|
||||
{ path: "../../public/fonts/YekanBakh-Regular.ttf", weight: "700", style: "normal" },
|
||||
],
|
||||
variable: "--font-yekan-bakh",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const iranYekan = localFont({
|
||||
src: [
|
||||
{ path: "../../public/fonts/IranYekan-Light.ttf", weight: "300", style: "normal" },
|
||||
{ path: "../../public/fonts/IranYekan-Regular.ttf", weight: "400", style: "normal" },
|
||||
{ path: "../../public/fonts/IranYekan-Medium.ttf", weight: "500", style: "normal" },
|
||||
{ path: "../../public/fonts/IranYekan-Bold.ttf", weight: "700", style: "normal" },
|
||||
],
|
||||
variable: "--font-iran-yekan",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export async function generateMetadata() {
|
||||
// Favicon comes from the backend; falls back to the bundled default.
|
||||
const { faviconUrl } = await getBranding();
|
||||
return {
|
||||
metadataBase: new URL("https://dejband.com"),
|
||||
title: {
|
||||
default: "صفحه اصلی | مهندسین مشاور دژبند قم",
|
||||
template: "%s | مهندسین مشاور دژبند قم",
|
||||
},
|
||||
description:
|
||||
"شرکت مهندسین مشاور دژبند قم (سهامی خاص) از پیشروان عرصه مشاوره در حوزه مهندسی کشور؛ طراحی سوله صنعتی، سازه فلزی، معماری و نقشههای برق و مکانیک.",
|
||||
keywords: [
|
||||
"دژبند",
|
||||
"مهندسین مشاور دژبند قم",
|
||||
"طراحی سوله صنعتی",
|
||||
"سازه فلزی",
|
||||
"طراحی نما",
|
||||
"طراحی ساختمان اداری",
|
||||
],
|
||||
icons: { icon: faviconUrl, shortcut: faviconUrl, apple: faviconUrl },
|
||||
openGraph: {
|
||||
title: "مهندسین مشاور دژبند قم",
|
||||
description: "طراحی اصولی، اجرای دقیق، راه حل مهندسی",
|
||||
locale: "fa_IR",
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html
|
||||
lang="fa"
|
||||
dir="rtl"
|
||||
className={`${yekanBakh.variable} ${iranYekan.variable} ${montserrat.variable}`}
|
||||
>
|
||||
<body>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import Hero from "@/components/Hero";
|
||||
import Services from "@/components/Services";
|
||||
import Consulting from "@/components/Consulting";
|
||||
import Stats from "@/components/Stats";
|
||||
import AboutBlock from "@/components/AboutBlock";
|
||||
import Timeline from "@/components/Timeline";
|
||||
import Portfolio from "@/components/Portfolio";
|
||||
import CtaBand from "@/components/CtaBand";
|
||||
import Articles from "@/components/Articles";
|
||||
import { SITE_URL } from "@/lib/api";
|
||||
|
||||
export const metadata = {
|
||||
title: "طراحی سوله صنعتی، سازه فلزی و ساختمان",
|
||||
description:
|
||||
"مهندسین مشاور دژبند قم؛ بیش از ۲۵ سال تجربه در طراحی و نظارت سوله صنعتی، سازههای فلزی سبک و سنگین، طراحی نما و نقشههای برق، مکانیک و اعلام و اطفاء حریق.",
|
||||
alternates: { canonical: SITE_URL + "/" },
|
||||
};
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="sr-only">مهندسین مشاور دژبند قم — طراحی سوله صنعتی، سازه فلزی و ساختمان</h1>
|
||||
<Hero />
|
||||
<Services />
|
||||
<Consulting />
|
||||
<Stats />
|
||||
<AboutBlock />
|
||||
<Timeline />
|
||||
<Portfolio />
|
||||
<CtaBand />
|
||||
<Articles />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import Lightbox from "@/components/Lightbox";
|
||||
import { PROJECTS } from "@/lib/site";
|
||||
import { SITE_URL } from "@/lib/api";
|
||||
|
||||
export function generateStaticParams() {
|
||||
return PROJECTS.map((p) => ({ slug: p.slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { slug } = await params;
|
||||
const project = PROJECTS.find((p) => p.slug === slug);
|
||||
if (!project) return { title: "پروژه" };
|
||||
return {
|
||||
title: `${project.title} — ${project.category}`,
|
||||
description: project.description.replace(/\s+/g, " ").trim().slice(0, 160),
|
||||
alternates: { canonical: `${SITE_URL}/portfolios/${project.slug}` },
|
||||
openGraph: { title: project.title, type: "article", images: [project.img] },
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProjectDetailPage({ params }) {
|
||||
const { slug } = await params;
|
||||
const project = PROJECTS.find((p) => p.slug === slug);
|
||||
if (!project) notFound();
|
||||
|
||||
const related = PROJECTS.filter((p) => p.slug !== slug).slice(0, 3);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="پروژه ها" en="PROJECTS" crumb={project.title} as="div" />
|
||||
|
||||
<section className="pf-detail">
|
||||
<div className="container">
|
||||
<div className="pf-detail-head">
|
||||
<div className="pf-detail-cover">
|
||||
{/* hover-zoom on the main image */}
|
||||
<div className="pf-zoom-frame">
|
||||
<Image src={project.img} alt={project.title} width={800} height={600} priority />
|
||||
</div>
|
||||
</div>
|
||||
<div className="pf-detail-info">
|
||||
<h1>{project.title}</h1>
|
||||
<ul className="pf-meta">
|
||||
<li>
|
||||
<span>دستهبندی</span>
|
||||
<strong>{project.category}</strong>
|
||||
</li>
|
||||
<li>
|
||||
<span>موقعیت</span>
|
||||
<strong>{project.location}</strong>
|
||||
</li>
|
||||
<li>
|
||||
<span>سال</span>
|
||||
<strong>{project.year}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
<p>{project.description}</p>
|
||||
<Link className="btn btn-primary" href="/contactus">
|
||||
مشاوره برای پروژه مشابه
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sec-head" style={{ marginTop: 60 }}>
|
||||
<h4>گالری تصاویر</h4>
|
||||
<span className="en">GALLERY</span>
|
||||
</div>
|
||||
<Lightbox images={project.gallery} alt={project.title} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="related-section">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<h4>پروژههای مرتبط</h4>
|
||||
<span className="en">RELATED PROJECTS</span>
|
||||
</div>
|
||||
<ul className="projects-grid">
|
||||
{related.map((p) => (
|
||||
<li className="project-box" key={p.slug}>
|
||||
<Link href={`/portfolios/${p.slug}`}>
|
||||
<div className="project-image">
|
||||
<Image src={p.img} alt={p.title} width={600} height={600} />
|
||||
<div className="project-hover">
|
||||
<span className="project-zoom" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="M21 21l-4.3-4.3" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="project-title">{p.title}</h3>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PageTitle from "@/components/PageTitle";
|
||||
import { PROJECTS } from "@/lib/site";
|
||||
import { SITE_URL } from "@/lib/api";
|
||||
|
||||
export const metadata = {
|
||||
title: "نمونه پروژههای اجرا و نظارت شده",
|
||||
description:
|
||||
"نمونه پروژههای طراحی و نظارت مهندسین مشاور دژبند قم — کارخانه چسب و رنگ، کارخانه نیروموتور، هتل استقلال قم و مجتمع تجاری زمزم؛ سوله صنعتی، سازه فلزی و ساختمان.",
|
||||
alternates: { canonical: `${SITE_URL}/portfolios` },
|
||||
};
|
||||
|
||||
export default function PortfoliosPage() {
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="پروژه ها" en="PROJECTS" />
|
||||
|
||||
<section className="projects-section">
|
||||
<div className="container">
|
||||
<ul className="projects-grid">
|
||||
{PROJECTS.map((p) => (
|
||||
<li className="project-box" key={p.slug}>
|
||||
<Link href={`/portfolios/${p.slug}`}>
|
||||
<div className="project-image">
|
||||
<Image src={p.img} alt={p.title} width={600} height={600} />
|
||||
<div className="project-hover">
|
||||
<span className="project-zoom" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="M21 21l-4.3-4.3" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="project-title">{p.title}</h3>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { proxyText } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
export async function GET() {
|
||||
const { body, status } = await proxyText("robots.txt", "text/plain; charset=utf-8");
|
||||
const fallback = `User-agent: *
|
||||
Allow: /
|
||||
Disallow: /api/
|
||||
Disallow: /admin
|
||||
|
||||
Sitemap: https://dejband.com/sitemap.xml
|
||||
`;
|
||||
return new Response(status === 200 && body ? body : fallback, {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/plain; charset=utf-8" },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { proxyText, getBlogs, slugify, SITE_URL } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
const XML = "application/xml; charset=utf-8";
|
||||
|
||||
export async function GET() {
|
||||
const { body, status } = await proxyText("sitemap-blogs.xml", XML);
|
||||
if (status === 200 && body) {
|
||||
return new Response(body, { status: 200, headers: { "content-type": XML } });
|
||||
}
|
||||
// Fallback: build from the blogs API, canonical /blog/{id}/{titleSlug}
|
||||
const blogs = await getBlogs();
|
||||
const urls = blogs
|
||||
.map((b) => {
|
||||
const loc = `${SITE_URL}/blog/${b.id}/${slugify(b.title)}`;
|
||||
const lastmod = b.date ? `<lastmod>${b.date}</lastmod>` : "";
|
||||
return ` <url><loc>${loc}</loc>${lastmod}<changefreq>weekly</changefreq><priority>0.7</priority></url>`;
|
||||
})
|
||||
.join("\n");
|
||||
const fallback = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls}
|
||||
</urlset>`;
|
||||
return new Response(fallback, { status: 200, headers: { "content-type": XML } });
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { proxyText } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
const XML = "application/xml; charset=utf-8";
|
||||
|
||||
// Static routes to advertise when the API sitemap is unavailable.
|
||||
// Auth / checkout / cart / admin are deliberately excluded.
|
||||
const STATIC_ROUTES = ["/", "/aboutus", "/contactus", "/portfolios", "/articles"];
|
||||
|
||||
export async function GET() {
|
||||
const { body, status } = await proxyText("sitemap-main.xml", XML);
|
||||
if (status === 200 && body) {
|
||||
return new Response(body, { status: 200, headers: { "content-type": XML } });
|
||||
}
|
||||
const urls = STATIC_ROUTES.map(
|
||||
(r) =>
|
||||
` <url><loc>https://dejband.com${r}</loc><changefreq>weekly</changefreq><priority>${
|
||||
r === "/" ? "1.0" : "0.7"
|
||||
}</priority></url>`
|
||||
).join("\n");
|
||||
const fallback = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls}
|
||||
</urlset>`;
|
||||
return new Response(fallback, { status: 200, headers: { "content-type": XML } });
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { proxyText } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
const XML = "application/xml; charset=utf-8";
|
||||
|
||||
export async function GET() {
|
||||
const { body, status } = await proxyText("sitemap-products.xml", XML);
|
||||
const fallback = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
</urlset>`;
|
||||
return new Response(status === 200 && body ? body : fallback, {
|
||||
status: 200,
|
||||
headers: { "content-type": XML },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { proxyText } from "@/lib/api";
|
||||
|
||||
export const revalidate = 300;
|
||||
|
||||
const XML = "application/xml; charset=utf-8";
|
||||
|
||||
export async function GET() {
|
||||
const { body, status } = await proxyText("sitemap.xml", XML);
|
||||
const fallback = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<sitemap><loc>https://dejband.com/sitemap-main.xml</loc></sitemap>
|
||||
<sitemap><loc>https://dejband.com/sitemap-products.xml</loc></sitemap>
|
||||
<sitemap><loc>https://dejband.com/sitemap-blogs.xml</loc></sitemap>
|
||||
</sitemapindex>`;
|
||||
return new Response(status === 200 && body ? body : fallback, {
|
||||
status: 200,
|
||||
headers: { "content-type": XML },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
export default function AboutBlock() {
|
||||
return (
|
||||
<section className="about-block">
|
||||
<div className="container">
|
||||
<div className="about-block-top">
|
||||
<div className="about-lead">
|
||||
هر پروژه یک مسیر است,ما از اولین گام تا آخرین جزئیات کنارتان هستیم.
|
||||
</div>
|
||||
<div className="about-main">
|
||||
<h5>
|
||||
ما در دژبند، فقط نقشه نمیکشیم و سازه طراحی نمیکنیم؛
|
||||
<br />
|
||||
همراه شماییم تا رویاهای صنعتیتان، شکل بگیرد، ساخته شود و به بهرهبرداری برسد.
|
||||
<br />
|
||||
از مشاوره اولیه و طراحی فنی گرفته تا اجرای دقیق و نظارت کامل،
|
||||
<br />
|
||||
تجربه ۲۵ سالهمان را گذاشتهایم تا خیال شما راحت باشد.
|
||||
<br />
|
||||
اینجا شروع مسیر ساختوساز اصولی است : بیایید با هم شروع کنیم.
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="about-cols">
|
||||
<div className="about-col">
|
||||
<span className="rule" />
|
||||
<span className="about-col-title">درباره ما</span>
|
||||
<p>
|
||||
مهندسین مشاور دژبند قم با بیش از 4 دهه تجربه در طراحی و اجرای پروژههای صنعتی، ساختمانی و
|
||||
سازههای فلزی، بهعنوان یکی از شرکتهای پیشرو در این حوزه شناخته میشود.
|
||||
<br />
|
||||
ما با تکیه بر دانش مهندسی بهروز، دقت بالا در طراحی، و توجه کامل به جزئیات، راهکارهایی ارائه
|
||||
میدهیم که ترکیبی از کیفیت، ایمنی و بهرهوری هستند.
|
||||
<br />
|
||||
تخصص ما در زمینه طراحی سولههای صنعتی، سازههای فلزی سبک و سنگین، طراحی معماری، و نقشههای
|
||||
برق و مکانیک، تضمینکننده اجرای پروژههایی بینقص و مطابق با نیاز کارفرماست.
|
||||
<br />
|
||||
در دژبند، هر پروژه با رویکردی مهندسی، دقیق و آیندهنگرانه طراحی میشود؛ چون باور داریم طراحی
|
||||
خوب، پایهای برای ساخت موفق است.
|
||||
</p>
|
||||
</div>
|
||||
<div className="about-col">
|
||||
<span className="rule" />
|
||||
<span className="about-col-title">دژبند ,طراحی اصولی,اجرای دقیق,راه حل مهندسی</span>
|
||||
<p>
|
||||
در مهندسین مشاور دژبند قم، سابقهمان تنها به تعداد سالها محدود نمیشود؛ بلکه در کیفیت
|
||||
طراحیها، دقت اجرایی، و همراهی ما با کارفرماها نمایان است.
|
||||
<br />
|
||||
با تکیه بر دانش مهندسی روز، ما همواره تلاش کردهایم طراحیهایی اصولی، فنی و قابل اجرا ارائه
|
||||
دهیم—طراحیهایی که نهتنها زیبا و مهندسیشدهاند، بلکه در عمل هم جواب میدهند.
|
||||
<br />
|
||||
سرعت در فرآیند طراحی، مشاوره دقیق برای انتخاب بهترین راهکار ساخت، و همکاری مستمر در مراحل
|
||||
نظارت و اجرا، بخشی از تعهد ماست برای ساخت بنایی درست، ایمن و ماندگار.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ARTICLES } from "@/lib/site";
|
||||
|
||||
export default function Articles() {
|
||||
return (
|
||||
<section id="articles" className="art-section">
|
||||
<div className="container art-head">
|
||||
<span className="art-kicker">آخرین مقالات</span>
|
||||
<Link className="art-more" href="/#articles">
|
||||
مشاهده بیشتر
|
||||
</Link>
|
||||
</div>
|
||||
<div className="art-grid-wrap">
|
||||
<ul className="art-grid">
|
||||
{ARTICLES.map((a) => (
|
||||
<li className="art-item" key={a.title}>
|
||||
<a href={a.href} target="_blank" rel="noopener noreferrer">
|
||||
<div className="art-image">
|
||||
<Image src={a.img} alt={a.title} width={600} height={500} />
|
||||
<div className="art-image-overlay" />
|
||||
</div>
|
||||
<div className="art-details">
|
||||
<span className="art-category">{a.category}</span>
|
||||
<div className="art-bottom">
|
||||
<span className="art-date">{a.date}</span>
|
||||
<h6>{a.title}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import Image from "next/image";
|
||||
import { CONSULTING } from "@/lib/site";
|
||||
|
||||
export default function Consulting() {
|
||||
return (
|
||||
<section className="consult-section">
|
||||
<div className="container">
|
||||
{CONSULTING.map((c, idx) => (
|
||||
<div
|
||||
className={`consult-row ${idx % 2 === 1 ? "flip" : ""}`}
|
||||
key={c.title}
|
||||
>
|
||||
<div className="consult-media">
|
||||
<Image src={c.img} alt={c.title} width={799} height={512} />
|
||||
</div>
|
||||
<div className="consult-body">
|
||||
<h3>{c.title}</h3>
|
||||
<p className="en">{c.en}</p>
|
||||
<p className="txt">{c.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { IconSend } from "@/components/Icons";
|
||||
|
||||
export default function ContactForm() {
|
||||
const formRef = useRef(null);
|
||||
const [status, setStatus] = useState("idle"); // idle | loading | success | error
|
||||
const [toast, setToast] = useState(null); // { type, text }
|
||||
const [fieldErrors, setFieldErrors] = useState({});
|
||||
const timerRef = useRef(null);
|
||||
|
||||
useEffect(() => () => clearTimeout(timerRef.current), []);
|
||||
|
||||
function flash(type, text) {
|
||||
setToast({ type, text });
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => setToast(null), 5000);
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (status === "loading") return;
|
||||
|
||||
const fd = new FormData(e.currentTarget);
|
||||
const payload = {
|
||||
name: fd.get("name"),
|
||||
phone: fd.get("phone"),
|
||||
email: fd.get("email"),
|
||||
message: fd.get("message"),
|
||||
};
|
||||
|
||||
setStatus("loading");
|
||||
setFieldErrors({});
|
||||
try {
|
||||
const res = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
if (res.ok && data.ok) {
|
||||
formRef.current?.reset();
|
||||
setStatus("success");
|
||||
flash("success", data.message || "پیام شما با موفقیت ارسال شد.");
|
||||
} else {
|
||||
setFieldErrors(data.errors || {});
|
||||
setStatus("error");
|
||||
flash("error", data.message || "ارسال پیام ناموفق بود. دوباره تلاش کنید.");
|
||||
}
|
||||
} catch {
|
||||
setStatus("error");
|
||||
flash("error", "ارتباط با سرور برقرار نشد. اتصال اینترنت را بررسی کنید.");
|
||||
}
|
||||
}
|
||||
|
||||
const loading = status === "loading";
|
||||
|
||||
return (
|
||||
<div className="contact-form-wrap">
|
||||
{toast && (
|
||||
<div className={`toast toast-${toast.type}`} role="status" aria-live="polite">
|
||||
<span className="toast-icon" aria-hidden="true">
|
||||
{toast.type === "success" ? "✓" : "!"}
|
||||
</span>
|
||||
<span>{toast.text}</span>
|
||||
<button className="toast-close" onClick={() => setToast(null)} aria-label="بستن">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form ref={formRef} onSubmit={handleSubmit} noValidate>
|
||||
<h3 style={{ marginBottom: 6 }}>فرم را پر کنید و بهزودی با شما تماس خواهیم گرفت</h3>
|
||||
<p className="form-note">ما چگونه میتوانیم به شما کمک کنیم؟</p>
|
||||
|
||||
<div className="form-field">
|
||||
<label htmlFor="name">نام و نام خانوادگی</label>
|
||||
<input id="name" name="name" type="text" required disabled={loading} />
|
||||
{fieldErrors.name && <span className="field-err">{fieldErrors.name}</span>}
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<label htmlFor="phone">شماره تماس</label>
|
||||
<input id="phone" name="phone" type="tel" inputMode="tel" required disabled={loading} />
|
||||
{fieldErrors.phone && <span className="field-err">{fieldErrors.phone}</span>}
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<label htmlFor="email">ایمیل</label>
|
||||
<input id="email" name="email" type="email" disabled={loading} />
|
||||
{fieldErrors.email && <span className="field-err">{fieldErrors.email}</span>}
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<label htmlFor="message">پیام شما</label>
|
||||
<textarea id="message" name="message" rows={5} required disabled={loading} />
|
||||
{fieldErrors.message && <span className="field-err">{fieldErrors.message}</span>}
|
||||
</div>
|
||||
|
||||
<p className="form-note">
|
||||
ما متعهد به حفظ حریم شما هستیم و هرگز بدون رضایت صریح شما اطلاعاتی جمعآوری نمیکنیم.
|
||||
</p>
|
||||
|
||||
{status === "success" && (
|
||||
<p className="form-success-text">
|
||||
✓ پیام شما با موفقیت ثبت شد. از تماس شما سپاسگزاریم؛ همکاران ما در اسرع وقت پاسخگوی شما
|
||||
خواهند بود.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button className="btn btn-primary" type="submit" disabled={loading}>
|
||||
<IconSend width={17} height={17} />
|
||||
{loading ? "در حال ارسال…" : "ارسال پیام"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function CtaBand({
|
||||
text = "برای همکاری با ما تماس بگیرید.",
|
||||
linkLabel = "تماس با ما",
|
||||
href = "/contactus",
|
||||
}) {
|
||||
return (
|
||||
<section className="cta-section">
|
||||
<div className="container text-center">
|
||||
<h5>
|
||||
<span>{text} </span>
|
||||
<Link href={href}>{linkLabel}</Link>
|
||||
</h5>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { CONTACT, SOCIAL } from "@/lib/site";
|
||||
import { IconMail, IconTelegram, IconSend } from "@/components/Icons";
|
||||
|
||||
const socialIcon = { mail: IconMail, telegram: IconTelegram, send: IconSend };
|
||||
|
||||
const QUICK_LINKS = [
|
||||
{ label: "پروژه ها", href: "/portfolios" },
|
||||
{ label: "رتبههای فعلی", href: "/aboutus" },
|
||||
{ label: "ISOها", href: "/aboutus" },
|
||||
{ label: "تقدیرنامهها", href: "/aboutus" },
|
||||
];
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="site-footer">
|
||||
<div className="footer-top">
|
||||
<div className="container">
|
||||
<div className="footer-grid">
|
||||
<div className="footer-col">
|
||||
<Link href="/">
|
||||
<Image
|
||||
className="footer-logo"
|
||||
src="/images/logo.png"
|
||||
alt="مهندسین مشاور دژبند قم"
|
||||
width={230}
|
||||
height={60}
|
||||
/>
|
||||
</Link>
|
||||
<p>
|
||||
شرکت مهندسین مشاور دژبند قم (سهامی خاص) از پیشروان عرصه مشاوره در حوزه مهندسی کشور
|
||||
میباشد.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="footer-col">
|
||||
<h4>دسترسی سریع</h4>
|
||||
<ul>
|
||||
{QUICK_LINKS.map((l) => (
|
||||
<li key={l.label}>
|
||||
<Link href={l.href}>{l.label}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="footer-col">
|
||||
<h4>آدرس</h4>
|
||||
<p className="footer-addr">
|
||||
{CONTACT.address}
|
||||
<br />
|
||||
{CONTACT.mobile1}
|
||||
<br />
|
||||
{CONTACT.mobile2}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="footer-col">
|
||||
<h4>با ما همراه باشید</h4>
|
||||
<div className="footer-social">
|
||||
{SOCIAL.map((s) => {
|
||||
const Ic = socialIcon[s.icon] || IconSend;
|
||||
return (
|
||||
<a
|
||||
key={s.label}
|
||||
href={s.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={s.label}
|
||||
>
|
||||
<Ic width={16} height={16} />
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom bar — pattern mirrored from safeteb.com */}
|
||||
<div className="footer-bottom">
|
||||
<div className="container">
|
||||
<div className="footer-bottom-content">
|
||||
<div className="footer-bottom-bar">
|
||||
<div className="footer-bottom-site">مهندسین مشاور دژبند قم</div>
|
||||
<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>
|
||||
<img
|
||||
src="/images/meshkee-white.png"
|
||||
alt="Meshkee"
|
||||
className="meshkee-credit__logo"
|
||||
width={42}
|
||||
height={42}
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { NAV_LINKS, SOCIAL } from "@/lib/site";
|
||||
import { IconMail, IconTelegram, IconSend } from "@/components/Icons";
|
||||
import UserMenu from "@/components/UserMenu";
|
||||
|
||||
const socialIcon = { mail: IconMail, telegram: IconTelegram, send: IconSend };
|
||||
|
||||
export default function Header() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="site-header">
|
||||
<div className="container bar">
|
||||
<button
|
||||
className="nav-toggle"
|
||||
aria-label="منو"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</button>
|
||||
|
||||
<Link href="/" className="logo" aria-label="مهندسین مشاور دژبند قم">
|
||||
<Image src="/images/logo.png" alt="مهندسین مشاور دژبند قم" width={220} height={58} priority />
|
||||
</Link>
|
||||
|
||||
<nav className={`main-nav ${open ? "open" : ""}`}>
|
||||
<ul onClick={() => setOpen(false)}>
|
||||
{NAV_LINKS.map((l) => (
|
||||
<li key={l.label}>
|
||||
<Link href={l.href}>{l.label}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="header-actions">
|
||||
<div className="header-social">
|
||||
{SOCIAL.map((s) => {
|
||||
const Ic = socialIcon[s.icon] || IconSend;
|
||||
return (
|
||||
<a key={s.label} href={s.href} target="_blank" rel="noopener noreferrer" title={s.label}>
|
||||
<Ic width={15} height={15} />
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<UserMenu />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import { HERO_SLIDES } from "@/lib/site";
|
||||
|
||||
export default function Hero() {
|
||||
const [i, setI] = useState(0);
|
||||
const count = HERO_SLIDES.length;
|
||||
const go = useCallback((n) => setI(((n % count) + count) % count), [count]);
|
||||
|
||||
useEffect(() => {
|
||||
const t = setInterval(() => setI((v) => (v + 1) % count), 6000);
|
||||
return () => clearInterval(t);
|
||||
}, [count]);
|
||||
|
||||
return (
|
||||
<section className="hero" aria-label="اسلایدر معرفی خدمات">
|
||||
{HERO_SLIDES.map((s, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`hero-slide ${idx === i ? "active" : ""}`}
|
||||
aria-hidden={idx !== i}
|
||||
>
|
||||
<div
|
||||
className="hero-slide-bg"
|
||||
style={{ backgroundImage: `url(${s.bg})` }}
|
||||
/>
|
||||
<div className="hero-overlay" />
|
||||
<div className="container hero-row">
|
||||
<div className="hero-media">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={s.fg} alt={s.title} />
|
||||
</div>
|
||||
<div className="hero-copy">
|
||||
<h2 className="hero-title">{s.title}</h2>
|
||||
<Link href="/#services" className="hero-more">
|
||||
<span className="hero-more-text">مشاهده بیشتر</span>
|
||||
<span className="hero-more-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="hero-dots">
|
||||
{HERO_SLIDES.map((_, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
className={idx === i ? "active" : ""}
|
||||
onClick={() => go(idx)}
|
||||
aria-label={`اسلاید ${idx + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Minimal inline SVG icon set (stroke-based, currentColor)
|
||||
|
||||
const base = {
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "none",
|
||||
stroke: "currentColor",
|
||||
strokeWidth: 1.8,
|
||||
strokeLinecap: "round",
|
||||
strokeLinejoin: "round",
|
||||
};
|
||||
|
||||
export const IconMail = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<rect x="3" y="5" width="18" height="14" rx="2" />
|
||||
<path d="m3 7 9 6 9-6" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconPhone = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3-8.7A2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1.2.4 2.4.7 3.5a2 2 0 0 1-.5 2.1L8.1 10.5a16 16 0 0 0 6 6l1.2-1.2a2 2 0 0 1 2.1-.5c1.1.4 2.3.6 3.5.7a2 2 0 0 1 1.7 2Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconSend = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="m22 2-7 20-4-9-9-4Z" />
|
||||
<path d="M22 2 11 13" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconTelegram = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M21 4 3 11l6 2 2 6 3-4 5 4Z" />
|
||||
<path d="m9 13 9-7" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconLogin = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||
<path d="m10 17 5-5-5-5" />
|
||||
<path d="M15 12H3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconArrow = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M19 12H5" />
|
||||
<path d="m12 19-7-7 7-7" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconMapPin = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
|
||||
<circle cx="12" cy="10" r="3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IconWhatsapp = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M3 21l1.7-5A9 9 0 1 1 12 21a9 9 0 0 1-4.3-1.1L3 21Z" />
|
||||
<path d="M8.5 9c0 4 3 6.5 6.5 6.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// service icons
|
||||
export const IconBuilding = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<rect x="4" y="3" width="16" height="18" rx="1.5" />
|
||||
<path d="M9 8h.01M15 8h.01M9 12h.01M15 12h.01M10 21v-3h4v3" />
|
||||
</svg>
|
||||
);
|
||||
export const IconFactory = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M3 21V9l6 4V9l6 4V9l6 4v8Z" />
|
||||
<path d="M3 21h18M8 17h.01M13 17h.01M18 17h.01" />
|
||||
</svg>
|
||||
);
|
||||
export const IconShed = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M2 12 12 4l10 8" />
|
||||
<path d="M4 11v9h16v-9" />
|
||||
<path d="M9 20v-5h6v5" />
|
||||
</svg>
|
||||
);
|
||||
export const IconFacade = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<rect x="5" y="2" width="14" height="20" rx="1" />
|
||||
<path d="M9 6h6M9 10h6M9 14h6M9 18h6" />
|
||||
</svg>
|
||||
);
|
||||
export const IconBolt = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M13 2 4 14h7l-1 8 9-12h-7Z" />
|
||||
</svg>
|
||||
);
|
||||
export const IconFire = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<path d="M12 2c1 4 5 5 5 9a5 5 0 0 1-10 0c0-2 1-3 2-4 0 2 1 3 2 3 .5 0 1-.5 1-1 0-2-3-3-3-6Z" />
|
||||
</svg>
|
||||
);
|
||||
export const IconCompass = (p) => (
|
||||
<svg {...base} {...p}>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="m15 9-2 5-4 1 2-5Z" />
|
||||
</svg>
|
||||
);
|
||||
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Lightbox({ images = [], alt = "" }) {
|
||||
const [index, setIndex] = useState(-1);
|
||||
const open = index >= 0;
|
||||
|
||||
const close = useCallback(() => setIndex(-1), []);
|
||||
const prev = useCallback(
|
||||
() => setIndex((i) => (i - 1 + images.length) % images.length),
|
||||
[images.length]
|
||||
);
|
||||
const next = useCallback(
|
||||
() => setIndex((i) => (i + 1) % images.length),
|
||||
[images.length]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (e) => {
|
||||
if (e.key === "Escape") close();
|
||||
if (e.key === "ArrowLeft") next();
|
||||
if (e.key === "ArrowRight") prev();
|
||||
};
|
||||
document.addEventListener("keydown", onKey);
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKey);
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [open, close, next, prev]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul className="pf-gallery">
|
||||
{images.map((src, i) => (
|
||||
<li key={src + i}>
|
||||
<button
|
||||
type="button"
|
||||
className="pf-gallery-item"
|
||||
onClick={() => setIndex(i)}
|
||||
aria-label={`باز کردن تصویر ${i + 1}`}
|
||||
>
|
||||
<Image src={src} alt={`${alt} ${i + 1}`} width={600} height={450} />
|
||||
<span className="pf-gallery-zoom" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<path d="M21 21l-4.3-4.3M11 8v6M8 11h6" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{open && (
|
||||
<div className="lb-overlay" onClick={close} role="dialog" aria-modal="true">
|
||||
<button className="lb-close" onClick={close} aria-label="بستن">
|
||||
×
|
||||
</button>
|
||||
<button
|
||||
className="lb-nav lb-prev"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
prev();
|
||||
}}
|
||||
aria-label="قبلی"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<div className="lb-stage" onClick={(e) => e.stopPropagation()}>
|
||||
<Image
|
||||
src={images[index]}
|
||||
alt={`${alt} ${index + 1}`}
|
||||
width={1400}
|
||||
height={950}
|
||||
className="lb-image"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="lb-nav lb-next"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
next();
|
||||
}}
|
||||
aria-label="بعدی"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
<div className="lb-counter">
|
||||
{(index + 1).toLocaleString("fa-IR")} / {images.length.toLocaleString("fa-IR")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
const TABS = [
|
||||
{
|
||||
key: "mission",
|
||||
label: "ماموریت های شرکت",
|
||||
img: "/images/about/mission.jpg",
|
||||
heading: "نگاه ما به آینده ساختوساز",
|
||||
text: "ماموریتهای شرکت: ماموریت ما سادهست: کمک به ساخت پروژههایی با کیفیت، ایمن، دقیق و ماندگار. ما تو دژبند باور داریم که هر پروژه، چه کوچک چه بزرگ، شایستهی دقت و طراحی اصولیه.",
|
||||
},
|
||||
{
|
||||
key: "vision",
|
||||
label: "چشم انداز شرکت",
|
||||
img: "/images/about/vision.jpg",
|
||||
heading: "نگاه ما به آینده ساختوساز",
|
||||
text: "چشمانداز ما؟ ایجاد یک برند تخصصی در طراحی و مشاوره پروژههای صنعتی و ساختمانی، که وقتی اسمش میاد، خیال کارفرما راحت باشه.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function MissionTabs() {
|
||||
const [active, setActive] = useState(0);
|
||||
const t = TABS[active];
|
||||
|
||||
return (
|
||||
<div className="mv-tabs">
|
||||
<div className="mv-tabnav">
|
||||
{TABS.map((tab, i) => (
|
||||
<button
|
||||
key={tab.key}
|
||||
className={i === active ? "active" : ""}
|
||||
onClick={() => setActive(i)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mv-panel">
|
||||
<div className="mv-media">
|
||||
<Image src={t.img} alt={t.label} width={420} height={420} />
|
||||
</div>
|
||||
<div className="mv-body">
|
||||
<h6>{t.heading}</h6>
|
||||
<p>{t.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function PageTitle({ title, en, crumb, as = "h1" }) {
|
||||
const Heading = as;
|
||||
return (
|
||||
<section className="page-title-small">
|
||||
<div className="container">
|
||||
<div className="page-title-inner">
|
||||
<div className="page-title-main">
|
||||
<Heading className="page-title-h">{title}</Heading>
|
||||
{en ? <span className="en">{en}</span> : null}
|
||||
</div>
|
||||
<ul className="page-crumb">
|
||||
<li>
|
||||
<Link href="/">خانه</Link>
|
||||
</li>
|
||||
<li>
|
||||
<span>{crumb || title}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Image from "next/image";
|
||||
import { PORTFOLIO } from "@/lib/site";
|
||||
|
||||
export default function Portfolio() {
|
||||
return (
|
||||
<section id="portfolio" className="pf-section">
|
||||
<ul className="pf-grid">
|
||||
{PORTFOLIO.map((p) => (
|
||||
<li className="pf-item" key={p.img}>
|
||||
<Image src={p.img} alt={`${p.cat} — ${p.en}`} width={736} height={600} />
|
||||
<div className="pf-hover">
|
||||
<div className="pf-hover-text">
|
||||
<span className="pf-cat">{p.cat}</span>
|
||||
<h6>{p.en}</h6>
|
||||
</div>
|
||||
<span className="pf-arrow" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M7 17 17 7M8 7h9v9" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import Link from "next/link";
|
||||
import { SERVICES } from "@/lib/site";
|
||||
|
||||
export default function Services() {
|
||||
return (
|
||||
<section id="services" className="svc-section">
|
||||
<div className="container">
|
||||
<div className="sec-head">
|
||||
<h4>خدمات ما</h4>
|
||||
<span className="en">OUR SERVICES</span>
|
||||
</div>
|
||||
<div className="svc-grid">
|
||||
{SERVICES.map((s) => (
|
||||
<div className="svc-item" key={s.title}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={s.icon} width={150} height={150} alt={`آیکون ${s.title}`} />
|
||||
<Link href="/#services">
|
||||
<span>{s.title}</span>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { STATS } from "@/lib/site";
|
||||
|
||||
function Counter({ to, run }) {
|
||||
const [n, setN] = useState(0);
|
||||
useEffect(() => {
|
||||
if (!run) return;
|
||||
let raf;
|
||||
const dur = 1800;
|
||||
const start = performance.now();
|
||||
const tick = (now) => {
|
||||
const p = Math.min((now - start) / dur, 1);
|
||||
const eased = 1 - Math.pow(1 - p, 3);
|
||||
setN(Math.round(eased * to));
|
||||
if (p < 1) raf = requestAnimationFrame(tick);
|
||||
};
|
||||
raf = requestAnimationFrame(tick);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [run, to]);
|
||||
return <h4 className="stat-num">{n.toLocaleString("fa-IR")}</h4>;
|
||||
}
|
||||
|
||||
export default function Stats() {
|
||||
const ref = useRef(null);
|
||||
const [seen, setSeen] = useState(false);
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
const ob = new IntersectionObserver(
|
||||
([e]) => {
|
||||
if (e.isIntersecting) {
|
||||
setSeen(true);
|
||||
ob.disconnect();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.3 }
|
||||
);
|
||||
ob.observe(el);
|
||||
return () => ob.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="stats-section">
|
||||
<div className="container">
|
||||
<div className="text-center stats-head">
|
||||
<h6>آمار وبسایت ما</h6>
|
||||
<p>.در این بخش آمار وبسایت ما را مشاهده کنید</p>
|
||||
</div>
|
||||
<div className="stats-grid" ref={ref}>
|
||||
{STATS.map((s) => (
|
||||
<div className="stat" key={s.label}>
|
||||
<Counter to={s.to} run={seen} />
|
||||
<span className="stat-label">{s.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||