first commit
This commit is contained in:
86
app/components/AccommodationCard.tsx
Normal file
86
app/components/AccommodationCard.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { resortData } from "@/src/data/resort";
|
||||
import { motion } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
|
||||
interface AccommodationCardProps {
|
||||
room: any;
|
||||
lang: "tr";
|
||||
}
|
||||
|
||||
const getCloudinaryUrl = (publicId: string, width = 1200) => {
|
||||
if (!publicId) return "https://images.unsplash.com/photo-1566073771259-6a8506099945?auto=format&fit=crop&q=80&w=1200";
|
||||
return `https://res.cloudinary.com/du7xohbct/image/upload/q_auto,f_auto,w_${width}/${publicId}`;
|
||||
};
|
||||
|
||||
export default function AccommodationCard({ room, lang }: AccommodationCardProps) {
|
||||
const allImages = [
|
||||
room.mainImageId,
|
||||
...(room.galleryImageIds || [])
|
||||
];
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full bg-[#ECE7E1] mb-12 shadow-sm">
|
||||
{/* Precision Header Section */}
|
||||
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center px-10 py-12 gap-10">
|
||||
|
||||
{/* Left: Title & Descriptions */}
|
||||
<div className="flex-[2] space-y-3">
|
||||
<h3 className="text-[#4F5B3A] text-2xl font-serif tracking-tight leading-none mb-1">
|
||||
{room.name[lang]} <span className="text-sm font-sans opacity-60">({room.size})</span>
|
||||
</h3>
|
||||
<p className="text-[13px] leading-relaxed text-[#4F5B3A] opacity-90 font-light max-w-2xl">
|
||||
{room.description[lang]}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right: Booking Button */}
|
||||
<div className="flex-shrink-0 w-full lg:w-auto">
|
||||
<Link
|
||||
href={`/accommodation/${room.slug}`}
|
||||
className="block w-full lg:w-auto bg-[#C87E4B] hover:bg-[#A6693E] text-black px-12 py-4 text-[11px] font-bold tracking-[0.2em] rounded-md transition-all duration-300 transform hover:-translate-y-1 shadow-xl hover:shadow-[#C87E4B]/20 text-center uppercase"
|
||||
>
|
||||
Odayı İncele
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DRAGGABLE SLIDER */}
|
||||
<div
|
||||
style={{ height: '550px', position: 'relative', width: '100vw', left: '50%', right: '50%', marginLeft: '-50vw', marginRight: '-50vw' }}
|
||||
className="overflow-hidden cursor-grab active:cursor-grabbing bg-zinc-200"
|
||||
>
|
||||
<motion.div
|
||||
ref={scrollRef}
|
||||
drag="x"
|
||||
dragConstraints={{ right: 80, left: -((allImages.length - 1) * 720) }}
|
||||
className="flex h-full gap-4 px-[15vw]"
|
||||
>
|
||||
{allImages.map((id, idx) => (
|
||||
<div
|
||||
key={`${id}-${idx}`}
|
||||
style={{ height: '100%', position: 'relative', minWidth: '700px', flexShrink: 0 }}
|
||||
className="rounded-sm overflow-hidden shadow-sm group"
|
||||
>
|
||||
<Image
|
||||
src={getCloudinaryUrl(id, 1200)}
|
||||
alt="Gallery"
|
||||
fill
|
||||
style={{ objectFit: 'cover' }}
|
||||
sizes="(max-width: 1024px) 100vw, 80vw"
|
||||
className="transition-transform duration-700 group-hover:scale-105 pointer-events-none"
|
||||
draggable={false}
|
||||
priority={idx < 2}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
app/components/ExperienceCard.tsx
Normal file
41
app/components/ExperienceCard.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ExperienceSection } from "@/src/data/resort";
|
||||
import { getCloudinaryUrl } from "@/src/lib/cloudinary";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
interface ExperienceCardProps {
|
||||
experience: ExperienceSection;
|
||||
lang: "tr" | "en" | "de";
|
||||
}
|
||||
|
||||
export default function ExperienceCard({ experience, lang }: ExperienceCardProps) {
|
||||
return (
|
||||
<Link
|
||||
href={experience.href}
|
||||
className="group relative overflow-hidden rounded-2xl aspect-square block"
|
||||
>
|
||||
<Image
|
||||
src={getCloudinaryUrl(experience.imageId, { width: 800, height: 800, crop: "fill" })}
|
||||
alt={experience.title[lang]}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-110 group-hover:rotate-1"
|
||||
/>
|
||||
|
||||
{/* Dark Overlay */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-60 transition-opacity duration-700 group-hover:opacity-40" />
|
||||
|
||||
{/* Minimal Gold Border Hover */}
|
||||
<div className="absolute inset-4 border border-white/0 transition-all duration-700 group-hover:border-gold/30 group-hover:backdrop-blur-[2px] flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold tracking-[0.4em] opacity-0 translate-y-4 transition-all duration-700 group-hover:opacity-100 group-hover:translate-y-0 border-b border-gold pb-1">
|
||||
{lang === "tr" ? "KEŞFET" : lang === "en" ? "EXPLORE" : "ENTDECKEN"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="absolute bottom-8 left-8 text-white group-hover:opacity-0 transition-opacity duration-500">
|
||||
<h3 className="text-2xl font-serif font-bold mb-1 tracking-wider">{experience.title[lang]}</h3>
|
||||
<p className="text-white/70 text-[10px] uppercase tracking-[0.2em] font-medium">{experience.subtitle[lang]}</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
49
app/components/FloatingBookingBar.tsx
Normal file
49
app/components/FloatingBookingBar.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { resortData } from "@/src/data/resort";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function FloatingBookingBar() {
|
||||
// Normally would have state for dates, but following PRD's "minimal bar"
|
||||
const lang = "tr"; // Mocked locale
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-6 left-0 right-0 z-40 px-6 animate-fade-in-up">
|
||||
<div className="max-w-4xl mx-auto glass-dark text-white rounded-full p-2 flex items-center shadow-2xl">
|
||||
<div className="flex-1 flex items-center divide-x divide-white/10 px-4 overflow-x-auto no-scrollbar">
|
||||
{/* Check In */}
|
||||
<div className="px-4 py-2 flex flex-col min-w-[120px]">
|
||||
<span className="text-[10px] uppercase tracking-widest text-white/50">
|
||||
{resortData.floatingBar.checkIn[lang]}
|
||||
</span>
|
||||
<span className="text-sm font-semibold italic">Select Date</span>
|
||||
</div>
|
||||
|
||||
{/* Check Out */}
|
||||
<div className="px-4 py-2 flex flex-col min-w-[120px]">
|
||||
<span className="text-[10px] uppercase tracking-widest text-white/50">
|
||||
{resortData.floatingBar.checkOut[lang]}
|
||||
</span>
|
||||
<span className="text-sm font-semibold italic">Select Date</span>
|
||||
</div>
|
||||
|
||||
{/* Guests */}
|
||||
<div className="px-4 py-2 flex flex-col min-w-[100px]">
|
||||
<span className="text-[10px] uppercase tracking-widest text-white/50">
|
||||
{resortData.floatingBar.guests[lang]}
|
||||
</span>
|
||||
<span className="text-sm font-semibold">2 Adults</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href={resortData.bookingUrl}
|
||||
target="_blank"
|
||||
className="bg-gold text-white px-8 py-3 rounded-full font-bold text-sm hover:bg-gold-dark transition-all whitespace-nowrap"
|
||||
>
|
||||
{resortData.floatingBar.search[lang]}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
124
app/components/Footer.tsx
Normal file
124
app/components/Footer.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { resortData } from "@/src/data/resort";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-[#EAE5D8] pt-24 pb-8 px-6 overflow-hidden border-t border-black/5">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Main Footer Content */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-12 mb-20">
|
||||
|
||||
{/* Column 1: Brand & Newsletter */}
|
||||
<div className="lg:col-span-1 space-y-8">
|
||||
<h2 className="text-3xl font-serif text-gray-900 tracking-tighter">SALMAKIS</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="relative group">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="E-Posta Adresiniz"
|
||||
className="w-full bg-white border-none py-4 px-6 pr-32 text-xs tracking-widest outline-none focus:ring-1 focus:ring-gold transition-all"
|
||||
/>
|
||||
<button className="absolute right-1 top-1 bottom-1 bg-[#C59D5F] text-white px-6 text-[10px] font-bold tracking-widest hover:bg-gold transition-all">
|
||||
ABONE OL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-3 cursor-pointer group">
|
||||
<input type="checkbox" className="w-3 h-3 accent-gold border-gray-300" />
|
||||
<span className="text-[10px] text-gray-400 font-light tracking-wider group-hover:text-gray-600 transition-colors">
|
||||
Kişisel verilerimin işlenmesini kabul ediyorum.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 pt-4">
|
||||
{resortData.social.map((social) => (
|
||||
<Link
|
||||
key={social.platform}
|
||||
href={social.url}
|
||||
className="text-gray-900 hover:text-gold transition-colors text-lg"
|
||||
>
|
||||
<i className={`fab fa-${social.platform.toLowerCase()}`}></i>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 2: Salmakis */}
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-[10px] font-bold tracking-[0.3em] text-gray-400 uppercase">SALMAKIS</h4>
|
||||
<ul className="space-y-3">
|
||||
{["Doğada.", "Sofrada.", "Odalarda.", "Bizimle."].map((item) => (
|
||||
<li key={item}>
|
||||
<Link href="#" className="text-sm font-light text-gray-600 hover:text-gold transition-colors">{item}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Column 3: Sayfalar */}
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-[10px] font-bold tracking-[0.3em] text-gray-400 uppercase">SAYFALAR</h4>
|
||||
<ul className="space-y-3">
|
||||
{["Konaklama", "S.S.S", "Sürdürülebilirlik", "Resim Galerisi", "Fiyat Listesi"].map((item) => (
|
||||
<li key={item}>
|
||||
<Link href="#" className="text-sm font-light text-gray-600 hover:text-gold transition-colors">{item}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Column 4: Yasal */}
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-[10px] font-bold tracking-[0.3em] text-gray-400 uppercase">VERİ KORUMA</h4>
|
||||
<ul className="space-y-3">
|
||||
{["Künye", "Gizlilik Politikası", "KVKK", "Çerez Ayarları"].map((item) => (
|
||||
<li key={item}>
|
||||
<Link href="#" className="text-sm font-light text-gray-600 hover:text-gold transition-colors">{item}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Column 5: İletişim */}
|
||||
<div className="space-y-6">
|
||||
<h4 className="text-[10px] font-bold tracking-[0.3em] text-gray-400 uppercase">İLETİŞİM</h4>
|
||||
<ul className="space-y-3">
|
||||
<li><Link href="#" className="text-sm font-light text-gray-600 hover:text-gold transition-colors">Oda Rezerve Et</Link></li>
|
||||
<li><Link href="#" className="text-sm font-light text-gray-600 hover:text-gold transition-colors">Yol Tarifi Al</Link></li>
|
||||
<li><Link href="#" className="text-sm font-light text-gray-600 hover:text-gold transition-colors">İletişime Geç</Link></li>
|
||||
<li className="pt-2 text-sm text-gray-900 font-medium tracking-wider">{resortData.contact.phone}</li>
|
||||
<li className="text-sm text-gray-400 font-light">{resortData.contact.email}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Brand Logos / Partners */}
|
||||
<div className="border-t border-black/5 py-16 flex flex-wrap justify-center items-center gap-12 md:gap-24 opacity-40 grayscale hover:grayscale-0 transition-all duration-700">
|
||||
{/* Placeholder Logos reflecting the style in the image */}
|
||||
<div className="text-2xl font-serif tracking-widest text-[#1a2e1e]">BODRUM</div>
|
||||
<div className="text-sm font-bold tracking-[0.4em] text-[#1a2e1e]">EGE MUTFAĞI</div>
|
||||
<div className="text-xl font-serif text-[#1a2e1e] border border-current px-2">S</div>
|
||||
<div className="text-2xl font-sans font-extralight tracking-[0.3em] text-[#1a2e1e]">BLUE FLAG</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom copyright area */}
|
||||
<div className="relative pt-12 flex flex-col md:flex-row justify-between items-center gap-4 text-[9px] tracking-[0.2em] font-medium text-gray-400 uppercase">
|
||||
<div>© 2026 — HER HAKKI SAKLIDIR.</div>
|
||||
|
||||
{/* Decorative Mountain Line (SVG) */}
|
||||
<div className="absolute bottom-[-20px] left-1/2 -translate-x-1/2 w-full max-w-4xl opacity-10 -z-10">
|
||||
<svg viewBox="0 0 1000 100" className="w-full">
|
||||
<path d="M0 100 L200 60 L400 90 L600 40 L800 70 L1000 50 L1000 100 Z" fill="none" stroke="currentColor" strokeWidth="1" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div>SALMAKIS RESORT & SPA</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
80
app/components/Navbar.tsx
Normal file
80
app/components/Navbar.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Navbar() {
|
||||
const lang = "tr"; // Mocked locale
|
||||
|
||||
const navLinks = [
|
||||
{ label: { tr: "KONAKLAMA", en: "ACCOMMODATION", de: "UNTERKUNFT" }, href: "/accommodation", side: "left" },
|
||||
{ label: { tr: "YİYECEK & İÇECEK", en: "FOOD & BEVERAGE", de: "FOOD & BEVERAGE" }, href: "/dining", side: "left" },
|
||||
{ label: { tr: "AKTİVİTE", en: "ACTIVITIES", de: "AKTIVITÄTEN" }, href: "/activities", side: "left" },
|
||||
{ label: { tr: "ORGANİZASYON", en: "ORGANIZER", de: "ORGANISATION" }, href: "/organizations", side: "right" },
|
||||
{ label: { tr: "GALERİ", en: "GALLERY", de: "GALERIE" }, href: "/gallery", side: "right" },
|
||||
{ label: { tr: "SPA CENTER", en: "SPA CENTER", de: "SPA CENTER" }, href: "/spa", side: "right" },
|
||||
];
|
||||
|
||||
return (
|
||||
<nav className=" top-0 left-0 right-0 z-50 transition-all duration-500 py-6 bg-white shadow-sm border-b border-gray-50">
|
||||
<div className="max-w-[1600px] mx-auto px-10 flex items-center justify-between">
|
||||
|
||||
{/* Left Menu */}
|
||||
<div className="hidden lg:flex items-center space-x-12 flex-1">
|
||||
{navLinks.filter(l => l.side === "left").map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="group relative text-[11px] font-medium tracking-[0.2em] transition-all text-gray-900 overflow-hidden"
|
||||
>
|
||||
<span className="relative z-10 block transition-transform duration-300 group-hover:-translate-y-full">
|
||||
{link.label[lang as "tr"]}
|
||||
</span>
|
||||
<span className="absolute inset-0 z-20 block transition-transform duration-300 translate-y-full group-hover:translate-y-0 text-gold italic">
|
||||
{link.label[lang as "tr"]}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Centered Logo */}
|
||||
<div className="flex flex-col items-center">
|
||||
<Link href="/" className="flex flex-col items-center group">
|
||||
<span className="text-3xl md:text-5xl font-serif font-light tracking-[0.3em] text-gray-900 leading-none">
|
||||
SALMAKIS
|
||||
</span>
|
||||
<span className="text-[10px] tracking-[0.6em] mt-2 text-gray-400 font-sans uppercase">
|
||||
Resort & Spa
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Right Menu */}
|
||||
<div className="hidden lg:flex items-center justify-end space-x-12 flex-1">
|
||||
{navLinks.filter(l => l.side === "right").map((link) => (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="group relative text-[11px] font-medium tracking-[0.2em] transition-all text-gray-900 overflow-hidden"
|
||||
>
|
||||
<span className="relative z-10 block transition-transform duration-300 group-hover:-translate-y-full">
|
||||
{link.label[lang as "tr"]}
|
||||
</span>
|
||||
<span className="absolute inset-0 z-20 block transition-transform duration-300 translate-y-full group-hover:translate-y-0 text-gold italic">
|
||||
{link.label[lang as "tr"]}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<div className="lg:hidden">
|
||||
<button className="p-2 text-gray-900">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1} stroke="currentColor" className="w-8 h-8">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 9h16.5m-16.5 6.75h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
57
app/components/ScrollVideo.tsx
Normal file
57
app/components/ScrollVideo.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { motion, useScroll, useTransform, useSpring } from "framer-motion";
|
||||
|
||||
export default function ScrollVideo() {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Use scroll progress within this section
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ["start start", "end end"]
|
||||
});
|
||||
|
||||
// Smooth out the scroll value
|
||||
const smoothProgress = useSpring(scrollYProgress, {
|
||||
stiffness: 100,
|
||||
damping: 30,
|
||||
restDelta: 0.001
|
||||
});
|
||||
|
||||
// Calculate the expansion: Starts at 25% circle, grows to 150% (to fully cover screen)
|
||||
const clipPathValue = useTransform(smoothProgress, [0, 1], ["circle(25% at 50% 50%)", "circle(100% at 50% 50%)"]);
|
||||
|
||||
// Also fade the overlay
|
||||
const overlayOpacity = useTransform(smoothProgress, [0, 0.8], [0.3, 0]);
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={containerRef}
|
||||
className="relative h-[250vh] bg-white"
|
||||
>
|
||||
<div className="sticky top-0 h-screen w-full flex items-center justify-center overflow-hidden z-30">
|
||||
<motion.div
|
||||
style={{ clipPath: clipPathValue }}
|
||||
className="relative w-full h-full flex items-center justify-center bg-white will-change-transform shadow-2xl"
|
||||
>
|
||||
{/* YouTube Embed with Hardware Acceleration */}
|
||||
<div className="absolute inset-0 pointer-events-none scale-110 will-change-transform">
|
||||
<iframe
|
||||
className="absolute top-1/2 left-1/2 w-[115vw] h-[115vh] -translate-x-1/2 -translate-y-1/2 object-cover pointer-events-none"
|
||||
src="https://www.youtube.com/embed/avqL1kRkX0c?autoplay=1&mute=1&controls=0&loop=1&playlist=avqL1kRkX0c&playsinline=1&rel=0&modestbranding=1"
|
||||
allow="autoplay; encrypted-media"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
{/* Subtle Overlay */}
|
||||
<motion.div
|
||||
style={{ opacity: overlayOpacity }}
|
||||
className="absolute inset-0 bg-black/20 z-10 pointer-events-none"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
22
app/components/SecurityBadge.tsx
Normal file
22
app/components/SecurityBadge.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { resortData } from "@/src/data/resort";
|
||||
|
||||
export default function SecurityBadge({ lang }: { lang: "tr" | "en" | "de" }) {
|
||||
return (
|
||||
<div className="flex flex-col items-center text-center max-w-2xl mx-auto py-12 px-6 border-t border-gray-100">
|
||||
<div className="w-16 h-16 bg-bodrum-blue rounded-full mb-6 flex items-center justify-center text-white shadow-xl shadow-bodrum-blue/20">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-8 h-8">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 0 1-1.043 3.296 3.745 3.745 0 0 1-3.296 1.043A3.745 3.745 0 0 1 12 21a3.745 3.745 0 0 1-3.127-1.593 3.745 3.745 0 0 1-3.296-1.043 3.745 3.745 0 0 1-1.043-3.296A3.745 3.745 0 0 1 3 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 0 1 1.043-3.296 3.745 3.745 0 0 1 3.296-1.043A3.745 3.745 0 0 1 12 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 0 1 3.296 1.043 3.746 3.746 0 0 1 1.043 3.296A3.745 3.745 0 0 1 21 12Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4 className="text-xl font-bold text-bodrum-blue mb-2">
|
||||
{resortData.securityBadge.title[lang]}
|
||||
</h4>
|
||||
<p className="text-gray-500 text-sm leading-relaxed mb-4">
|
||||
{resortData.securityBadge.description[lang]}
|
||||
</p>
|
||||
<div className="text-[10px] uppercase tracking-[0.2em] font-bold text-gray-400">
|
||||
Official Website Protection • Verified Secure
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
app/components/SmoothScroll.tsx
Normal file
16
app/components/SmoothScroll.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { ReactLenis } from "lenis/react";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function SmoothScroll({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ReactLenis root options={{
|
||||
lerp: 0.05,
|
||||
duration: 1.5,
|
||||
smoothWheel: true
|
||||
}}>
|
||||
{children}
|
||||
</ReactLenis>
|
||||
);
|
||||
}
|
||||
95
app/components/SplitSection.tsx
Normal file
95
app/components/SplitSection.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { getCloudinaryUrl } from "@/src/lib/cloudinary";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
interface SplitSectionProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
description: string;
|
||||
mainImage: string;
|
||||
secondImage: string;
|
||||
href: string;
|
||||
reverse?: boolean;
|
||||
}
|
||||
|
||||
export default function SplitSection({
|
||||
title,
|
||||
subtitle,
|
||||
description,
|
||||
mainImage,
|
||||
secondImage,
|
||||
href,
|
||||
reverse = false
|
||||
}: SplitSectionProps) {
|
||||
return (
|
||||
<section className={`flex flex-col ${reverse ? 'md:flex-row-reverse' : 'md:flex-row'} min-h-screen w-full bg-[#FAF9F6] overflow-hidden`}>
|
||||
|
||||
{/* Text Side */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
className="w-full md:w-1/2 flex flex-col justify-center px-10 md:px-24 py-20"
|
||||
>
|
||||
<span className="text-gold text-xs tracking-[0.4em] font-bold uppercase mb-6 block">
|
||||
{subtitle}
|
||||
</span>
|
||||
<h2 className="text-5xl md:text-7xl font-serif text-gray-900 leading-tight mb-8">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-gray-500 text-lg leading-relaxed max-w-md mb-12 font-light">
|
||||
{description}
|
||||
</p>
|
||||
<div>
|
||||
<Link
|
||||
href={href}
|
||||
className="group relative inline-block overflow-hidden bg-[#C59D5F] text-white px-10 py-4 rounded-md text-[10px] font-bold tracking-widest transition-all hover:bg-gold uppercase active:scale-95"
|
||||
>
|
||||
<span className="relative z-10">KEŞFET</span>
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-white/20 -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-in-out"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Image Side */}
|
||||
<div className="w-full md:w-1/2 relative min-h-[500px] md:min-h-screen">
|
||||
<motion.div
|
||||
initial={{ scale: 1.1, opacity: 0 }}
|
||||
whileInView={{ scale: 1, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1.5, ease: "easeOut" }}
|
||||
className="relative w-full h-full"
|
||||
>
|
||||
<Image
|
||||
src={getCloudinaryUrl(mainImage, { width: 1200, height: 1600, crop: "fill" })}
|
||||
alt={title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Floating Secondary Image with Parallax-ish Scroll Effect */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: reverse ? -100 : 100, y: 50 }}
|
||||
whileInView={{ opacity: 1, x: reverse ? "-40px" : "40px", y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: 0.4, ease: "easeOut" }}
|
||||
className={`absolute bottom-20 ${reverse ? 'left-0' : 'right-0'} z-10 w-48 md:w-80 aspect-[3/4] shadow-2xl hidden md:block`}
|
||||
>
|
||||
<Image
|
||||
src={getCloudinaryUrl(secondImage, { width: 600, height: 800, crop: "fill" })}
|
||||
alt={`${title} detail`}
|
||||
fill
|
||||
className="object-cover border-[10px] border-white"
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
120
app/components/TestimonialsSlider.tsx
Normal file
120
app/components/TestimonialsSlider.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useRef, useState, useEffect } from "react";
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
name: "Ayşe Y.",
|
||||
location: "Türkiye",
|
||||
text: "Bardakçı Koyu'nun büyüleyici manzarasında unutulmaz bir tatil geçirdik. Personelin ilgisi ve otelin zarafeti bizi mest etti.",
|
||||
stars: 5
|
||||
},
|
||||
{
|
||||
name: "John D.",
|
||||
location: "United Kingdom",
|
||||
text: "An absolutely stunning resort. The spa treatment was world-class, and the breakfast by the sea was the highlight of our stay.",
|
||||
stars: 5
|
||||
},
|
||||
{
|
||||
name: "Klaus M.",
|
||||
location: "Germany",
|
||||
text: "Sehr schönes Hotel mit exzellentem Service. Der Privatstrand ist kristallklar ve sehr ruhig. Wir kommen auf jeden Fall wieder!",
|
||||
stars: 5
|
||||
},
|
||||
{
|
||||
name: "Elena S.",
|
||||
location: "Italy",
|
||||
text: "Un posto magico. La colazione è fantastica e la camera con vista mare ha superato le nostre aspettative. Grazie Salmakis!",
|
||||
stars: 5
|
||||
},
|
||||
{
|
||||
name: "Mehmet A.",
|
||||
location: "Türkiye",
|
||||
text: "Gastronomi anlamında gerçekten çok başarılı. Her akşam farklı bir lezzet şöleni yaşadık. Sahili ise kelimenin tam anlamıyla kusursuz.",
|
||||
stars: 5
|
||||
}
|
||||
];
|
||||
|
||||
export default function TestimonialsSlider() {
|
||||
const constraintsRef = useRef<HTMLDivElement>(null);
|
||||
const [width, setWidth] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (constraintsRef.current) {
|
||||
// Calculate how much we can drag: total width - container width
|
||||
setWidth(constraintsRef.current.scrollWidth - constraintsRef.current.offsetWidth);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="bg-[#EAE5D8] py-24 overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto px-6 mb-16">
|
||||
<div className="flex flex-col md:flex-row justify-between items-end gap-8">
|
||||
<div className="space-y-4">
|
||||
<span className="text-gold text-xs tracking-[0.4em] font-bold uppercase">
|
||||
MİSAFİR YORUMLARI
|
||||
</span>
|
||||
<h2 className="text-5xl md:text-7xl font-serif text-gray-900 leading-tight">
|
||||
Misafirlerimiz <br /> Ne Diyor?
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="max-w-md text-right md:text-right flex flex-col items-end gap-6 pb-4">
|
||||
<p className="text-gray-600 font-light leading-relaxed">
|
||||
Gerçek deneyimler ve samimi sözler. Salmakis Resort & Spa'daki anıların
|
||||
her türlü tanımdan daha fazlasını anlattığına inanıyoruz.
|
||||
</p>
|
||||
<button className="bg-[#C59D5F] text-white px-8 py-3 rounded-md text-[10px] font-bold tracking-[0.2em] hover:bg-gold transition-all uppercase">
|
||||
DENEYİMİNİZİ PAYLAŞIN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slider Container */}
|
||||
<div className="relative w-full overflow-hidden pb-10">
|
||||
<motion.div
|
||||
ref={constraintsRef}
|
||||
drag="x"
|
||||
dragConstraints={{ right: 0, left: -width }}
|
||||
dragElastic={0.1}
|
||||
whileTap={{ cursor: "grabbing" }}
|
||||
className="flex gap-6 px-6 md:px-[calc((100vw-1280px)/2)] cursor-grab whitespace-nowrap"
|
||||
initial={{ x: 100, opacity: 0 }}
|
||||
whileInView={{ x: 0, opacity: 1 }}
|
||||
transition={{ duration: 1 }}
|
||||
>
|
||||
{testimonials.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="min-w-[320px] md:min-w-[400px] bg-[#3A4D3F] p-10 flex flex-col justify-between h-[450px] shadow-2xl select-none"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="flex gap-1">
|
||||
{[...Array(item.stars)].map((_, i) => (
|
||||
<span key={i} className="text-white text-sm">★</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<span className="text-white/40 text-[10px] tracking-widest font-bold uppercase">
|
||||
{item.location}
|
||||
</span>
|
||||
<p className="text-white/90 text-lg md:text-xl font-light leading-relaxed italic whitespace-normal">
|
||||
“{item.text}”
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-6 border-t border-white/10">
|
||||
<span className="text-white text-xs tracking-[0.2em] font-bold uppercase">
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user