feat: integrate Cloudinary, add new fleet items and image gallery
This commit is contained in:
140
components/FleetGallery.tsx
Normal file
140
components/FleetGallery.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
|
||||
interface FleetGalleryProps {
|
||||
images: string[];
|
||||
}
|
||||
|
||||
export function FleetGallery({ images }: FleetGalleryProps) {
|
||||
const [selectedImage, setSelectedImage] = useState<number | null>(null);
|
||||
|
||||
const nextImage = () => {
|
||||
if (selectedImage !== null) {
|
||||
setSelectedImage((selectedImage + 1) % images.length);
|
||||
}
|
||||
};
|
||||
|
||||
const prevImage = () => {
|
||||
if (selectedImage !== null) {
|
||||
setSelectedImage((selectedImage - 1 + images.length) % images.length);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="py-24 bg-background px-6 md:px-12">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="mb-12"
|
||||
>
|
||||
<span className="text-primary font-headline font-bold text-xs tracking-[0.3em] uppercase mb-2 block">
|
||||
SAHADAN KARELER
|
||||
</span>
|
||||
<h2 className="text-4xl md:text-5xl font-black text-on-surface uppercase tracking-tighter leading-none">
|
||||
FİLOMUZ <span className="text-primary">AKSİYONDA</span>
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{images.map((src, index) => (
|
||||
<motion.div
|
||||
key={src}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
whileHover={{ scale: 0.98 }}
|
||||
className="relative aspect-square cursor-pointer overflow-hidden group bg-surface-container-low"
|
||||
onClick={() => setSelectedImage(index)}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
alt={`Fleet image ${index + 1}`}
|
||||
fill
|
||||
sizes="(max-width: 768px) 50vw, 25vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-110 grayscale group-hover:grayscale-0"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||
<div className="w-12 h-12 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center border border-white/20">
|
||||
<div className="w-1 h-4 bg-white rounded-full"></div>
|
||||
<div className="w-4 h-1 bg-white rounded-full absolute"></div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{selectedImage !== null && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/95 backdrop-blur-sm p-4 md:p-12"
|
||||
onClick={() => setSelectedImage(null)}
|
||||
>
|
||||
<motion.button
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="absolute top-8 right-8 text-white hover:text-primary transition-colors z-[110]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedImage(null);
|
||||
}}
|
||||
>
|
||||
<X size={40} strokeWidth={1} />
|
||||
</motion.button>
|
||||
|
||||
<button
|
||||
className="absolute left-4 md:left-8 text-white hover:text-primary transition-colors z-[110]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
prevImage();
|
||||
}}
|
||||
>
|
||||
<ChevronLeft size={60} strokeWidth={1} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="absolute right-4 md:right-8 text-white hover:text-primary transition-colors z-[110]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
nextImage();
|
||||
}}
|
||||
>
|
||||
<ChevronRight size={60} strokeWidth={1} />
|
||||
</button>
|
||||
|
||||
<motion.div
|
||||
layoutId={images[selectedImage]}
|
||||
className="relative w-full h-full flex items-center justify-center"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="relative w-full h-full max-w-5xl max-h-[80vh]">
|
||||
<Image
|
||||
src={images[selectedImage]}
|
||||
alt="Full size fleet image"
|
||||
fill
|
||||
className="object-contain"
|
||||
sizes="100vw"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 text-white/50 font-label text-xs tracking-widest uppercase">
|
||||
{selectedImage + 1} / {images.length}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Phone, MessageCircle } from "lucide-react";
|
||||
import { siteConfig } from "@/lib/data";
|
||||
import { cloudinaryUrl } from "@/lib/cloudinary";
|
||||
|
||||
export function Hero() {
|
||||
const handleWhatsApp = () => {
|
||||
@@ -18,7 +19,7 @@ export function Hero() {
|
||||
<div className="absolute inset-0 z-0">
|
||||
<div className="absolute inset-0 bg-background/40 backdrop-blur-sm z-10" />
|
||||
<Image
|
||||
src="/images/Vinç hizmetleri/Ekran görüntüsü 2026-04-16 005221.png"
|
||||
src={cloudinaryUrl("/images/Vinç hizmetleri/Ekran görüntüsü 2026-04-16 005221.png")}
|
||||
alt="Industrial crane"
|
||||
fill
|
||||
priority
|
||||
|
||||
@@ -4,22 +4,23 @@ import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { cloudinaryUrl } from "@/lib/cloudinary";
|
||||
|
||||
const SERVICES = [
|
||||
{
|
||||
title: "Ağır Nakliyat",
|
||||
description: "Gabari dışı yüklerin özel low-bed araçlarla taşınması.",
|
||||
image: "/images/Nakliyat-Taşımacılık/Ekran görüntüsü 2026-04-16 005533.png"
|
||||
image: cloudinaryUrl("/images/Nakliyat-Taşımacılık/Ekran görüntüsü 2026-04-16 005533.png")
|
||||
},
|
||||
{
|
||||
title: "Mobil Vinç",
|
||||
description: "Yüksek tonajlı her türlü yük için teleskopik vinç çözümleri.",
|
||||
image: "/images/Vinç hizmetleri/Ekran görüntüsü 2026-04-16 005221.png"
|
||||
image: cloudinaryUrl("/images/Vinç hizmetleri/Ekran görüntüsü 2026-04-16 005221.png")
|
||||
},
|
||||
{
|
||||
title: "Sepetli Platform",
|
||||
description: "75 metreye kadar yüksek irtifa çalışma alanları.",
|
||||
image: "/images/Sepetli platform hizmetleri/Ekran görüntüsü 2026-04-16 005332.png"
|
||||
image: cloudinaryUrl("/images/Sepetli platform hizmetleri/Ekran görüntüsü 2026-04-16 005332.png")
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user