feat: update Dockerfile, add category pages, fonts and product images

Rebuild Docker setup with simplified nginx-only Dockerfile (build runs
on host). Add category detail pages, IRANSans font files, product
images, and update all landing sections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-07 00:45:51 +03:30
co-authored by Claude Opus 4.6
parent e31ef5a21f
commit bb7f1e3378
33 changed files with 920 additions and 196 deletions
+1 -5
View File
@@ -2,12 +2,8 @@ node_modules
.nuxt .nuxt
.output .output
.git .git
.gitignore
app
server
shared
public
*.md *.md
_dev.log _dev.log
.env* .env*
npm-debug.log* npm-debug.log*
logo.png
+1 -1
View File
@@ -6,7 +6,7 @@ node_modules/
.output/ .output/
.data/ .data/
dist dist
# out/ عمداً در git نگه داشته می‌شود (بیلد محلی → دیپلوی با Docker) out/
# misc # misc
.DS_Store .DS_Store
+3 -3
View File
@@ -1,8 +1,8 @@
FROM docker.arvancloud.ir/nginx:latest # Static export is built on the host (`npm run build` → ./out)
# This image only serves the pre-built files via nginx.
FROM docker.arvancloud.ir/nginx:alpine
# خروجی static export (npm run build → پوشه out)
COPY out /usr/share/nginx/html COPY out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80 EXPOSE 80
+47 -21
View File
@@ -1,20 +1,46 @@
@import "tailwindcss"; @import "tailwindcss";
/* ---- local fonts (replaces next/font/local) ---- /* ---- local fonts ---- */
Same family names, fallback faces and metric overrides that next/font generated, so text
(including Persian glyphs inside font-mono, which fall back to the size-adjusted Arial face)
renders exactly as before. */
@font-face { @font-face {
font-family: "vazirmatn"; font-family: "IRANSans";
src: url("/fonts/Vazirmatn-Variable.woff2") format("woff2"); src: url("/fonts/IRANSansWeb_UltraLight.woff2") format("woff2");
font-weight: 100 900; font-weight: 200;
font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "vazirmatn Fallback"; font-family: "IRANSans";
src: url("/fonts/IRANSansWeb_Light.woff2") format("woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANSans";
src: url("/fonts/IRANSansWeb.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANSans";
src: url("/fonts/IRANSansWeb_Medium.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANSans";
src: url("/fonts/IRANSansWeb_Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANSans Fallback";
src: local("Arial"); src: local("Arial");
ascent-override: 102.54%; ascent-override: 100%;
descent-override: 53.71%; descent-override: 35%;
line-gap-override: 0%; line-gap-override: 0%;
size-adjust: 100%; size-adjust: 100%;
} }
@@ -49,23 +75,23 @@
} }
:root { :root {
--font-vazirmatn: "vazirmatn", "vazirmatn Fallback", Tahoma, system-ui, sans-serif; --font-iransans: "IRANSans", "IRANSans Fallback", Tahoma, system-ui, sans-serif;
--font-ibm-plex-mono: "ibmPlexMono", "ibmPlexMono Fallback", ui-monospace, monospace; --font-ibm-plex-mono: "ibmPlexMono", "ibmPlexMono Fallback", ui-monospace, monospace;
} }
@theme { @theme {
--font-sans: var(--font-vazirmatn), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; /* font variables defined in :root above */ --font-sans: var(--font-iransans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
--font-mono: var(--font-ibm-plex-mono), "Vazirmatn", ui-monospace, monospace; /* font variables defined in :root above */ --font-mono: var(--font-ibm-plex-mono), "IRANSans", ui-monospace, monospace;
--color-bg: #0a0a0c; --color-bg: #0b1224;
--color-panel: #121216; --color-panel: #101a32;
--color-panel-2: #17171c; --color-panel-2: #152040;
--color-ink: #edede6; --color-ink: #edede6;
--color-muted: #8e8e96; --color-muted: #8e9ab0;
--color-accent: #ff5a00; --color-accent: #ff5a00;
--color-accent-soft: #ff7a2a; --color-accent-soft: #ff7a2a;
--color-hazard: #ffb800; --color-hazard: #ffb800;
--color-line: #232329; --color-line: #1e2d4d;
--animate-marquee: marquee 32s linear infinite; --animate-marquee: marquee 32s linear infinite;
--animate-spin-slow: spin-slow 18s linear infinite; --animate-spin-slow: spin-slow 18s linear infinite;
@@ -162,7 +188,7 @@ body {
background: var(--color-bg); background: var(--color-bg);
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #2c2c33; background: #253558;
border-radius: 8px; border-radius: 8px;
border: 2px solid var(--color-bg); border: 2px solid var(--color-bg);
} }
@@ -175,7 +201,7 @@ body {
background-image: repeating-linear-gradient( background-image: repeating-linear-gradient(
-45deg, -45deg,
#ffb800 0 14px, #ffb800 0 14px,
#0a0a0c 14px 28px #0b1224 14px 28px
); );
} }
@@ -214,6 +240,6 @@ body {
background-image: repeating-linear-gradient( background-image: repeating-linear-gradient(
45deg, 45deg,
#ffb800 0 10px, #ffb800 0 10px,
#0a0a0c 10px 20px #0b1224 10px 20px
); );
} }
+20 -23
View File
@@ -1,24 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { AtSign, Globe, Phone, Send } from "lucide-vue-next"; import { AtSign, Globe, Phone, Send } from "lucide-vue-next";
import { CATEGORIES } from "#shared/data/categories";
import Logo from "~/components/ui/Logo.vue"; import Logo from "~/components/ui/Logo.vue";
const QUICK_LINKS = [ const QUICK_LINKS = [
{ href: "#home", label: "خانه" }, { href: "/#home", label: "خانه" },
{ href: "#products", label: "محصولات" }, { href: "/#products", label: "محصولات" },
{ href: "#about", label: "درباره ما" }, { href: "/#about", label: "درباره ما" },
{ href: "#services", label: "خدمات" }, { href: "/#services", label: "خدمات" },
{ href: "#standards", label: "استانداردها" }, { href: "/#standards", label: "استانداردها" },
{ href: "#industries", label: "صنایع" }, { href: "/#industries", label: "صنایع" },
]; ];
const PRODUCT_LINKS = [ const PRODUCT_LINKS = CATEGORIES.map((c) => ({
"کلاه ایمنی", label: c.name,
"دستکش ضدبرش", href: `/categories/${c.slug}`,
"کفش ایمنی", }));
"عینک ایمنی",
"هارنس ضدسقوط",
"لباس کار بازتابنده",
];
const SOCIALS = [ const SOCIALS = [
{ icon: Globe, label: "وب‌سایت" }, { icon: Globe, label: "وب‌سایت" },
@@ -32,7 +29,7 @@ const SOCIALS = [
<footer class="relative overflow-hidden border-t border-line"> <footer class="relative overflow-hidden border-t border-line">
<div class="hazard h-2.5" /> <div class="hazard h-2.5" />
<div class="mx-auto max-w-7xl px-5 md:px-8"> <div class="mx-auto max-w-[1480px] px-5 md:px-8">
<div class="grid gap-12 py-16 md:grid-cols-12 md:py-20"> <div class="grid gap-12 py-16 md:grid-cols-12 md:py-20">
<!-- brand --> <!-- brand -->
<div class="md:col-span-5"> <div class="md:col-span-5">
@@ -45,7 +42,7 @@ const SOCIALS = [
<a <a
v-for="s in SOCIALS" v-for="s in SOCIALS"
:key="s.label" :key="s.label"
href="#contact" href="/#contact"
:aria-label="s.label" :aria-label="s.label"
class="flex size-10 items-center justify-center rounded-lg border border-line text-muted transition-all duration-300 hover:border-accent hover:bg-accent hover:text-bg" class="flex size-10 items-center justify-center rounded-lg border border-line text-muted transition-all duration-300 hover:border-accent hover:bg-accent hover:text-bg"
> >
@@ -70,10 +67,10 @@ const SOCIALS = [
<div class="md:col-span-2"> <div class="md:col-span-2">
<div class="font-mono text-xs tracking-[0.25em] text-accent">// محصولات</div> <div class="font-mono text-xs tracking-[0.25em] text-accent">// محصولات</div>
<ul class="mt-5 space-y-3"> <ul class="mt-5 space-y-3">
<li v-for="p in PRODUCT_LINKS" :key="p"> <li v-for="p in PRODUCT_LINKS" :key="p.href">
<a href="#products" class="text-sm text-muted transition-colors hover:text-ink"> <NuxtLink :to="p.href" class="text-sm text-muted transition-colors hover:text-ink">
{{ p }} {{ p.label }}
</a> </NuxtLink>
</li> </li>
</ul> </ul>
</div> </div>
@@ -88,7 +85,7 @@ const SOCIALS = [
خیابان فر، کوچه دوم، پلاک ۱۲ خیابان فر، کوچه دوم، پلاک ۱۲
</p> </p>
<p class="font-mono tracking-wider" dir="ltr">۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰</p> <p class="font-mono tracking-wider" dir="ltr">۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰</p>
<p class="font-mono tracking-wider" dir="ltr">info@ariaiman.ir</p> <p class="font-mono tracking-wider" dir="ltr">info@imenpishgaman.ir</p>
<p class="pt-2 text-xs leading-6"> <p class="pt-2 text-xs leading-6">
شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰ شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰
<br /> <br />
@@ -101,14 +98,14 @@ const SOCIALS = [
<!-- giant wordmark --> <!-- giant wordmark -->
<div aria-hidden="true" class="pointer-events-none select-none pb-6"> <div aria-hidden="true" class="pointer-events-none select-none pb-6">
<div class="text-outline text-center text-[17vw] font-black leading-[0.85] md:text-[11vw]"> <div class="text-outline text-center text-[17vw] font-black leading-[0.85] md:text-[11vw]">
آریا ایمن ایمن پیشگامان
</div> </div>
</div> </div>
<div <div
class="flex flex-wrap items-center justify-between gap-4 border-t border-line py-6 text-xs text-muted" class="flex flex-wrap items-center justify-between gap-4 border-t border-line py-6 text-xs text-muted"
> >
<div>© ۱۴۰۴ آریا ایمن — تمامی حقوق محفوظ است.</div> <div>© ۱۴۰۴ ایمن پیشگامان — تمامی حقوق محفوظ است.</div>
<div class="font-mono text-[10px] tracking-[0.25em]"> <div class="font-mono text-[10px] tracking-[0.25em]">
ISO 9001:2015 — CE CERTIFIED PPE SUPPLIER ISO 9001:2015 — CE CERTIFIED PPE SUPPLIER
</div> </div>
+9 -9
View File
@@ -4,13 +4,13 @@ import { Menu, Phone, X } from "lucide-vue-next";
import Logo from "~/components/ui/Logo.vue"; import Logo from "~/components/ui/Logo.vue";
const LINKS = [ const LINKS = [
{ href: "#home", label: "خانه" }, { href: "/#home", label: "خانه" },
{ href: "#products", label: "محصولات" }, { href: "/#products", label: "محصولات" },
{ href: "#about", label: "درباره ما" }, { href: "/#about", label: "درباره ما" },
{ href: "#services", label: "خدمات" }, { href: "/#services", label: "خدمات" },
{ href: "#standards", label: "استانداردها" }, { href: "/#standards", label: "استانداردها" },
{ href: "#industries", label: "صنایع" }, { href: "/#industries", label: "صنایع" },
{ href: "#contact", label: "تماس" }, { href: "/#contact", label: "تماس" },
]; ];
const ui = useUiStore(); const ui = useUiStore();
@@ -37,7 +37,7 @@ onBeforeUnmount(() => {
scrolled ? 'border-b border-line bg-bg/85 py-3 backdrop-blur-xl' : 'py-5' scrolled ? 'border-b border-line bg-bg/85 py-3 backdrop-blur-xl' : 'py-5'
}`" }`"
> >
<div class="mx-auto flex max-w-7xl items-center justify-between px-5 md:px-8"> <div class="mx-auto flex max-w-[1480px] items-center justify-between px-5 md:px-8">
<Logo /> <Logo />
<nav class="hidden items-center gap-7 lg:flex"> <nav class="hidden items-center gap-7 lg:flex">
@@ -65,7 +65,7 @@ onBeforeUnmount(() => {
</div> </div>
<a <a
href="#contact" href="/#contact"
class="hidden items-center gap-2 rounded-full bg-accent px-5 py-2.5 text-sm font-bold text-bg transition-all duration-300 hover:bg-accent-soft hover:shadow-[0_0_30px_rgba(255,90,0,0.4)] md:flex" class="hidden items-center gap-2 rounded-full bg-accent px-5 py-2.5 text-sm font-bold text-bg transition-all duration-300 hover:bg-accent-soft hover:shadow-[0_0_30px_rgba(255,90,0,0.4)] md:flex"
> >
<Phone class="size-4" /> <Phone class="size-4" />
+1 -1
View File
@@ -54,7 +54,7 @@ const step = computed(
<ShieldCheck class="size-7" /> <ShieldCheck class="size-7" />
</span> </span>
<span class="font-mono text-xs tracking-[0.35em] text-muted"> <span class="font-mono text-xs tracking-[0.35em] text-muted">
ARIA IMEN CO. IMEN PISHGAMAN CO.
</span> </span>
</div> </div>
+3 -3
View File
@@ -4,7 +4,7 @@ import Reveal from "~/components/ui/Reveal.vue";
import SectionHead from "~/components/ui/SectionHead.vue"; import SectionHead from "~/components/ui/SectionHead.vue";
const TIMELINE = [ const TIMELINE = [
{ year: "۱۳۸۶", text: "تأسیس آریا ایمن در تهران با تمرکز بر تجهیزات حفاظت فردی" }, { year: "۱۳۸۶", text: "تأسیس ایمن پیشگامان در تهران با تمرکز بر تجهیزات حفاظت فردی" },
{ year: "۱۳۹۲", text: "توسعه شبکه توزیع به تمام ۳۱ استان کشور" }, { year: "۱۳۹۲", text: "توسعه شبکه توزیع به تمام ۳۱ استان کشور" },
{ year: "۱۳۹۸", text: "راه‌اندازی آزمایشگاه کنترل کیفیت و تست تجهیزات" }, { year: "۱۳۹۸", text: "راه‌اندازی آزمایشگاه کنترل کیفیت و تست تجهیزات" },
{ year: "۱۴۰۲", text: "افتتاح مرکز تخصصی آموزش و بازرسی HSE" }, { year: "۱۴۰۲", text: "افتتاح مرکز تخصصی آموزش و بازرسی HSE" },
@@ -25,14 +25,14 @@ const FEATURES = [
> >
<div class="absolute inset-0 bg-grid-fine opacity-50" /> <div class="absolute inset-0 bg-grid-fine opacity-50" />
<div class="relative mx-auto max-w-7xl px-5 md:px-8"> <div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
<div class="grid items-center gap-14 lg:grid-cols-2"> <div class="grid items-center gap-14 lg:grid-cols-2">
<!-- ---- image ---- --> <!-- ---- image ---- -->
<Reveal class="relative order-2 lg:order-1" :delay="0.1"> <Reveal class="relative order-2 lg:order-1" :delay="0.1">
<div class="relative overflow-hidden rounded-2xl border border-line"> <div class="relative overflow-hidden rounded-2xl border border-line">
<img <img
src="/images/about.jpg" src="/images/about.jpg"
alt="انبار تجهیزات ایمنی آریا ایمن" alt="انبار تجهیزات ایمنی ایمن پیشگامان"
width="800" width="800"
height="600" height="600"
loading="lazy" loading="lazy"
+2 -2
View File
@@ -21,7 +21,7 @@ const INFO = [
value: "تهران، خیابان کارگر شمالی، خیابان فر، کوچه دوم، پلاک ۱۲", value: "تهران، خیابان کارگر شمالی، خیابان فر، کوچه دوم، پلاک ۱۲",
}, },
{ icon: Phone, label: "تلفن تماس", value: "۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰", ltr: true }, { icon: Phone, label: "تلفن تماس", value: "۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰", ltr: true },
{ icon: Mail, label: "ایمیل", value: "info@ariaiman.ir", ltr: true }, { icon: Mail, label: "ایمیل", value: "info@imenpishgaman.ir", ltr: true },
{ icon: Clock, label: "ساعات کاری", value: "شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰" }, { icon: Clock, label: "ساعات کاری", value: "شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰" },
]; ];
@@ -46,7 +46,7 @@ const contact = useContactStore();
> >
<div class="absolute inset-0 bg-grid-fine opacity-40" /> <div class="absolute inset-0 bg-grid-fine opacity-40" />
<div class="relative mx-auto max-w-7xl px-5 md:px-8"> <div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
<div class="grid gap-14 lg:grid-cols-2"> <div class="grid gap-14 lg:grid-cols-2">
<!-- ---- info ---- --> <!-- ---- info ---- -->
<div> <div>
+5 -5
View File
@@ -44,7 +44,7 @@ const MINI_STATS = [
<!-- ---- content ---- --> <!-- ---- content ---- -->
<div <div
class="relative z-10 mx-auto flex w-full max-w-7xl flex-1 items-center px-5 pb-24 pt-36 md:px-8" class="relative z-10 mx-auto flex w-full max-w-[1480px] flex-1 items-center px-5 pb-24 pt-36 md:px-8"
> >
<div class="grid w-full items-center gap-12 lg:grid-cols-12"> <div class="grid w-full items-center gap-12 lg:grid-cols-12">
<div class="lg:col-span-7"> <div class="lg:col-span-7">
@@ -72,7 +72,7 @@ const MINI_STATS = [
<p <p
class="animate-fade-up-3 mt-7 max-w-xl text-base leading-9 text-ink/70 md:text-lg md:leading-10" class="animate-fade-up-3 mt-7 max-w-xl text-base leading-9 text-ink/70 md:text-lg md:leading-10"
> >
آریا ایمن با بیش از ۱۸ سال تجربه، تأمین‌کننده تخصصی تجهیزات ایمنی و ایمن پیشگامان با بیش از ۱۸ سال تجربه، تأمین‌کننده تخصصی تجهیزات ایمنی و
حفاظت فردی برای سازمان‌هایی است که جان انسان‌ها را جدی می‌گیرند؛ از حفاظت فردی برای سازمان‌هایی است که جان انسان‌ها را جدی می‌گیرند؛ از
کلاه ایمنی تا هارنس نجات، از مشاوره تا بازرسی و آموزش. کلاه ایمنی تا هارنس نجات، از مشاوره تا بازرسی و آموزش.
</p> </p>
@@ -121,7 +121,7 @@ const MINI_STATS = [
</defs> </defs>
<text font-size="9" letter-spacing="2.6" class="fill-ink/60 font-mono"> <text font-size="9" letter-spacing="2.6" class="fill-ink/60 font-mono">
<textPath href="#badge-circle"> <textPath href="#badge-circle">
ARIA IMEN SAFETY • ISO 9001 • CE CERTIFIED • EST 2007 • IMEN PISHGAMAN SAFETY • ISO 9001 • CE CERTIFIED • EST 2007 •
</textPath> </textPath>
</text> </text>
</svg> </svg>
@@ -156,7 +156,7 @@ const MINI_STATS = [
</div> </div>
<div class="my-4 h-px bg-line" /> <div class="my-4 h-px bg-line" />
<div class="font-mono text-[11px] leading-6 text-muted"> <div class="font-mono text-[11px] leading-6 text-muted">
سیستم پایش حرفه‌ای آریا ایمن<span class="animate-blink text-accent">▍</span> سیستم پایش حرفه‌ای ایمن پیشگامان<span class="animate-blink text-accent">▍</span>
</div> </div>
</div> </div>
</div> </div>
@@ -173,7 +173,7 @@ const MINI_STATS = [
<!-- ---- bottom bar ---- --> <!-- ---- bottom bar ---- -->
<div class="animate-fade-up-8 relative z-10 border-t border-ink/10 backdrop-blur-sm"> <div class="animate-fade-up-8 relative z-10 border-t border-ink/10 backdrop-blur-sm">
<div class="mx-auto grid max-w-7xl grid-cols-1 gap-px bg-ink/10 sm:grid-cols-3"> <div class="mx-auto grid max-w-[1480px] grid-cols-1 gap-px bg-ink/10 sm:grid-cols-3">
<div <div
v-for="s in MINI_STATS" v-for="s in MINI_STATS"
:key="s.label" :key="s.label"
+1 -1
View File
@@ -51,7 +51,7 @@ const onMove = (e: MouseEvent) => {
class="relative border-t border-line py-24 md:py-32" class="relative border-t border-line py-24 md:py-32"
@mousemove="onMove" @mousemove="onMove"
> >
<div class="mx-auto max-w-7xl px-5 md:px-8"> <div class="mx-auto max-w-[1480px] px-5 md:px-8">
<SectionHead <SectionHead
heading-id="industries-title" heading-id="industries-title"
tag="۰۵ — صنایع" tag="۰۵ — صنایع"
+72 -100
View File
@@ -1,69 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { AnimatePresence, motion } from "motion-v";
import { ArrowLeft, BadgeCheck, PackageCheck, ShieldCheck } from "lucide-vue-next"; import { ArrowLeft, BadgeCheck, PackageCheck, ShieldCheck } from "lucide-vue-next";
import { CATEGORIES } from "#shared/data/categories";
import Reveal from "~/components/ui/Reveal.vue"; import Reveal from "~/components/ui/Reveal.vue";
import SectionHead from "~/components/ui/SectionHead.vue"; import SectionHead from "~/components/ui/SectionHead.vue";
type Product = { const CATEGORY_IMAGES: Record<string, string> = {
code: string; workwear: "/images/products/vest.jpg",
name: string; "safety-footwear": "/images/products/shoes.jpg",
en: string; "protective-gloves": "/images/products/gloves.jpg",
desc: string; "safety-helmets": "/images/products/helmet.jpg",
img: string; "eye-protection": "/images/products/goggles.jpg",
specs: string[]; "hearing-protection": "/images/products/hearing.jpg",
"respiratory-protection": "/images/products/respiratory.jpg",
"traffic-equipment": "/images/products/traffic.jpg",
}; };
const PRODUCTS: Product[] = [
{
code: "AP-101",
name: "کلاه ایمنی",
en: "SAFETY HELMET",
img: "/images/products/helmet.jpg",
desc: "کلاه‌های ایمنی با بدنه HDPE مقاوم به ضربه و ترتیه‌بند ضدتعریق؛ با قابلیت نصب هدفون و شیشه محافظ صورت برای محیط‌های ساختمانی و صنعتی.",
specs: ["EN 397", "مقاوم به ضربه ۵۰ ژول", "بدنه HDPE"],
},
{
code: "AP-205",
name: "دستکش ضدبرش",
en: "CUT-RESISTANT GLOVE",
img: "/images/products/gloves.jpg",
desc: "دستکش‌های سطح ۵ ضدبرش با پوشش نیتریل؛ طراحی‌شده برای کار با ورق فلز، شیشه و ابزار برنده با چسبندگی عالی در محیط خشک و چرب.",
specs: ["EN 388 — 4X43C", "سطح ۵ ضدبرش", "پوشش نیتریل"],
},
{
code: "AP-310",
name: "کفش ایمنی",
en: "SAFETY BOOTS",
img: "/images/products/shoes.jpg",
desc: "کفش‌های ایمنی با پنجه فولادی و کف مقاوم در برابر سوراخ‌شدگی و روغن؛ راحتی تمام‌روز برای شیفت‌های طولانی در سایت.",
specs: ["EN ISO 20345", "پنجه فولادی", "کف ضدروغن"],
},
{
code: "AP-412",
name: "عینک ایمنی",
en: "SAFETY GOGGLES",
img: "/images/products/goggles.jpg",
desc: "عینک‌های محافظ ضدخش با پوشش UV400 و تهویه ضدمه؛ سازگار با عینک طبی برای کارهای جوشکاری، سنگ‌زنی و محیط‌های شیمیایی.",
specs: ["EN 166", "عدسی ضدخش", "پوشش UV400"],
},
{
code: "AP-508",
name: "هارنس ضدسقوط",
en: "FULL BODY HARNESS",
img: "/images/products/harness.jpg",
desc: "هارنس تمام‌بدنی با دو شیر پیچی و نوارهای دوخته‌شده با نخ نارنجی سیگنال؛ تست‌شده برای کار در ارتفاع با ظرفیت ۱۰۰ کیلوگرم.",
specs: ["EN 361", "ظرفیت ۱۰۰ کیلوگرم", "شیر دوگانه"],
},
{
code: "AP-615",
name: "لباس کار بازتابنده",
en: "HI-VIS WORKWEAR",
img: "/images/products/vest.jpg",
desc: "جلیقه‌ها و لباس‌های کار بازتابنده کلاس ۲ با نوارهای ۳M؛ دیده‌شدن کامل پرسنل در شب، مه و محیط‌های پرتردد خودرویی.",
specs: ["EN ISO 20471", "کلاس ۲", "نوار ۳M"],
},
];
const TRUST = [ const TRUST = [
{ {
icon: BadgeCheck, icon: BadgeCheck,
@@ -87,12 +38,12 @@ const active = ref(0);
<template> <template>
<section id="products" aria-labelledby="products-title" class="relative py-24 md:py-32"> <section id="products" aria-labelledby="products-title" class="relative py-24 md:py-32">
<div class="mx-auto max-w-7xl px-5 md:px-8"> <div class="mx-auto max-w-[1480px] px-5 md:px-8">
<div class="flex flex-wrap items-end justify-between gap-6"> <div class="flex flex-wrap items-end justify-between gap-6">
<SectionHead <SectionHead
heading-id="products-title" heading-id="products-title"
tag="۰۱ — محصولات" tag="۰۱ — محصولات"
desc="هر محصول پیش از عرضه، زیر ذره‌بین کنترل کیفیت و تطبیق با استانداردهای جهانی قرار می‌گیرد. روی هر پنل کلیک کنید." desc="هر محصول پیش از عرضه، زیر ذره‌بین کنترل کیفیت و تطبیق با استانداردهای جهانی قرار می‌گیرد."
> >
<template #title> <template #title>
خط اول دفاع، خط اول دفاع،
@@ -102,7 +53,7 @@ const active = ref(0);
</SectionHead> </SectionHead>
<Reveal :delay="0.2" class="hidden md:block"> <Reveal :delay="0.2" class="hidden md:block">
<a <a
href="#contact" href="/#contact"
class="group inline-flex items-center gap-2 rounded-full border border-line px-6 py-3 text-sm text-muted transition-colors hover:border-accent hover:text-accent" class="group inline-flex items-center gap-2 rounded-full border border-line px-6 py-3 text-sm text-muted transition-colors hover:border-accent hover:text-accent"
> >
دریافت کاتالوگ کامل دریافت کاتالوگ کامل
@@ -112,29 +63,70 @@ const active = ref(0);
</div> </div>
</div> </div>
<div class="mx-auto mt-14 max-w-7xl px-5 md:px-8"> <!-- ---- mobile: card grid ---- -->
<div class="flex flex-col gap-2 md:h-[74vh] md:min-h-[580px] md:flex-row"> <div class="mx-auto mt-14 max-w-[1480px] px-5 md:hidden">
<div <div class="grid grid-cols-2 gap-3">
v-for="(p, i) in PRODUCTS" <Reveal v-for="(cat, i) in CATEGORIES" :key="cat.code" :delay="i * 0.05">
:key="p.code" <NuxtLink
:to="`/categories/${cat.slug}`"
class="group relative block aspect-[3/4] overflow-hidden rounded-xl border border-line transition-colors duration-300 active:border-accent/60"
>
<img
v-if="CATEGORY_IMAGES[cat.slug]"
:src="CATEGORY_IMAGES[cat.slug]"
:alt="cat.name"
loading="lazy"
decoding="async"
class="absolute inset-0 size-full object-cover transition-transform duration-500 group-active:scale-105"
/>
<div v-else class="absolute inset-0 bg-panel" />
<div class="absolute inset-0 bg-gradient-to-t from-bg via-bg/50 to-transparent" />
<div class="absolute inset-x-0 top-0 flex items-center justify-between p-3">
<span class="font-mono text-[9px] tracking-widest text-ink/50">{{ cat.code }}</span>
<span class="font-mono text-[10px] text-accent">
{{ faNum(String(i + 1).padStart(2, "0")) }}
</span>
</div>
<div class="absolute inset-x-0 bottom-0 p-3">
<h3 class="text-sm font-black leading-6">{{ cat.name }}</h3>
<p class="mt-0.5 font-mono text-[8px] tracking-[0.15em] text-muted">{{ cat.en }}</p>
</div>
</NuxtLink>
</Reveal>
</div>
</div>
<!-- ---- desktop: accordion panels ---- -->
<div class="mx-auto mt-14 hidden max-w-[1480px] px-8 md:block">
<div class="flex h-[74vh] min-h-[580px] flex-row gap-2">
<NuxtLink
v-for="(cat, i) in CATEGORIES"
:key="cat.code"
:to="`/categories/${cat.slug}`"
data-hover data-hover
:class="`group relative cursor-pointer overflow-hidden rounded-xl border transition-all duration-700 ease-[cubic-bezier(0.22,1,0.36,1)] ${ :class="`group relative cursor-pointer overflow-hidden rounded-xl border transition-all duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] ${
active === i active === i
? 'h-[500px] flex-[3.4] border-accent/60 md:h-auto' ? 'flex-[3.4] border-accent/60'
: 'h-24 flex-[1] border-line hover:border-ink/30 md:h-auto' : 'flex-[1] border-line hover:border-ink/30'
}`" }`"
@mouseenter="active = i" @mouseenter="active = i"
@click="active = i"
> >
<img <img
:src="p.img" v-if="CATEGORY_IMAGES[cat.slug]"
:alt="p.name" :src="CATEGORY_IMAGES[cat.slug]"
:alt="cat.name"
loading="lazy" loading="lazy"
decoding="async" decoding="async"
:class="`absolute inset-0 size-full object-cover transition-all duration-700 ${ :class="`absolute inset-0 size-full object-cover transition-all duration-700 ${
active === i ? 'scale-100 grayscale-0' : 'scale-110 grayscale group-hover:grayscale-0' active === i ? 'scale-100 grayscale-0' : 'scale-110 grayscale group-hover:grayscale-0'
}`" }`"
/> />
<div
v-else
class="absolute inset-0 bg-panel"
/>
<div <div
:class="`absolute inset-0 transition-opacity duration-700 ${ :class="`absolute inset-0 transition-opacity duration-700 ${
active === i active === i
@@ -144,7 +136,7 @@ const active = ref(0);
/> />
<div class="absolute inset-x-0 top-0 flex items-center justify-between p-5"> <div class="absolute inset-x-0 top-0 flex items-center justify-between p-5">
<span class="font-mono text-[10px] tracking-widest text-ink/60">{{ p.code }}</span> <span class="font-mono text-[10px] tracking-widest text-ink/60">{{ cat.code }}</span>
<span <span
:class="`font-mono text-xs transition-colors duration-500 ${ :class="`font-mono text-xs transition-colors duration-500 ${
active === i ? 'text-accent' : 'text-ink/40' active === i ? 'text-accent' : 'text-ink/40'
@@ -154,42 +146,22 @@ const active = ref(0);
</span> </span>
</div> </div>
<div class="absolute inset-x-0 bottom-0 p-5 md:p-6"> <div class="absolute inset-x-0 bottom-0 p-6">
<h3 <h3
:class="`font-black transition-all duration-500 ${ :class="`font-black transition-all duration-500 ${
active === i ? 'text-2xl' : 'text-base md:text-sm' active === i ? 'text-2xl' : 'text-sm'
}`" }`"
> >
{{ p.name }} {{ cat.name }}
</h3> </h3>
<p class="mt-1 font-mono text-[10px] tracking-[0.2em] text-muted">{{ p.en }}</p> <p class="mt-1 font-mono text-[10px] tracking-[0.2em] text-muted">{{ cat.en }}</p>
<AnimatePresence>
<motion.div
v-if="active === i"
:initial="{ opacity: 0, y: 18 }"
:animate="{ opacity: 1, y: 0 }"
:exit="{ opacity: 0, y: 10 }"
:transition="{ duration: 0.45, delay: 0.12, ease: EASE }"
>
<p class="mt-4 max-w-md text-sm leading-8 text-ink/75">{{ p.desc }}</p>
<div class="mt-4 flex flex-wrap gap-2">
<span
v-for="s in p.specs"
:key="s"
class="rounded-full border border-accent/40 bg-accent/10 px-3 py-1 font-mono text-[10px] text-accent"
>
{{ s }}
</span>
</div>
</motion.div>
</AnimatePresence>
</div> </div>
</div> </NuxtLink>
</div> </div>
</div> </div>
<div <div
class="mx-auto mt-10 grid max-w-7xl gap-px overflow-hidden rounded-xl border border-line bg-line md:grid-cols-3" class="mx-auto mt-10 grid max-w-[1480px] gap-px overflow-hidden rounded-xl border border-line bg-line md:grid-cols-3"
> >
<Reveal v-for="(t, i) in TRUST" :key="t.title" :delay="i * 0.08"> <Reveal v-for="(t, i) in TRUST" :key="t.title" :delay="i * 0.08">
<div class="flex h-full items-center gap-4 bg-panel/60 p-6"> <div class="flex h-full items-center gap-4 bg-panel/60 p-6">
+2 -2
View File
@@ -58,11 +58,11 @@ const open = ref(0);
<template> <template>
<section id="services" aria-labelledby="services-title" class="border-t border-line py-24 md:py-32"> <section id="services" aria-labelledby="services-title" class="border-t border-line py-24 md:py-32">
<div class="mx-auto max-w-7xl px-5 md:px-8"> <div class="mx-auto max-w-[1480px] px-5 md:px-8">
<SectionHead <SectionHead
heading-id="services-title" heading-id="services-title"
tag="۰۳ — خدمات" tag="۰۳ — خدمات"
desc="ما در آریا ایمن، تجهیزات را تحویل می‌دهیم و کنار شما می‌مانیم؛ از نخستین ارزیابی ریسک تا آخرین تست دوره‌ای." desc="ما در ایمن پیشگامان، تجهیزات را تحویل می‌دهیم و کنار شما می‌مانیم؛ از نخستین ارزیابی ریسک تا آخرین تست دوره‌ای."
> >
<template #title> <template #title>
فراتر از تأمین؛ فراتر از تأمین؛
+1 -1
View File
@@ -27,7 +27,7 @@ const STANDARDS = [
CE CE
</div> </div>
<div class="relative mx-auto max-w-7xl px-5 md:px-8"> <div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
<div class="grid items-center gap-14 lg:grid-cols-12"> <div class="grid items-center gap-14 lg:grid-cols-12">
<div class="lg:col-span-5"> <div class="lg:col-span-5">
<SectionHead <SectionHead
+1 -1
View File
@@ -11,7 +11,7 @@ const STATS = [
</script> </script>
<template> <template>
<section class="relative" aria-label="آمار و دستاوردهای آریا ایمن"> <section class="relative" aria-label="آمار و دستاوردهای ایمن پیشگامان">
<div class="grid grid-cols-2 gap-px border-y border-line bg-line lg:grid-cols-4"> <div class="grid grid-cols-2 gap-px border-y border-line bg-line lg:grid-cols-4">
<div <div
v-for="(s, i) in STATS" v-for="(s, i) in STATS"
+1 -1
View File
@@ -5,7 +5,7 @@ import SectionHead from "~/components/ui/SectionHead.vue";
const QUOTES = [ const QUOTES = [
{ {
text: "آریا ایمن فقط تأمین‌کننده نیست؛ شریک ایمنی ماست. بازرسی‌های دوره‌ای و آموزش‌های میدانی‌شان نرخ حوادث سایت را به کمترین رقم تاریخ مجموعه رساند.", text: "ایمن پیشگامان فقط تأمین‌کننده نیست؛ شریک ایمنی ماست. بازرسی‌های دوره‌ای و آموزش‌های میدانی‌شان نرخ حوادث سایت را به کمترین رقم تاریخ مجموعه رساند.",
name: "مهندس رضا کاظمی", name: "مهندس رضا کاظمی",
role: "مدیر HSE — مجتمع پتروشیمی خلیج فارس", role: "مدیر HSE — مجتمع پتروشیمی خلیج فارس",
}, },
+23
View File
@@ -0,0 +1,23 @@
<script setup lang="ts">
import { ImageOff } from "lucide-vue-next";
withDefaults(
defineProps<{
label?: string;
aspectClass?: string;
}>(),
{
label: "تصویر محصول",
aspectClass: "aspect-[4/3]",
},
);
</script>
<template>
<div
:class="`flex flex-col items-center justify-center border border-dashed border-line bg-panel ${aspectClass}`"
>
<ImageOff class="size-10 text-muted/30" />
<span class="mt-2 font-mono text-[10px] tracking-wider text-muted/40">{{ label }}</span>
</div>
</template>
+12 -7
View File
@@ -1,19 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { ShieldCheck } from "lucide-vue-next";
</script> </script>
<template> <template>
<a href="#home" class="flex items-center gap-3" aria-label="آریا ایمن"> <NuxtLink to="/" class="flex items-center gap-3" aria-label="ایمن پیشگامان">
<span <span
class="flex size-10 items-center justify-center rounded-xl bg-accent text-bg shadow-[0_0_28px_rgba(255,90,0,0.35)]" class="flex size-10 items-center justify-center rounded-xl bg-white shadow-[0_0_28px_rgba(10,30,92,0.45)]"
> >
<ShieldCheck class="size-6" /> <img
src="/images/logo.png"
alt="ایمن پیشگامان"
width="28"
height="28"
class="size-7 object-contain"
/>
</span> </span>
<span class="leading-tight"> <span class="leading-tight">
<span class="block text-lg font-black">آریا ایمن</span> <span class="block text-lg font-black">ایمن پیشگامان</span>
<span class="block font-mono text-[9px] tracking-[0.32em] text-muted"> <span class="block font-mono text-[9px] tracking-[0.32em] text-muted">
ARIA IMEN CO. IMEN PISHGAMAN CO.
</span> </span>
</span> </span>
</a> </NuxtLink>
</template> </template>
+192
View File
@@ -0,0 +1,192 @@
<script setup lang="ts">
import { ArrowRight, Phone } from "lucide-vue-next";
import { absUrl } from "#shared/config/site";
import { CATEGORIES, findCategory } from "#shared/data/categories";
import { categoryPageSchema } from "#shared/seo/schema";
import AppShell from "~/components/layout/AppShell.vue";
import Footer from "~/components/layout/Footer.vue";
import SectionHead from "~/components/ui/SectionHead.vue";
import Reveal from "~/components/ui/Reveal.vue";
const CATEGORY_IMAGES: Record<string, string> = {
workwear: "/images/products/vest.jpg",
"safety-footwear": "/images/products/shoes.jpg",
"protective-gloves": "/images/products/gloves.jpg",
"safety-helmets": "/images/products/helmet.jpg",
"eye-protection": "/images/products/goggles.jpg",
"hearing-protection": "/images/products/hearing.jpg",
"respiratory-protection": "/images/products/respiratory.jpg",
"traffic-equipment": "/images/products/traffic.jpg",
};
const route = useRoute();
const slug = route.params.slug as string;
const category = findCategory(slug);
if (!category) {
throw createError({ statusCode: 404, statusMessage: "دسته‌بندی یافت نشد" });
}
const categoryIndex = CATEGORIES.findIndex((c) => c.slug === slug);
const canonical = absUrl(`/categories/${slug}`);
useHead({
title: `${category.name} | ایمن پیشگامان`,
link: [{ rel: "canonical", href: canonical }],
});
useSeoMeta({
description: category.fullDesc,
robots: "index, follow",
ogUrl: canonical,
ogTitle: `${category.name} | ایمن پیشگامان`,
ogDescription: category.fullDesc,
});
useJsonLd(categoryPageSchema(category), `category-${slug}`);
</script>
<template>
<AppShell>
<main>
<!-- ---- hero banner ---- -->
<section class="relative overflow-hidden pt-32 pb-20 md:pt-40 md:pb-28">
<div class="absolute inset-0">
<img
v-if="CATEGORY_IMAGES[slug]"
:src="CATEGORY_IMAGES[slug]"
:alt="category.name"
class="absolute inset-0 size-full object-cover"
fetchpriority="high"
decoding="async"
/>
<div class="absolute inset-0 bg-gradient-to-l from-bg via-bg/85 to-bg/40" />
<div class="absolute inset-0 bg-gradient-to-t from-bg via-bg/50 to-bg/30" />
<div class="absolute inset-0 bg-grid opacity-40" />
</div>
<div class="tape-v absolute bottom-0 end-0 z-10 h-32 w-1.5 opacity-70" />
<div class="relative z-10 mx-auto max-w-[1480px] px-5 md:px-8">
<Reveal>
<nav class="flex items-center gap-2 text-sm text-muted" aria-label="مسیر">
<NuxtLink to="/" class="transition-colors hover:text-accent">خانه</NuxtLink>
<span class="text-line">/</span>
<NuxtLink to="/#products" class="transition-colors hover:text-accent">
محصولات
</NuxtLink>
<span class="text-line">/</span>
<span class="text-ink">{{ category.name }}</span>
</nav>
</Reveal>
<div class="mt-8">
<SectionHead
:tag="`${faNum(String(categoryIndex + 1).padStart(2, '0'))} — محصولات`"
:desc="category.fullDesc"
>
<template #title>{{ category.name }}</template>
</SectionHead>
</div>
<Reveal :delay="0.2">
<div class="mt-8 flex flex-wrap items-center gap-4">
<span
class="rounded-full border border-accent/40 bg-accent/10 px-4 py-1.5 font-mono text-xs text-accent backdrop-blur"
>
{{ faNum(category.products.length) }} محصول
</span>
<span class="font-mono text-xs tracking-[0.25em] text-muted/60">
{{ category.en }}
</span>
</div>
</Reveal>
</div>
</section>
<!-- ---- products grid ---- -->
<section class="relative py-16 md:py-24">
<div class="absolute inset-0 bg-grid-fine opacity-30" />
<div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<Reveal v-for="(product, i) in category.products" :key="product.code" :delay="i * 0.06">
<div
class="group h-full overflow-hidden rounded-xl border border-line bg-panel/80 backdrop-blur transition-all duration-500 hover:border-accent/40 hover:shadow-[0_8px_40px_rgba(255,90,0,0.08)]"
>
<div class="relative aspect-[4/3] overflow-hidden">
<img
:src="CATEGORY_IMAGES[slug]"
:alt="product.name"
loading="lazy"
decoding="async"
class="size-full object-cover transition-transform duration-700 group-hover:scale-105"
/>
<div class="absolute inset-0 bg-gradient-to-t from-bg/60 to-transparent" />
<div class="absolute inset-x-0 top-0 flex items-center justify-between p-4">
<span class="rounded-full bg-bg/60 px-3 py-1 font-mono text-[10px] tracking-widest text-ink/70 backdrop-blur">
{{ product.code }}
</span>
<span class="font-mono text-xs text-accent">
{{ faNum(String(i + 1).padStart(2, "0")) }}
</span>
</div>
</div>
<div class="p-5 md:p-6">
<h3 class="text-xl font-black">{{ product.name }}</h3>
<p class="mt-1 font-mono text-[10px] tracking-[0.2em] text-muted">
{{ product.en }}
</p>
<p class="mt-3 text-sm leading-8 text-muted">{{ product.desc }}</p>
<div class="mt-4 flex flex-wrap gap-2">
<span
v-for="s in product.specs"
:key="s"
class="rounded-full border border-accent/40 bg-accent/10 px-3 py-1 font-mono text-[10px] text-accent"
>
{{ s }}
</span>
</div>
<div v-if="product.variants?.length" class="mt-4">
<div class="mb-2 text-xs text-muted">مدل‌ها / رنگ‌ها:</div>
<div class="flex flex-wrap gap-1.5">
<span
v-for="v in product.variants"
:key="v"
class="rounded-md border border-line bg-panel-2 px-2.5 py-1 text-[11px] text-ink/70"
>
{{ v }}
</span>
</div>
</div>
</div>
</div>
</Reveal>
</div>
</div>
</section>
<!-- ---- bottom CTA ---- -->
<section class="border-t border-line py-12 md:py-16">
<div class="mx-auto flex max-w-[1480px] flex-wrap items-center justify-between gap-6 px-5 md:px-8">
<NuxtLink
to="/#products"
class="group inline-flex items-center gap-2 text-muted transition-colors hover:text-accent"
>
<ArrowRight class="size-4 transition-transform group-hover:translate-x-1" />
بازگشت به دسته‌بندی‌ها
</NuxtLink>
<a
href="/#contact"
class="inline-flex items-center gap-2 rounded-full bg-accent px-6 py-3 text-sm font-bold text-bg transition-all hover:bg-accent-soft hover:shadow-[0_8px_45px_rgba(255,90,0,0.5)]"
>
<Phone class="size-4" />
درخواست قیمت
</a>
</div>
</section>
</main>
<Footer />
</AppShell>
</template>
+17 -2
View File
@@ -1,6 +1,7 @@
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { siteConfig } from "./shared/config/site"; import { siteConfig } from "./shared/config/site";
import { categorySlugs } from "./shared/data/categories";
/** Unmatched path rendered through error.vue at build time and saved as out/404.html */ /** Unmatched path rendered through error.vue at build time and saved as out/404.html */
const NOT_FOUND_ROUTE = "/__not-found"; const NOT_FOUND_ROUTE = "/__not-found";
@@ -23,7 +24,14 @@ export default defineNuxtConfig({
link: [ link: [
{ {
rel: "preload", rel: "preload",
href: "/fonts/Vazirmatn-Variable.woff2", href: "/fonts/IRANSansWeb.woff2",
as: "font",
type: "font/woff2",
crossorigin: "",
},
{
rel: "preload",
href: "/fonts/IRANSansWeb_Bold.woff2",
as: "font", as: "font",
type: "font/woff2", type: "font/woff2",
crossorigin: "", crossorigin: "",
@@ -51,7 +59,14 @@ export default defineNuxtConfig({
output: { publicDir: fileURLToPath(new URL("./out", import.meta.url)) }, output: { publicDir: fileURLToPath(new URL("./out", import.meta.url)) },
prerender: { prerender: {
crawlLinks: false, crawlLinks: false,
routes: ["/", "/robots.txt", "/sitemap.xml", "/manifest.webmanifest", NOT_FOUND_ROUTE], routes: [
"/",
"/robots.txt",
"/sitemap.xml",
"/manifest.webmanifest",
NOT_FOUND_ROUTE,
...categorySlugs.map((s) => `/categories/${s}`),
],
}, },
hooks: { hooks: {
// Nuxt's own /404.html is an empty SPA shell. Replace it with a server-rendered error.vue // Nuxt's own /404.html is an empty SPA shell. Replace it with a server-rendered error.vue
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+11 -2
View File
@@ -1,6 +1,15 @@
import { absUrl } from "../../shared/config/site"; import { absUrl } from "../../shared/config/site";
import { categorySlugs } from "../../shared/data/categories";
const pages = ["/"]; interface SitemapEntry {
loc: string;
priority: string;
}
const pages: SitemapEntry[] = [
{ loc: "/", priority: "1" },
...categorySlugs.map((s) => ({ loc: `/categories/${s}`, priority: "0.8" })),
];
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
setHeader(event, "Content-Type", "application/xml; charset=utf-8"); setHeader(event, "Content-Type", "application/xml; charset=utf-8");
@@ -8,7 +17,7 @@ export default defineEventHandler((event) => {
const urls = pages const urls = pages
.map( .map(
(p) => (p) =>
`<url>\n<loc>${absUrl(p)}</loc>\n<lastmod>${lastmod}</lastmod>\n<changefreq>monthly</changefreq>\n<priority>1</priority>\n</url>`, `<url>\n<loc>${absUrl(p.loc)}</loc>\n<lastmod>${lastmod}</lastmod>\n<changefreq>monthly</changefreq>\n<priority>${p.priority}</priority>\n</url>`,
) )
.join("\n"); .join("\n");
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls}\n</urlset>\n`; return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls}\n</urlset>\n`;
+6 -6
View File
@@ -1,13 +1,13 @@
export const siteConfig = { export const siteConfig = {
name: "آریا ایمن", name: "ایمن پیشگامان",
nameEn: "Aria Imen Safety", nameEn: "Imen Pishgaman Safety",
url: "https://ariaiman.ir", // TODO: replace with the real production domain before deploy url: "https://imenpishgaman.ir", // TODO: replace with the real production domain before deploy
locale: "fa_IR", locale: "fa_IR",
lang: "fa", lang: "fa",
dir: "rtl" as const, dir: "rtl" as const,
title: "آریا ایمن | تجهیزات ایمنی و حفاظت فردی صنعتی", title: "ایمن پیشگامان | تجهیزات ایمنی و حفاظت فردی صنعتی",
description: description:
"آریا ایمن — تأمین‌کننده تخصصی تجهیزات حفاظت فردی (PPE)، بازرسی، آموزش و راهکارهای ایمنی صنعتی برای سازمان‌هایی که جان انسان‌ها را جدی می‌گیرند.", "ایمن پیشگامان — تأمین‌کننده تخصصی تجهیزات حفاظت فردی (PPE)، بازرسی، آموزش و راهکارهای ایمنی صنعتی برای سازمان‌هایی که جان انسان‌ها را جدی می‌گیرند.",
// TODO: replace with a dedicated 1200×630 social image when available // TODO: replace with a dedicated 1200×630 social image when available
ogImage: { ogImage: {
src: "/images/hero.jpg", src: "/images/hero.jpg",
@@ -28,7 +28,7 @@ export const siteConfig = {
contact: { contact: {
phone: "+98-21-91008800", phone: "+98-21-91008800",
phoneDisplay: "۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰", phoneDisplay: "۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰",
email: "info@ariaiman.ir", email: "info@imenpishgaman.ir",
address: { address: {
streetAddress: "خیابان کارگر شمالی، خیابان فر، کوچه دوم، پلاک ۱۲", streetAddress: "خیابان کارگر شمالی، خیابان فر، کوچه دوم، پلاک ۱۲",
addressLocality: "تهران", addressLocality: "تهران",
+458
View File
@@ -0,0 +1,458 @@
export interface Product {
code: string;
name: string;
en: string;
desc: string;
specs: string[];
variants?: string[];
image?: string;
}
export interface Category {
slug: string;
code: string;
name: string;
en: string;
shortDesc: string;
fullDesc: string;
icon: string;
products: Product[];
}
export const CATEGORIES: readonly Category[] = [
{
slug: "workwear",
code: "IP-C01",
name: "پوشاک کار و البسه صنعتی",
en: "INDUSTRIAL WORKWEAR",
shortDesc:
"لباس‌کار صنعتی با پارچه فلامنت کجراه اعلا، دوخت سه سوزنه و آستر عرق‌گیر؛ مناسب محیط‌های کارگاهی و صنعتی.",
fullDesc:
"مجموعه کامل پوشاک کار صنعتی شامل کاپشن و شلوار مهندسی، لباس جوشکاری، پیش‌بند دوبنده و تی‌شرت کار. تمامی مدل‌ها با پارچه فلامنت کجراه اعلا و کتان مقاوم، دوخت صنعتی سه سوزنه پیوسته برای جلوگیری از شکافتگی درزها و آستر عرق‌گیر اختصاصی تولید می‌شوند. موجود در سایزهای M تا 4XL.",
icon: "Shirt",
products: [
{
code: "IP-W01",
name: "لباسکار مدل آلفا",
en: "ALPHA WORKWEAR",
desc: "کاپشن و شلوار مهندسی با جیب‌های پاکتی و زیپ‌دار، نوار مغزی تزئینی و طراحی ارگونومیک برای تیم‌های فنی و مهندسی.",
specs: ["فلامنت کجراه اعلا", "دوخت سه سوزنه", "آستر عرق‌گیر", "سایز M تا 4XL"],
variants: [
"طوسی/آبی",
"مشکی/قرمز",
"مشکی/زرد",
"کرم/سرمه‌ای",
],
},
{
code: "IP-W02",
name: "لباسکار مدل اورجینال",
en: "ORIGINAL WORKWEAR",
desc: "کاپشن و شلوار استاندارد با دوام بالا، مناسب کادر فنی کارخانجات و خطوط تولید با ترکیب‌های صنعتی دورنگ.",
specs: ["کتان مقاوم", "دوخت سه سوزنه", "آستر عرق‌گیر", "سایز M تا 4XL"],
variants: ["ترکیب‌های صنعتی دورنگ"],
},
{
code: "IP-W03",
name: "لباسکار مدل بوفالو",
en: "BUFFALO WORKWEAR",
desc: "طراحی اسپرت با برش‌های تقویتی روی شانه و بازو، زیپ مخفی محافظ‌دار؛ مناسب محیط‌های صنعتی فعال.",
specs: ["فلامنت کجراه اعلا", "زیپ مخفی محافظ‌دار", "برش تقویتی شانه", "سایز M تا 4XL"],
variants: [
"سرمه‌ای/زرد",
"طوسی/قرمز",
"طوسی/خردلی",
"آبی/مشکی",
],
},
{
code: "IP-W04",
name: "لباسکار ست ورک",
en: "SET WORK",
desc: "ست اداری/کارگاهی با انعطاف بالا جهت فعالیت‌های فنی و تعمیراتی؛ قابل استفاده در محیط‌های ستادی و عملیاتی.",
specs: ["کتان مقاوم", "دوخت سه سوزنه", "طراحی منعطف", "سایز M تا 4XL"],
variants: ["ترکیب‌های چندگانه کارگاهی"],
},
{
code: "IP-W05",
name: "لباسکار جوشکاری",
en: "WELDING WORKWEAR",
desc: "متریال سنگین کتان یا چرم جهت مقاومت در برابر پاشش جرقه‌های مذاب؛ طراحی‌شده برای جوشکاران صنعتی.",
specs: ["مقاوم به جرقه مذاب", "کتان سنگین / چرم", "دوخت صنعتی", "سایز M تا 4XL"],
},
{
code: "IP-W06",
name: "پیش‌بند دوبنده",
en: "BIB OVERALLS",
desc: "آزادی عمل کامل کتف و شانه با جیب‌های چندمنظوره ابزار بر روی پیش‌سینه؛ ایده‌آل برای کارهای تعمیراتی و مونتاژ.",
specs: ["آزادی حرکت شانه", "جیب ابزار پیش‌سینه", "دوخت سه سوزنه", "سایز M تا 4XL"],
variants: ["سرمه‌ای", "خاکستری"],
},
{
code: "IP-W07",
name: "تی‌شرت صنعتی",
en: "INDUSTRIAL T-SHIRT",
desc: "تی‌شرت یقه‌دار جودون / پنبه‌ای مناسب فصول گرم و زیرپوش کاپشن کارگاهی در سوله‌های سرپوشیده.",
specs: ["پنبه / جودون", "یقه‌دار", "مناسب فصول گرم", "سایز M تا 4XL"],
},
],
},
{
slug: "safety-footwear",
code: "IP-C02",
name: "پایپوش و کفش ایمنی",
en: "SAFETY FOOTWEAR",
shortDesc:
"کفش‌های ایمنی مهندسی، کارگری، اداری و زنانه به همراه چکمه صنعتی؛ مجهز به سرپنجه ایمنی و زیره ضد لغزش.",
fullDesc:
"مجموعه کامل پایپوش ایمنی شامل کفش‌های اداری، مهندسی، کارگری و زنانه به همراه چکمه‌های صنعتی. هر مدل متناسب با شرایط محیط کار طراحی شده و مجهز به سرپنجه ایمنی مقاوم، زیره تخصصی ضد لغزش و مواد مقاوم در برابر سایش و مواد شیمیایی.",
icon: "Footprints",
products: [
{
code: "IP-F01",
name: "کفش اداری ایمنی",
en: "OFFICE SAFETY SHOE",
desc: "طراحی شکیل و رسمی برای محیط‌های اداری و ستادی؛ وزن سبک، زیره مقاوم و راحتی در استفاده طولانی‌مدت.",
specs: ["طراحی رسمی", "وزن سبک", "زیره مقاوم", "راحتی طولانی‌مدت"],
},
{
code: "IP-F02",
name: "کفش ایمنی مهندسی",
en: "ENGINEERING SAFETY BOOT",
desc: "مجهز به سرپنجه ایمنی مقاوم و زیره تخصصی ضد لغزش؛ مناسب محیط‌های صنعتی و کارگاهی ناظران و مهندسان.",
specs: ["سرپنجه ایمنی", "Anti-Slip", "مقاوم به سایش", "زیره تخصصی"],
},
{
code: "IP-F03",
name: "کفش ایمنی کارگری",
en: "WORKER SAFETY BOOT",
desc: "مقاومت بالا در برابر سایش فیزیکی و تماس با مواد شیمیایی سبک؛ مناسب کارگاه‌های تولیدی، انبارها و پروژه‌های ساختمانی.",
specs: ["مقاوم به سایش", "مقاوم شیمیایی", "سرپنجه فولادی", "زیره ضد لغزش"],
},
{
code: "IP-F04",
name: "کفش ایمنی زنانه",
en: "WOMEN'S SAFETY SHOE",
desc: "قالب اختصاصی ارگونومیک برای بانوان شاغل در خطوط تولید و کارگاه‌ها؛ وزن سبک و مقاوم در برابر لغزش و سایش.",
specs: ["قالب ارگونومیک زنانه", "وزن سبک", "ضد لغزش", "مقاوم به سایش"],
},
{
code: "IP-F05",
name: "چکمه صنعتی",
en: "INDUSTRIAL BOOT",
desc: "کاملاً نفوذناپذیر در برابر آب و مواد شیمیایی؛ زیره آج‌دار عمیق برای محیط‌های مرطوب، آب‌روها و تماس با گل.",
specs: ["نفوذناپذیر", "مقاوم شیمیایی", "زیره آج‌دار عمیق", "ساق بلند"],
},
],
},
{
slug: "protective-gloves",
code: "IP-C03",
name: "دستکش‌های حفاظتی",
en: "PROTECTIVE GLOVES",
shortDesc:
"دستکش‌های ضد برش، چرم، ضد اسید، ضد حلال و آرگون از برندهای استادکار، گیلان، فاکس و کالکان.",
fullDesc:
"دستکش‌های حفاظت فردی شامل مدل‌های ضد برش، چرم گاوی، ضد اسید، ضد حلال و آرگون. تأمین از برندهای معتبر استادکار، گیلان، فاکس و کالکان. هر مدل متناسب با نوع خطر محیط کار طراحی شده و مشخصات فنی دقیقی دارد.",
icon: "Hand",
products: [
{
code: "IP-G01",
name: "دستکش ضد برش",
en: "CUT-RESISTANT GLOVE",
desc: "کف مواد نیتریل، لاتکس یا PU بر روی الیاف مقاوم؛ مقاومت برشی بالا، مناسب فلزکاری، برشکاری و صنایع سنگین. قابل شستشو و چندبار مصرف.",
specs: ["پوشش نیتریل / لاتکس / PU", "مقاومت برشی بالا", "قابل شستشو", "چندبار مصرف"],
variants: ["استادکار", "گیلان", "فاکس", "کالکان"],
},
{
code: "IP-G02",
name: "دستکش چرم",
en: "LEATHER GLOVE",
desc: "چرم گاوی یا اشپالت ضخیم با مقاومت مکانیکی بالا در برابر سایش، جرقه و اجسام تیز و زبر.",
specs: ["چرم گاوی / اشپالت", "مقاومت مکانیکی بالا", "مقاوم به جرقه", "مقاوم به سایش"],
},
{
code: "IP-G03",
name: "دستکش ضد اسید",
en: "ACID-RESISTANT GLOVE",
desc: "لایه‌های ضخیم PVC یا نئوپرن برای محافظت کامل دست و ساعد در برابر انواع اسیدها، بازها و قلیاها.",
specs: ["PVC / نئوپرن", "محافظت دست و ساعد", "مقاوم به اسید و باز", "ساق بلند"],
},
{
code: "IP-G04",
name: "دستکش ضد حلال",
en: "SOLVENT-RESISTANT GLOVE",
desc: "نیتریل خالص و لاستیک‌های مقاوم با مقاومت شیمیایی بالا در تماس با حلال‌ها، تینر و مشتقات نفتی.",
specs: ["نیتریل خالص", "مقاوم شیمیایی", "مقاوم به تینر", "مقاوم به مشتقات نفتی"],
},
{
code: "IP-G05",
name: "دستکش آرگون",
en: "ARGON WELDING GLOVE",
desc: "چرم سبک و لطیف بزی با ساق بلند؛ نرمی و حس لامسه بالا برای جوشکاری ظریف آرگون (TIG) و مقاومت حرارتی.",
specs: ["چرم بزی سبک", "ساق بلند", "حس لامسه بالا", "مقاوم حرارتی"],
},
],
},
{
slug: "safety-helmets",
code: "IP-C04",
name: "کلاه ایمنی",
en: "SAFETY HELMETS",
shortDesc:
"کلاه‌های مهندسی، کارگری، کار در ارتفاع، ضد حرارت، کپ و عایق برق با تنوع رنگی استاندارد HSE.",
fullDesc:
"انواع کلاه ایمنی صنعتی شامل مدل‌های مهندسی، کارگری، کار در ارتفاع، ضد حرارت، کپ ضربه‌گیر نقاب‌دار و عایق برق. با پوشش تنوع رنگی استاندارد HSE (سفید، زرد، آبی، قرمز، سبز)، یراق و رگلاژ داخلی قابل تنظیم و پوسته ضربه‌گیر مقاوم.",
icon: "HardHat",
products: [
{
code: "IP-H01",
name: "کلاه ایمنی مهندسی",
en: "ENGINEERING HELMET",
desc: "کلاه ایمنی مهندسی با پوسته HDPE مقاوم به ضربه، رگلاژ داخلی قابل تنظیم و قابلیت نصب هدفون و شیلد صورت.",
specs: ["پوسته HDPE", "رگلاژ قابل تنظیم", "قابلیت نصب لوازم جانبی", "رنگ‌بندی HSE"],
variants: ["سفید", "زرد", "آبی", "قرمز", "سبز"],
},
{
code: "IP-H02",
name: "کلاه ایمنی کارگری",
en: "WORKER HELMET",
desc: "کلاه ایمنی استاندارد برای خطوط تولید و محیط‌های کارگاهی؛ سبک و مقاوم با تهویه مناسب.",
specs: ["سبک و مقاوم", "تهویه مناسب", "تسمه چانه", "پوسته ضربه‌گیر"],
variants: ["سفید", "زرد", "آبی", "قرمز"],
},
{
code: "IP-H03",
name: "کلاه ایمنی ارتفاع",
en: "HEIGHT SAFETY HELMET",
desc: "کلاه ایمنی ویژه کار در ارتفاع با تسمه چانه‌بند مستحکم و سیستم نگهداری چندنقطه‌ای برای داربست‌بندی و پروژه‌های عمرانی.",
specs: ["چانه‌بند مستحکم", "نگهداری چندنقطه‌ای", "مقاوم به سقوط اجسام", "سبک‌وزن"],
},
{
code: "IP-H04",
name: "کلاه ضد حرارت",
en: "HEAT-RESISTANT HELMET",
desc: "مقاوم در برابر دماهای بالا و پاشش مواد مذاب؛ مناسب محیط‌های ذوب، ریخته‌گری و کوره‌های صنعتی.",
specs: ["مقاوم حرارتی", "مقاوم به مواد مذاب", "پوسته تقویت‌شده", "عایق حرارتی"],
},
{
code: "IP-H05",
name: "کلاه کپ ایمنی",
en: "SAFETY CAP",
desc: "کلاه ایمنی نقاب‌دار ضربه‌گیر با ظاهر شبیه کلاه معمولی؛ مناسب محیط‌هایی با ریسک برخورد سر ولی نیاز به ظاهر رسمی.",
specs: ["نقاب‌دار", "ضربه‌گیر داخلی", "ظاهر رسمی", "سبک‌وزن"],
},
{
code: "IP-H06",
name: "کلاه عایق برق",
en: "ELECTRICALLY INSULATED HELMET",
desc: "عایق در برابر جریان الکتریکی، مناسب تأسیسات برق فشار قوی و کار با تجهیزات الکتریکی صنعتی.",
specs: ["عایق الکتریکی", "فشار قوی", "پوسته ABS / HDPE", "رگلاژ قابل تنظیم"],
},
],
},
{
slug: "eye-protection",
code: "IP-C05",
name: "عینک ایمنی و صنعتی",
en: "EYE PROTECTION",
shortDesc:
"عینک‌های مهندسی فریم‌دار، گاگل‌های محافظتی و شیلدهای صورت با ویژگی‌های ضد ضربه، ضد خش و UV.",
fullDesc:
"عینک‌های ایمنی صنعتی شامل مدل‌های مهندسی فریم‌دار، گاگل‌های محافظتی کش‌دار کارگاهی و آزمایشگاهی و شیلدهای دودی و شفاف. مجهز به ویژگی‌های ضد ضربه، ضد خش (Anti-Scratch)، ضد بخار (Anti-Fog) و فیلتر اشعه UV با قابلیت تنظیم فریم و دسته‌ها.",
icon: "Glasses",
products: [
{
code: "IP-E01",
name: "عینک مهندسی فریم‌دار",
en: "FRAMED SAFETY GLASSES",
desc: "عینک ایمنی فریم‌دار با لنز ضد ضربه و پوشش UV400؛ سازگار با عینک طبی و قابلیت تنظیم فریم و دسته‌ها.",
specs: ["ضد ضربه", "Anti-Scratch", "UV400", "قابل تنظیم"],
},
{
code: "IP-E02",
name: "گاگل محافظتی",
en: "PROTECTIVE GOGGLES",
desc: "گاگل محافظتی کش‌دار با تهویه ضد بخار؛ مناسب سنگ‌زنی، تراشکاری و محیط‌های شیمیایی و آزمایشگاهی.",
specs: ["Anti-Fog", "ضد ضربه", "تهویه مناسب", "کش‌دار ارگونومیک"],
},
{
code: "IP-E03",
name: "شیلد محافظ صورت",
en: "FACE SHIELD",
desc: "شیلد محافظ تمام صورت با لنز شفاف یا دودی؛ محافظت کامل صورت در برابر پاشش مواد، براده و ذرات.",
specs: ["محافظت تمام صورت", "لنز شفاف / دودی", "ضد ضربه", "سبک‌وزن"],
},
],
},
{
slug: "hearing-protection",
code: "IP-C06",
name: "حفاظت شنوایی",
en: "HEARING PROTECTION",
shortDesc:
"ایرماف روگوشی با کاهش ۲۸ دسی‌بل و ایرپلاگ‌های اسفنجی و سیلیکونی برای محیط‌های پرسروصدا.",
fullDesc:
"تجهیزات حفاظت شنوایی شامل ایرماف (محافظ روگوشی) با کاهش تراز صدا تا ۲۸ دسی‌بل و ایرپلاگ (داخل‌گوشی) در مدل‌های اسفنجی فرم‌گیر و پله‌ای سیلیکونی. مناسب محیط‌های با آلودگی صوتی بالای ۸۵ دسی‌بل، کمپرسورخانه‌ها و خطوط تولید سنگین.",
icon: "Ear",
products: [
{
code: "IP-HP01",
name: "ایرماف محافظ",
en: "PROTECTIVE EARMUFF",
desc: "محافظ روگوشی با کاهش تراز صدا تا ۲۸ دسی‌بل؛ هدبند قابل تنظیم، سبک و بالشتک‌های نرم جهت شیفت‌های کاری طولانی.",
specs: ["کاهش ۲۸ دسی‌بل", "هدبند قابل تنظیم", "بالشتک نرم", "سبک‌وزن"],
},
{
code: "IP-HP02",
name: "ایرپلاگ",
en: "EARPLUG",
desc: "محافظت در برابر نویزهای تیز محیطی؛ مدل‌های اسفنجی فرم‌گیر و پله‌ای سیلیکونی بنددار، یکبار مصرف و چندبار مصرف.",
specs: ["اسفنجی فرم‌گیر", "سیلیکونی پله‌ای", "بنددار", "یکبار / چندبار مصرف"],
},
],
},
{
slug: "respiratory-protection",
code: "IP-C07",
name: "حفاظت تنفسی",
en: "RESPIRATORY PROTECTION",
shortDesc:
"ماسک‌های نیم‌صورت، تمام‌صورت، شیمیایی، P2/P3 و فیلتردار برای محافظت در برابر گازها و ذرات.",
fullDesc:
"تجهیزات حفاظت تنفسی شامل ماسک‌های نیم‌صورت، تمام‌صورت، شیمیایی با کارتریج تخصصی، ماسک‌های فیلتردار P2/P3 برای ذرات میکرونی، ماسک‌های با فیلتر تعویضی اقتصادی و ماسک‌های سه‌لایه بهداشتی. مناسب کار با حلال‌های فرار، غبارهای معدنی، رنگ‌پاشی و خطوط شیمیایی.",
icon: "ShieldPlus",
products: [
{
code: "IP-R01",
name: "ماسک نیم‌صورت",
en: "HALF-FACE RESPIRATOR",
desc: "مناسب فضاهای صنعتی با آلودگی و گازهای با غلظت متوسط؛ سبک و راحت برای استفاده طولانی‌مدت.",
specs: ["پوشش بینی و دهان", "فیلتر قابل تعویض", "بند قابل تنظیم", "سبک‌وزن"],
},
{
code: "IP-R02",
name: "ماسک تمام‌صورت",
en: "FULL-FACE RESPIRATOR",
desc: "پوشش همزمان چشم و تنفس جهت کار با مواد سمی و بخارات خورنده پرخطر؛ شیشه ضد ضربه و ضد بخار.",
specs: ["محافظت چشم و تنفس", "شیشه ضد ضربه", "Anti-Fog", "فیلتر چندمنظوره"],
},
{
code: "IP-R03",
name: "ماسک شیمیایی",
en: "CHEMICAL RESPIRATOR",
desc: "مجهز به کارتریج‌های تخصصی فیلتراسیون بخارات آلی و گازهای اسیدی؛ مناسب صنایع شیمیایی و پتروشیمی.",
specs: ["کارتریج تخصصی", "فیلتر بخارات آلی", "فیلتر گازهای اسیدی", "آب‌بندی کامل"],
},
{
code: "IP-R04",
name: "ماسک فیلتردار P2/P3",
en: "P2/P3 PARTICULATE MASK",
desc: "محافظت اختصاصی در برابر ذرات میکرونی، گردوغبار و میست‌های مایع؛ مناسب معادن و محیط‌های پرگردوغبار.",
specs: ["فیلتر P2 / P3", "ذرات میکرونی", "گردوغبار و میست", "سوپاپ بازدمی"],
},
{
code: "IP-R05",
name: "ماسک با فیلتر تعویضی",
en: "REPLACEABLE FILTER MASK",
desc: "قابلیت تعویض مدیا و استفاده اقتصادی طولانی‌مدت؛ مناسب مصارف روزمره صنعتی.",
specs: ["فیلتر تعویضی", "اقتصادی", "استفاده طولانی‌مدت", "آب‌بندی مطمئن"],
},
{
code: "IP-R06",
name: "ماسک سه‌لایه بهداشتی",
en: "THREE-LAYER MASK",
desc: "مناسب مراجعات روزمره و فضاهای عمومی با غلظت پایین آلودگی؛ سبک و یکبار مصرف.",
specs: ["سه‌لایه", "یکبار مصرف", "سبک‌وزن", "بهداشتی"],
},
],
},
{
slug: "traffic-equipment",
code: "IP-C08",
name: "تجهیزات ترافیکی و ایمنی محیطی",
en: "TRAFFIC & SITE SAFETY",
shortDesc:
"مخروط، نیوجرسی، استوانه، سرعت‌گیر، آینه محدب و سایر تجهیزات ایمن‌سازی محوطه صنعتی.",
fullDesc:
"تجهیزات ترافیکی و ایمنی محیطی شامل مخروط ترافیکی، نیوجرسی پلاستیکی، استوانه و بولارد، چراغ و تابلوهای هشداردهنده، باتوم ترافیکی، نوارهای خطر، سرعت‌گیر لاستیکی، آینه‌های محدب، محافظ گوشه ستون و گل‌میخ خورشیدی. مناسب ایمن‌سازی محوطه کارخانه، انبارها، رمپ‌ها و پارکینگ‌ها.",
icon: "TriangleAlert",
products: [
{
code: "IP-T01",
name: "مخروط ترافیکی",
en: "TRAFFIC CONE",
desc: "PVC انعطاف‌پذیر با شبرنگ‌های استاندارد روز و شب؛ مقاوم در برابر واژگونی و ضربه خودرو.",
specs: ["PVC انعطاف‌پذیر", "شبرنگ استاندارد", "مقاوم به واژگونی", "قابل رؤیت شبانه"],
},
{
code: "IP-T02",
name: "نیوجرسی پلاستیکی",
en: "NEW JERSEY BARRIER",
desc: "بدنه پلی‌اتیلنی مدولار قابل اتصال با قابلیت پرشوندگی با آب یا ماسه جهت تثبیت و ایجاد حائل.",
specs: ["پلی‌اتیلن مدولار", "قابل اتصال", "پرشدنی با آب/ماسه", "مقاوم UV"],
},
{
code: "IP-T03",
name: "استوانه ترافیکی",
en: "TRAFFIC BOLLARD",
desc: "پلی‌اورتان ارتجاعی انعطاف‌پذیر پس از ضربه؛ مجهز به حلقه‌های شبرنگ هشدار برای تفکیک مسیر.",
specs: ["پلی‌اورتان ارتجاعی", "بازگشت پس از ضربه", "شبرنگ هشدار", "نصب آسان"],
},
{
code: "IP-T04",
name: "چراغ و تابلوی هشدار",
en: "WARNING LIGHT & SIGN",
desc: "چراغ‌های چشمک‌زن سولار، تابلوی دستی ایست/حرکت و تابلوهای علائم هشداردهنده ترافیکی.",
specs: ["سولار / باتری", "LED چشمک‌زن", "تابلوی Stop/Go", "علائم استاندارد"],
},
{
code: "IP-T05",
name: "باتوم ترافیکی",
en: "TRAFFIC BATON",
desc: "باتوم LED دار دستی جهت هدایت ترافیک در شب و محیط‌های کم‌نور؛ سبک و ارگونومیک.",
specs: ["LED داخلی", "قابل رؤیت شبانه", "سبک‌وزن", "ارگونومیک"],
},
{
code: "IP-T06",
name: "نوار خطر",
en: "CAUTION TAPE",
desc: "نوار شطرنجی و خط‌کشی زرد/قرمز هشدار محوطه گودبرداری و مناطق خطرناک با رول‌های بازتابنده نور.",
specs: ["زرد / قرمز هشدار", "بازتابنده نور", "رول بلند", "مقاوم به پارگی"],
},
{
code: "IP-T07",
name: "سرعت‌گیر لاستیکی",
en: "RUBBER SPEED BUMP",
desc: "قطعات لاستیکی سنگین مدولار با مقاومت وزنی بالا و المان‌های بازتابش شبانه؛ نصب آسان.",
specs: ["لاستیکی مدولار", "مقاومت وزنی بالا", "بازتاب شبانه", "نصب پیچی"],
},
{
code: "IP-T08",
name: "آینه محدب ترافیکی",
en: "CONVEX TRAFFIC MIRROR",
desc: "آینه نشکن فریم‌دار و کلاه‌دار جهت پوشش نقاط کور پیچ‌ها، تقاطع‌ها و درب انبار.",
specs: ["نشکن", "فریم‌دار و کلاه‌دار", "پوشش نقاط کور", "مقاوم به آب و باد"],
},
{
code: "IP-T09",
name: "محافظ گوشه ستون",
en: "CORNER GUARD",
desc: "ضربه‌گیر لاستیکی شبرنگ‌دار جهت نصب روی ستون‌ها برای ممانعت از آسیب لیفتراک و خودروهای سنگین.",
specs: ["لاستیکی ضربه‌گیر", "شبرنگ‌دار", "نصب دیواری", "محافظ ستون"],
},
{
code: "IP-T10",
name: "گل‌میخ ترافیکی خورشیدی",
en: "SOLAR ROAD STUD",
desc: "بدنه آلومینیومی مستحکم مجهز به پنل خورشیدی و LED جهت تعیین مسیر و خط‌کشی شبانه.",
specs: ["آلومینیوم مستحکم", "پنل خورشیدی", "LED", "مقاوم به عبور خودرو"],
},
],
},
] as const;
export const findCategory = (slug: string): Category | undefined =>
CATEGORIES.find((c) => c.slug === slug);
export const categorySlugs: readonly string[] = CATEGORIES.map((c) => c.slug);
+31
View File
@@ -63,6 +63,37 @@ export const serviceSchema = (opts: {
provider: { "@id": `${s.url}/#organization` }, provider: { "@id": `${s.url}/#organization` },
}); });
export const categoryPageSchema = (category: {
name: string;
en: string;
fullDesc: string;
slug: string;
products: { name: string; desc: string }[];
}) =>
clean({
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": `${s.url}/categories/${category.slug}`,
name: category.name,
description: category.fullDesc,
url: abs(`/categories/${category.slug}`),
isPartOf: { "@id": `${s.url}/#website` },
provider: { "@id": `${s.url}/#organization` },
mainEntity: {
"@type": "ItemList",
numberOfItems: category.products.length,
itemListElement: category.products.map((p, i) => ({
"@type": "ListItem",
position: i + 1,
item: {
"@type": "Product",
name: p.name,
description: p.desc,
},
})),
},
});
/** Serialize JSON-LD safely for an inline <script> (escape "<"). */ /** Serialize JSON-LD safely for an inline <script> (escape "<"). */
export const toJsonLd = (data: Json) => export const toJsonLd = (data: Json) =>
JSON.stringify(data).replace(/</g, "\\u003c"); JSON.stringify(data).replace(/</g, "\\u003c");