feat: integrate Cloudinary, add new fleet items and image gallery

This commit is contained in:
2026-04-16 20:52:12 +03:00
parent bac925b5bc
commit 0efef41f5d
94 changed files with 494 additions and 19 deletions

140
components/FleetGallery.tsx Normal file
View 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>
);
}

View File

@@ -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

View File

@@ -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")
}
];