Files
imen_pishghaman/app/components/sections/Products.vue
T
s.esmaeiliandClaude Opus 4.6 bb7f1e3378 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>
2026-10-07 00:45:51 +03:30

182 lines
7.0 KiB
Vue

<script setup lang="ts">
import { ArrowLeft, BadgeCheck, PackageCheck, ShieldCheck } from "lucide-vue-next";
import { CATEGORIES } from "#shared/data/categories";
import Reveal from "~/components/ui/Reveal.vue";
import SectionHead from "~/components/ui/SectionHead.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 TRUST = [
{
icon: BadgeCheck,
title: "گارانتی اصالت کالا",
desc: "تمام محصولات با فاکتور رسمی و کد رهگیری اصالت",
},
{
icon: PackageCheck,
title: "تست نمونه قبل از خرید",
desc: "نمونه‌های نمایشی برای ارزیابی تیم HSE شما",
},
{
icon: ShieldCheck,
title: "امکان قرارداد سالانه",
desc: "قیمت ثابت و تأمین مستمر برای سازمان‌های بزرگ",
},
];
const active = ref(0);
</script>
<template>
<section id="products" aria-labelledby="products-title" class="relative py-24 md:py-32">
<div class="mx-auto max-w-[1480px] px-5 md:px-8">
<div class="flex flex-wrap items-end justify-between gap-6">
<SectionHead
heading-id="products-title"
tag="۰۱ — محصولات"
desc="هر محصول پیش از عرضه، زیر ذره‌بین کنترل کیفیت و تطبیق با استانداردهای جهانی قرار می‌گیرد."
>
<template #title>
خط اول دفاع،
<br />
تجهیزات ماست
</template>
</SectionHead>
<Reveal :delay="0.2" class="hidden md:block">
<a
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"
>
دریافت کاتالوگ کامل
<ArrowLeft class="size-4 transition-transform group-hover:-translate-x-1" />
</a>
</Reveal>
</div>
</div>
<!-- ---- mobile: card grid ---- -->
<div class="mx-auto mt-14 max-w-[1480px] px-5 md:hidden">
<div class="grid grid-cols-2 gap-3">
<Reveal v-for="(cat, i) in CATEGORIES" :key="cat.code" :delay="i * 0.05">
<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
: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
? 'flex-[3.4] border-accent/60'
: 'flex-[1] border-line hover:border-ink/30'
}`"
@mouseenter="active = i"
>
<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-all duration-700 ${
active === i ? 'scale-100 grayscale-0' : 'scale-110 grayscale group-hover:grayscale-0'
}`"
/>
<div
v-else
class="absolute inset-0 bg-panel"
/>
<div
:class="`absolute inset-0 transition-opacity duration-700 ${
active === i
? 'bg-gradient-to-t from-bg via-bg/35 to-bg/5'
: 'bg-gradient-to-t from-bg/95 via-bg/30 to-transparent'
}`"
/>
<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">{{ cat.code }}</span>
<span
:class="`font-mono text-xs transition-colors duration-500 ${
active === i ? 'text-accent' : 'text-ink/40'
}`"
>
{{ faNum(String(i + 1).padStart(2, "0")) }}
</span>
</div>
<div class="absolute inset-x-0 bottom-0 p-6">
<h3
:class="`font-black transition-all duration-500 ${
active === i ? 'text-2xl' : 'text-sm'
}`"
>
{{ cat.name }}
</h3>
<p class="mt-1 font-mono text-[10px] tracking-[0.2em] text-muted">{{ cat.en }}</p>
</div>
</NuxtLink>
</div>
</div>
<div
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">
<div class="flex h-full items-center gap-4 bg-panel/60 p-6">
<span
class="flex size-12 shrink-0 items-center justify-center rounded-xl bg-accent/10 text-accent"
>
<component :is="t.icon" class="size-6" />
</span>
<div>
<div class="font-bold">{{ t.title }}</div>
<div class="mt-1 text-xs leading-6 text-muted">{{ t.desc }}</div>
</div>
</div>
</Reveal>
</div>
</section>
</template>