Initial commit: Salmakis Yachting Portal with Cloudinary & i18n

This commit is contained in:
2026-04-14 12:34:19 +03:00
parent e6784f8056
commit 8b1bdfd3c6
99 changed files with 4118 additions and 115 deletions

View File

@@ -0,0 +1,481 @@
'use client';
import { yachts } from "../../../data/yachts";
import { notFound } from "next/navigation";
import { Link } from "@/i18n/routing";
import { motion } from "framer-motion";
import { use, useState, useCallback, useEffect } from "react";
import { AnimatePresence } from "framer-motion";
import { CldImage } from "next-cloudinary";
import { useTranslations, useLocale } from "next-intl";
interface PageProps {
params: Promise<{ slug: string }>;
}
export default function YachtPage({ params }: PageProps) {
const { slug } = use(params);
const yacht = yachts.find((y) => y.slug === slug);
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
const t = useTranslations('FleetDetail');
const locale = useLocale();
const openLightbox = (index: number) => setLightboxIndex(index);
const closeLightbox = () => setLightboxIndex(null);
const goNext = useCallback(() => {
if (lightboxIndex !== null && yacht) {
setLightboxIndex((lightboxIndex + 1) % yacht.gallery.length);
}
}, [lightboxIndex, yacht]);
const goPrev = useCallback(() => {
if (lightboxIndex !== null && yacht) {
setLightboxIndex((lightboxIndex - 1 + yacht.gallery.length) % yacht.gallery.length);
}
}, [lightboxIndex, yacht]);
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (lightboxIndex === null) return;
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowRight') goNext();
if (e.key === 'ArrowLeft') goPrev();
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [lightboxIndex, goNext, goPrev]);
if (!yacht) {
notFound();
}
const fadeInUp = {
hidden: { opacity: 0, y: 40 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } }
};
const staggerContainer = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.15 } }
};
return (
<div className="bg-surface">
{/* Editorial Hero Section */}
<section className="relative h-[90vh] md:h-screen w-full overflow-hidden">
<motion.div
initial={{ scale: 1.1, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut" }}
className="absolute inset-0 z-0"
>
<CldImage
src={yacht.heroImage}
alt={yacht.name}
fill
crop="fill"
gravity="auto"
className="object-cover grayscale-[10%]"
priority
/>
</motion.div>
<div className="absolute inset-0 bg-gradient-to-t from-primary/90 via-primary/20 to-transparent z-10 flex flex-col items-center justify-center text-center px-6">
<motion.div
initial="hidden"
animate="visible"
variants={staggerContainer}
className="flex flex-col items-center"
>
<motion.span variants={fadeInUp} className="font-label text-[10px] md:text-xs tracking-[0.5em] text-secondary uppercase mb-6">
The Fleet Collection
</motion.span>
<motion.h1 variants={fadeInUp} className="text-white font-headline text-5xl md:text-8xl lg:text-9xl mb-4 uppercase tracking-tighter">
{yacht.name}
</motion.h1>
<motion.p variants={fadeInUp} className="text-secondary font-headline text-lg md:text-2xl italic tracking-wide">
{yacht.tagline}
</motion.p>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1, duration: 1 }}
className="absolute bottom-12 left-1/2 -translate-x-1/2 flex flex-col items-center"
>
<span className="text-white/40 text-[9px] md:text-[10px] tracking-[0.5em] uppercase mb-4">Explore Specifications</span>
<div className="w-px h-12 md:h-16 bg-gradient-to-b from-secondary to-transparent"></div>
</motion.div>
</div>
</section>
{/* Technical Atelier Bar */}
<div className="bg-primary py-12 md:py-16 text-white border-y border-white/5 relative z-20">
<div className="w-full flex justify-center">
<div className="w-full max-w-5xl mx-auto grid grid-cols-2 md:grid-cols-5 px-6 gap-y-12 md:gap-y-0 text-center">
<div className="flex flex-col items-center">
<span className="material-symbols-outlined text-secondary text-2xl md:text-3xl mb-4">straighten</span>
<span className="text-secondary font-label text-[9px] tracking-[0.4em] mb-2 uppercase">{t('length')}</span>
<span className="text-xl md:text-2xl font-headline tracking-widest">{yacht.length}</span>
</div>
<div className="flex flex-col items-center md:border-l md:border-white/10">
<span className="material-symbols-outlined text-secondary text-2xl md:text-3xl mb-4">groups</span>
<span className="text-secondary font-label text-[9px] tracking-[0.4em] mb-2 uppercase">{t('guests')}</span>
<span className="text-xl md:text-2xl font-headline tracking-widest">{yacht.guests}</span>
</div>
<div className="flex flex-col items-center md:border-l md:border-white/10">
<span className="material-symbols-outlined text-secondary text-2xl md:text-3xl mb-4">bed</span>
<span className="text-secondary font-label text-[9px] tracking-[0.4em] mb-2 uppercase">{t('cabins')}</span>
<span className="text-xl md:text-2xl font-headline tracking-widest">{yacht.cabins}</span>
</div>
<div className="flex flex-col items-center md:border-l md:border-white/10">
<span className="material-symbols-outlined text-secondary text-2xl md:text-3xl mb-4">support_agent</span>
<span className="text-secondary font-label text-[9px] tracking-[0.4em] mb-2 uppercase">{t('crew')}</span>
<span className="text-xl md:text-2xl font-headline tracking-widest">{yacht.crew}</span>
</div>
<div className="flex flex-col items-center md:border-l md:border-white/10 col-span-2 md:col-span-1">
<span className="material-symbols-outlined text-secondary text-2xl md:text-3xl mb-4">speed</span>
<span className="text-secondary font-label text-[9px] tracking-[0.4em] mb-2 uppercase">{t('speed')}</span>
<span className="text-xl md:text-2xl font-headline tracking-widest">{yacht.speed}</span>
</div>
</div>
</div>
</div>
{/* Editorial Content Section */}
<section className="pt-24 md:pt-24 pb-32 md:pb-48 px-6 md:px-12 overflow-hidden flex flex-col items-center justify-center w-full">
{/* Intro Block - Full Width Centered */}
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
className="max-w-4xl mx-auto text-center mb-32 md:mb-48"
>
<motion.span variants={fadeInUp} className="font-label text-xs tracking-[0.4em] text-secondary uppercase mb-6 block">
{t('design_engineering')}
</motion.span>
<motion.h2 variants={fadeInUp} className="text-primary font-headline text-5xl md:text-8xl mb-8 leading-[1.05] tracking-tight">
{t('sanctuary_title1')} <br />
<span className="text-secondary italic">{t('sanctuary_title2')}</span>
</motion.h2>
<motion.div variants={fadeInUp} className="h-px w-16 bg-secondary mx-auto mb-12"></motion.div>
<motion.div variants={fadeInUp} className="flex justify-center gap-12 mb-12 font-label text-xs text-on-surface-variant uppercase tracking-widest">
<div className="text-center">
<span className="block text-[9px] text-outline mb-1">{t('builder')}</span>
<span className="font-medium text-primary">{yacht.builder}</span>
</div>
<div className="w-px h-8 bg-outline-variant/20"></div>
<div className="text-center">
<span className="block text-[9px] text-outline mb-1">{t('year_refit')}</span>
<span className="font-medium text-primary">{yacht.year} {yacht.refitYear && `(${yacht.refitYear})`}</span>
</div>
</motion.div>
<motion.p variants={fadeInUp} className="font-body text-on-surface-variant font-light text-lg md:text-xl leading-[2] max-w-2xl mx-auto mb-16">
{locale === 'tr' && yacht.description_tr ? yacht.description_tr : yacht.description}
</motion.p>
<motion.div variants={fadeInUp}>
<Link href="/contact" className="inline-block px-16 py-5 bg-secondary text-white font-headline tracking-[0.3em] uppercase text-xs hover:bg-primary transition-colors duration-500">
{t('inquire_btn')}
</Link>
</motion.div>
</motion.div>
{/* Specifications Grid - Modern Cards */}
<div className="max-w-[1440px] mx-auto space-y-32">
{/* 1. Construction & Design */}
{(yacht.construction || yacht.furniture) && (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
className="flex flex-col items-center"
>
<motion.div variants={fadeInUp} className="flex flex-col items-center text-center mb-16">
<span className="font-headline text-7xl md:text-9xl text-outline-variant/8 font-bold leading-none mb-4">01</span>
<h3 className="font-headline text-xl md:text-2xl tracking-[0.3em] text-primary uppercase">{t('design_construction').replace(/0\d\s\/\s/, '')}</h3>
<div className="h-px w-12 bg-secondary mt-4"></div>
</motion.div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full">
{yacht.construction && Object.entries(yacht.construction).map(([key, value]) => (
<motion.div
key={key}
variants={fadeInUp}
className="bg-white p-8 border border-outline-variant/8 hover:border-secondary/30 hover:shadow-lg transition-all duration-500 group text-center"
>
<span className="font-label text-[9px] tracking-[0.3em] text-secondary uppercase block mb-3">
{key.replace(/([A-Z])/g, ' $1').trim()}
</span>
<span className="font-headline text-base md:text-lg text-primary tracking-tight block">
{value}
</span>
</motion.div>
))}
</div>
</motion.div>
)}
{/* 2. Equipment on Board */}
{yacht.equipment && yacht.equipment.length > 0 && (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
className="flex flex-col items-center"
>
<motion.div variants={fadeInUp} className="flex flex-col items-center text-center mb-16">
<span className="font-headline text-7xl md:text-9xl text-outline-variant/8 font-bold leading-none mb-4">02</span>
<h3 className="font-headline text-xl md:text-2xl tracking-[0.3em] text-primary uppercase">{t('tech_equipment').replace(/0\d\s\/\s/, '')}</h3>
<div className="h-px w-12 bg-secondary mt-4"></div>
</motion.div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 w-full">
{yacht.equipment.map((item, idx) => (
<motion.div
key={idx}
variants={fadeInUp}
className="flex items-center gap-5 px-6 py-5 bg-white border border-outline-variant/8 hover:border-secondary/20 hover:shadow-md transition-all duration-500 group"
>
<div className="w-8 h-8 flex items-center justify-center bg-surface border border-outline-variant/10 group-hover:bg-secondary/10 group-hover:border-secondary/20 transition-all duration-500 shrink-0">
<span className="material-symbols-outlined text-secondary text-sm">check</span>
</div>
<span className="font-body text-sm text-primary/80 tracking-wide leading-snug">{item}</span>
</motion.div>
))}
</div>
</motion.div>
)}
{/* 3. Tenders & Toys */}
{yacht.watersports && yacht.watersports.length > 0 && (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
className="flex flex-col items-center"
>
<motion.div variants={fadeInUp} className="flex flex-col items-center text-center mb-16">
<span className="font-headline text-7xl md:text-9xl text-outline-variant/8 font-bold leading-none mb-4">03</span>
<h3 className="font-headline text-xl md:text-2xl tracking-[0.3em] text-primary uppercase">{t('water_toys').replace(/0\d\s\/\s/, '')}</h3>
<div className="h-px w-12 bg-secondary mt-4"></div>
</motion.div>
<div className="flex flex-wrap justify-center gap-4">
{yacht.watersports.map((item, idx) => (
<motion.div
key={idx}
variants={fadeInUp}
className="px-8 py-5 bg-white border border-outline-variant/10 hover:border-secondary hover:shadow-lg transition-all duration-500 group cursor-default"
>
<div className="flex items-center gap-4">
<span className="material-symbols-outlined text-secondary/40 text-lg group-hover:text-secondary transition-colors duration-500">anchor</span>
<span className="font-headline text-xs tracking-[0.25em] text-primary uppercase group-hover:text-secondary transition-colors duration-500">{item}</span>
</div>
</motion.div>
))}
</div>
</motion.div>
)}
</div>
</section>
{/* Atelier Gallery Section */}
{yacht.gallery && yacht.gallery.length > 0 && (
<section className="py-24 md:py-32 bg-white">
<div className="flex flex-col items-center text-center mb-16">
<span className="font-label text-xs tracking-[0.4em] text-secondary uppercase block mb-4">{t('atelier_experience')}</span>
<h2 className="font-headline text-4xl md:text-5xl text-primary leading-tight uppercase tracking-widest">
{t('gallery')}
</h2>
</div>
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
className="w-full max-w-[1600px] mx-auto px-4 md:px-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 auto-rows-[400px]"
>
{yacht.gallery.map((imgUrl, index) => {
const isLarge = index === 0;
return (
<motion.div
key={index}
variants={fadeInUp}
onClick={() => openLightbox(index)}
className={`relative overflow-hidden group cursor-pointer ${isLarge ? 'md:col-span-2 lg:col-span-2 row-span-2' : 'col-span-1 row-span-1'}`}
>
<CldImage
src={imgUrl}
alt={`${yacht.name} Gallery ${index + 1}`}
fill
crop="fill"
gravity="auto"
className="object-cover transition-transform duration-1000 group-hover:scale-105"
/>
<div className="absolute inset-0 bg-black/10 group-hover:bg-transparent transition-colors duration-500 flex items-center justify-center">
<span className="material-symbols-outlined text-white text-3xl opacity-0 group-hover:opacity-80 transition-opacity duration-500">zoom_in</span>
</div>
</motion.div>
);
})}
</motion.div>
</section>
)}
{/* Charter Rates Section */}
{yacht.prices && yacht.prices.length > 0 && (
<section className="py-32 md:py-48 bg-[#0a0a0a] text-white overflow-hidden relative">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none opacity-[0.02] w-full text-center">
<span className="font-headline text-[10rem] md:text-[20rem] font-bold leading-none tracking-tighter uppercase">RATES</span>
</div>
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
className="max-w-6xl mx-auto px-6 md:px-12 relative z-10"
>
<div className="flex flex-col items-center text-center mb-24 border-b border-white/10 pb-16">
<motion.span variants={fadeInUp} className="font-label text-xs tracking-[0.4em] text-secondary uppercase mb-6 block">
Charter Investment
</motion.span>
<motion.h2 variants={fadeInUp} className="font-headline text-4xl md:text-6xl uppercase tracking-widest mb-6">
{t('rates')}
</motion.h2>
<motion.p variants={fadeInUp} className="font-label text-xs tracking-widest text-white/50 uppercase">
{t('rates_desc')}
</motion.p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-px bg-white/10 border border-white/10">
{yacht.prices.map((p) => (
<motion.div key={p.month} variants={fadeInUp} className="bg-[#0a0a0a] p-12 hover:bg-white/5 transition-colors duration-500 flex flex-col items-center justify-center text-center group">
<span className="block font-label text-[10px] tracking-[0.4em] text-secondary uppercase mb-4 transition-transform duration-500 group-hover:-translate-y-1">
{p.month}
</span>
<span className="text-3xl md:text-4xl font-headline tracking-tighter text-white">
{p.price}
</span>
</motion.div>
))}
</div>
<motion.div variants={fadeInUp} className="mt-16 text-center">
<p className="font-body text-xs text-white/40 leading-loose max-w-2xl mx-auto">
* {t('apa_note')} <br />
* {t('vat_note')}
</p>
</motion.div>
</motion.div>
</section>
)}
{/* Editorial Navigation */}
<section className="py-32 md:py-48 bg-surface text-center px-6 flex flex-col items-center justify-center">
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={staggerContainer}
className="flex flex-col items-center max-w-3xl"
>
<motion.span variants={fadeInUp} className="font-label text-xs tracking-[0.4em] text-secondary uppercase mb-8 block">
{t('journey_title')}
</motion.span>
<motion.h2 variants={fadeInUp} className="text-primary font-headline text-4xl md:text-7xl mb-16 italic tracking-tight leading-[1.1]">
{t('ready_title')}
</motion.h2>
<motion.div variants={fadeInUp} className="flex flex-col md:flex-row items-center justify-center gap-6 w-full">
<Link href="/contact" className="px-12 py-5 bg-secondary text-white font-headline text-xs tracking-[0.3em] uppercase w-full md:w-auto hover:bg-primary transition-colors duration-500">
{t('start_inquiry_btn')}
</Link>
<Link href="/fleet" className="px-12 py-5 border border-primary/20 text-primary font-headline text-xs tracking-[0.3em] uppercase w-full md:w-auto hover:bg-primary hover:text-white transition-colors duration-500">
{t('explore_btn')}
</Link>
</motion.div>
</motion.div>
</section>
{/* Lightbox Overlay */}
<AnimatePresence>
{lightboxIndex !== null && yacht.gallery && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="fixed inset-0 z-[100] bg-black/95 flex items-center justify-center"
onClick={closeLightbox}
>
{/* Close Button */}
<button
onClick={closeLightbox}
className="absolute top-8 right-8 text-white/60 hover:text-white transition-colors z-[110]"
>
<span className="material-symbols-outlined text-4xl">close</span>
</button>
{/* Image Counter */}
<div className="absolute top-8 left-8 font-label text-xs tracking-[0.3em] text-white/50 uppercase z-[110]">
{lightboxIndex + 1} / {yacht.gallery.length}
</div>
{/* Previous Arrow */}
<button
onClick={(e) => { e.stopPropagation(); goPrev(); }}
className="absolute left-4 md:left-8 top-1/2 -translate-y-1/2 w-14 h-14 flex items-center justify-center text-white/40 hover:text-white transition-colors z-[110]"
>
<span className="material-symbols-outlined text-4xl">chevron_left</span>
</button>
{/* Next Arrow */}
<button
onClick={(e) => { e.stopPropagation(); goNext(); }}
className="absolute right-4 md:right-8 top-1/2 -translate-y-1/2 w-14 h-14 flex items-center justify-center text-white/40 hover:text-white transition-colors z-[110]"
>
<span className="material-symbols-outlined text-4xl">chevron_right</span>
</button>
{/* Main Image */}
<motion.div
key={lightboxIndex}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.3 }}
className="relative w-[90vw] h-[80vh] max-w-[1400px]"
onClick={(e) => e.stopPropagation()}
>
<CldImage
src={yacht.gallery[lightboxIndex]}
alt={`${yacht.name} Gallery ${lightboxIndex + 1}`}
fill
crop="pad"
className="object-contain"
sizes="90vw"
priority
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

