Files
imen_pishghaman/app/components/sections/Industries.vue
T
S. EsmaeiliandClaude Opus 4.6 e31ef5a21f feat: migrate landing from Next.js to Nuxt 4 (Vue 3)
Full 1:1 migration preserving all visuals, animations and behavior:
- Nuxt 4 + Vue 3 (script setup, TypeScript strict) with SSG output
- Tailwind CSS v4 with @tailwindcss/vite
- Ant Design Vue 4 (dark theme, RTL, Persian locale) for design system
- Pinia for state management (ui + contact stores)
- motion-v (framer-motion port) for all animations
- lucide-vue-next for icons
- SEO improvements: og:image, canonical fix, real 404 page, JSON-LD services
- JS bundle reduced from 302KB to 239KB gzip
- Docker/nginx deployment preserved (out/ -> nginx)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-06 16:28:05 +03:30

138 lines
5.0 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { AnimatePresence, motion, useMotionValue, useSpring } from "motion-v";
import { ArrowLeft } from "lucide-vue-next";
import SectionHead from "~/components/ui/SectionHead.vue";
const INDUSTRIES = [
{ name: "نفت و گاز", en: "OIL & GAS", count: "۱۴۰+", img: "/images/industries/oil-gas.jpg" },
{
name: "ساختمان و عمران",
en: "CONSTRUCTION",
count: "۲۱۰+",
img: "/images/industries/construction.jpg",
},
{
name: "پتروشیمی",
en: "PETROCHEMICAL",
count: "۹۰+",
img: "/images/industries/petrochemical.jpg",
},
{ name: "صنایع معدنی", en: "MINING", count: "۷۵+", img: "/images/industries/mining.jpg" },
{
name: "برق و انرژی",
en: "POWER & ENERGY",
count: "۶۰+",
img: "/images/industries/power-energy.jpg",
},
{
name: "حمل‌ونقل و لجستیک",
en: "TRANSPORT",
count: "۴۵+",
img: "/images/industries/transport.jpg",
},
];
const hovered = ref<number | null>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const sx = useSpring(x, { stiffness: 220, damping: 26, mass: 0.6 });
const sy = useSpring(y, { stiffness: 220, damping: 26, mass: 0.6 });
const onMove = (e: MouseEvent) => {
x.set(e.clientX);
y.set(e.clientY);
};
</script>
<template>
<section
id="industries"
aria-labelledby="industries-title"
class="relative border-t border-line py-24 md:py-32"
@mousemove="onMove"
>
<div class="mx-auto max-w-7xl px-5 md:px-8">
<SectionHead
heading-id="industries-title"
tag="۰۵ — صنایع"
desc="جایی که دمای محیط از ۵۰ درجه گذر می‌کند یا ارتفاع کار به ده‌ها متر می‌رسد، تجهیزات ما باید بی‌نقص عمل کنند — و عمل می‌کنند."
>
<template #title>
حضور در سخت‌ترین
<br />
میدان‌های کاری
</template>
</SectionHead>
<div class="mt-16" @mouseleave="hovered = null">
<motion.div
v-for="(ind, i) in INDUSTRIES"
:key="ind.name"
v-bind="fade(i * 0.05)"
data-hover
:class="`group relative flex cursor-pointer items-center justify-between gap-4 border-t border-line px-2 py-7 transition-colors duration-300 hover:bg-panel/40 md:px-4 md:py-9 ${
i === INDUSTRIES.length - 1 ? 'border-b' : ''
}`"
@mouseenter="hovered = i"
>
<div class="flex min-w-0 items-baseline gap-5 md:gap-8">
<span class="shrink-0 font-mono text-xs text-muted">
{{ faNum(String(i + 1).padStart(2, "0")) }}
</span>
<h3
class="truncate text-2xl font-black transition-all duration-300 group-hover:-translate-x-2 group-hover:text-accent md:text-5xl"
>
{{ ind.name }}
</h3>
<span
class="hidden shrink-0 font-mono text-[10px] tracking-[0.3em] text-muted/60 md:inline"
>
{{ ind.en }}
</span>
</div>
<div class="flex shrink-0 items-center gap-4 md:gap-8">
<span class="text-xs text-muted md:text-sm">{{ ind.count }} پروژه</span>
<span
class="flex size-10 items-center justify-center rounded-full border border-line text-muted transition-all duration-300 group-hover:-translate-x-1.5 group-hover:border-accent group-hover:text-accent md:size-12"
>
<ArrowLeft class="size-4 md:size-5" />
</span>
</div>
</motion.div>
</div>
</div>
<!-- ---- floating preview (desktop) ---- -->
<motion.div
:style="{ x: sx, y: sy }"
class="pointer-events-none fixed left-0 top-0 z-40 hidden lg:block"
>
<AnimatePresence>
<motion.div
v-if="hovered !== null"
:key="hovered"
:initial="{ opacity: 0, scale: 0.82, rotate: -3 }"
:animate="{ opacity: 1, scale: 1, rotate: 0 }"
:exit="{ opacity: 0, scale: 0.9, rotate: 2 }"
:transition="{ duration: 0.35, ease: EASE }"
class="relative -ml-40 -mt-28 h-56 w-80 overflow-hidden rounded-xl border border-accent/40 shadow-[0_25px_80px_rgba(0,0,0,0.6)]"
>
<img
:src="INDUSTRIES[hovered]!.img"
:alt="INDUSTRIES[hovered]!.name"
decoding="async"
class="absolute inset-0 size-full object-cover grayscale-[35%] transition-all duration-500"
/>
<div class="absolute inset-0 bg-accent/15 mix-blend-multiply" />
<div class="absolute inset-0 bg-gradient-to-t from-bg/70 to-transparent" />
<div
class="absolute bottom-3 start-3 rounded-md border border-line bg-bg/80 px-3 py-1.5 font-mono text-[10px] tracking-widest text-ink backdrop-blur"
>
{{ INDUSTRIES[hovered]!.en }}
</div>
</motion.div>
</AnimatePresence>
</motion.div>
</section>
</template>