View File

@@ -0,0 +1,76 @@
import { yachts } from "../../data/yachts";
import { Link } from "@/i18n/routing";
import { useTranslations, useLocale } from "next-intl";
import { CldImage } from "next-cloudinary";
export default function FleetPage() {
const t = useTranslations('FleetList');
const locale = useLocale();
return (
<div className="pt-40 pb-24 px-6 md:px-24 min-h-screen bg-surface">
<div className="max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row items-end gap-16 mb-24">
<div className="w-full md:w-1/2">
<span className="font-label text-xs tracking-[0.4em] text-secondary uppercase mb-4 block">{t('collection')}</span>
<h1 className="font-headline text-5xl md:text-7xl text-primary leading-tight mb-8">
{t('title1')} <br />
<span className="text-secondary italic">{t('title2')}</span>
</h1>
<div className="h-px w-24 bg-secondary mb-8"></div>
<p className="font-body text-on-surface-variant text-lg leading-relaxed font-light">
{t('description')}
</p>
</div>
<div className="w-full md:w-1/2 flex justify-end">
<div className="text-right">
<span className="font-label text-6xl md:text-8xl text-outline-variant/30 font-bold leading-none uppercase">{t('bg_text')}</span>
</div>
</div>
</div>
<div className="space-y-40">
{yachts.map((yacht, index) => (
<div key={yacht.slug} className={`grid grid-cols-1 md:grid-cols-12 gap-0 group ${index % 2 !== 0 ? 'md:flex-row-reverse' : ''}`}>
<div className={`md:col-span-7 relative h-[500px] md:h-[700px] overflow-hidden ${index % 2 !== 0 ? 'md:order-2' : ''}`}>
<CldImage
src={yacht.heroImage}
alt={yacht.name}
fill
crop="fill"
gravity="auto"
className="w-full h-full object-cover transition-all duration-1000 scale-100 group-hover:scale-105"
/>
<div className="absolute inset-0 bg-primary/20 group-hover:bg-transparent transition-colors duration-700" />
</div>
<div className={`md:col-span-5 flex flex-col justify-center py-12 md:py-0 ${index % 2 !== 0 ? 'md:order-1 md:pr-20 md:text-right md:items-end' : 'md:pl-20'}`}>
<span className="font-label text-xs tracking-[0.4em] text-secondary uppercase mb-4">{t('masterpiece')}{index + 1}</span>
<h3 className="font-headline text-4xl md:text-5xl text-primary mb-8 tracking-wide uppercase">{yacht.name}</h3>
<p className="text-on-surface-variant font-light leading-relaxed mb-12 max-w-sm">
{locale === 'tr' && yacht.description_tr ? yacht.description_tr : yacht.description}
</p>
<div className="grid grid-cols-3 border-t border-outline-variant/20 pt-8 w-full text-left">
<div className="flex flex-col gap-1">
<span className="material-symbols-outlined text-secondary text-xl">straighten</span>
<span className="font-label text-[10px] tracking-widest text-outline uppercase mt-2">{t('length')}</span>
<span className="font-body text-sm text-primary font-semibold">{yacht.length}</span>
</div>
<div className="flex flex-col gap-1">
<span className="material-symbols-outlined text-secondary text-xl">groups</span>
<span className="font-label text-[10px] tracking-widest text-outline uppercase mt-2">{t('guests')}</span>
<span className="font-body text-sm text-primary font-semibold">{yacht.guests} {t('guests_suffix')}</span>
</div>
<div className="flex flex-col gap-1">
<Link href={`/fleet/${yacht.slug}`} className="mt-auto">
<span className="font-label text-[10px] tracking-widest text-secondary border-b border-secondary pb-1 uppercase hover:opacity-70 smooth-transition">{t('discover')}</span>
</Link>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
);
}