This commit is contained in:
2026-04-19 17:23:31 +03:00
parent 9cad199125
commit 4f2188363a
122 changed files with 3215 additions and 116 deletions

46
components/Amenities.tsx Normal file
View File

@@ -0,0 +1,46 @@
'use client'
import {
ShieldCheck,
Bell,
Car,
BedDouble,
CookingPot,
AirVent,
Bed,
Flame,
Wifi,
Video
} from 'lucide-react'
export default function Amenities({ dict }: { dict: any }) {
const ams = [
{ icon: <Video className="w-5 h-5" />, label: dict.amenities.cctv },
{ icon: <Bell className="w-5 h-5" />, label: dict.amenities.fire_alarm },
{ icon: <Car className="w-5 h-5" />, label: dict.amenities.parking },
{ icon: <BedDouble className="w-5 h-5" />, label: dict.amenities.double_bed },
{ icon: <CookingPot className="w-5 h-5" />, label: dict.amenities.kitchen },
{ icon: <AirVent className="w-5 h-5" />, label: dict.amenities.ac },
{ icon: <Bed className="w-5 h-5" />, label: dict.amenities.single_bed },
{ icon: <Flame className="w-5 h-5" />, label: dict.amenities.fire_ext },
{ icon: <Wifi className="w-5 h-5" />, label: dict.amenities.wifi },
]
return (
<div className="py-12 border-t border-[#1A1A1A]/10">
<h3 className="text-2xl font-serif text-[#1A1A1A] mb-10">{dict.amenities.title}</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-y-8 gap-x-12">
{ams.map((item, idx) => (
<div key={idx} className="flex items-center space-x-4 group">
<div className="bg-[#C88C4B]/10 p-3 rounded-full text-[#C88C4B] group-hover:bg-[#C88C4B] group-hover:text-white transition-all duration-300">
{item.icon}
</div>
<span className="text-lg text-[#1A1A1A]/60 font-medium group-hover:text-[#1A1A1A] transition-colors">
{item.label}
</span>
</div>
))}
</div>
</div>
)
}

124
components/CallToAction.tsx Normal file
View File

@@ -0,0 +1,124 @@
'use client'
import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'
import Link from 'next/link'
import Image from 'next/image'
const images = [
{
src: "https://images.unsplash.com/photo-1541410950669-e771b058097d?q=80&w=2070&auto=format&fit=crop",
w: 305, h: 225,
tx: -550, ty: -350,
rotate: -2
},
{
src: "https://images.unsplash.com/photo-1590490360182-c33d57733427?q=80&w=2100&auto=format&fit=crop",
w: 325, h: 425,
tx: 550, ty: -350,
rotate: 1
},
{
src: "https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2070&auto=format&fit=crop",
w: 385, h: 485,
tx: -550, ty: 350,
rotate: -1
},
{
src: "https://images.unsplash.com/photo-1571896349842-33c89424de2d?q=80&w=2073&auto=format&fit=crop",
w: 425, h: 365,
tx: 550, ty: 350,
rotate: 2
},
]
export default function CallToAction({ lang, dict }: { lang: string, dict: any }) {
const containerRef = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start start", "end end"]
})
// Title: Stays hit at 0 until scroll hits 30%, then reveals quickly.
const entranceOpacity = useTransform(scrollYProgress, [0.85, 0.95, 1], [0, 1, 1])
// Description / Button: Reveals even later, ensuring images are already scattering
const textOpacity = useTransform(scrollYProgress, [0.5, 0.7, 1], [0, 1, 1])
const textY = useTransform(scrollYProgress, [0.5, 0.7, 1], [50, 0, 0])
// Scatter range: Images start dispersing at 20% to clear the path for text
const scatterRange = [0.2, 0.95]
const scatterTLX = useTransform(scrollYProgress, scatterRange, [0, -580])
const scatterTLY = useTransform(scrollYProgress, scatterRange, [0, -380])
const scatterTRX = useTransform(scrollYProgress, scatterRange, [0, 580])
const scatterTRY = useTransform(scrollYProgress, scatterRange, [0, -380])
const scatterBLX = useTransform(scrollYProgress, scatterRange, [0, -580])
const scatterBLY = useTransform(scrollYProgress, scatterRange, [0, 380])
const scatterBRX = useTransform(scrollYProgress, scatterRange, [0, 580])
const scatterBRY = useTransform(scrollYProgress, scatterRange, [0, 380])
const scatterX = [scatterTLX, scatterTRX, scatterBLX, scatterBRX]
const scatterY = [scatterTLY, scatterTRY, scatterBLY, scatterBRY]
return (
<section ref={containerRef} className="relative h-[300vh] bg-white z-60">
<div className="sticky top-0 h-screen flex items-center justify-center overflow-hidden">
{/* SCATTERING POLAROIDS */}
{images.map((img, i) => (
<motion.div
key={i}
style={{
x: scatterX[i],
y: scatterY[i],
rotate: img.rotate
}}
className="absolute z-10"
>
<div className={`relative shadow-2xl p-4 bg-white`} style={{ width: img.w, height: img.h }}>
<div className="relative w-full h-full">
<Image src={img.src} alt="Ayris" fill className="object-cover" />
</div>
</div>
</motion.div>
))}
{/* REVEALING CENTER CONTENT */}
<motion.div
style={{ opacity: entranceOpacity }}
className="relative z-20 text-center max-w-4xl px-8"
>
<div className="space-y-12">
<h2 className="text-6xl md:text-[110px] font-serif text-[#1A1A1A] leading-[1] tracking-tight uppercase">
{dict.hero.title}
</h2>
<div className="flex flex-col items-center space-y-12">
<motion.p
style={{ opacity: textOpacity, y: textY }}
className="text-[#1A1A1A]/60 text-xl md:text-2xl font-medium max-w-xl italic leading-relaxed"
>
&quot;{dict.footer.desc}&quot;
</motion.p>
<motion.div style={{ opacity: textOpacity, y: textY }}>
<Link
href={`/${lang}/reservation`}
className="inline-flex items-center space-x-3 group text-[14px] font-bold tracking-[0.4em] text-[#1A1A1A] uppercase border-b-2 border-[#1A1A1A] pb-3"
>
<span className="text-[#C88C4B] text-xl transform group-hover:translate-x-1.5 transition-transform duration-500"></span>
<span>{dict.footer.book}</span>
</Link>
</motion.div>
</div>
</div>
</motion.div>
</div>
</section>
)
}

104
components/Experiences.tsx Normal file
View File

@@ -0,0 +1,104 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import Image from 'next/image'
import { useState } from 'react'
const experiences = [
{
title: "Infinity Pool",
description: "Swim towards the horizon in our stunning heated infinity pool, overlooking the turquoise expanse of the Aegean Sea.",
image: "https://images.unsplash.com/photo-1576013551627-0cfde316be70?q=80&w=1974&auto=format&fit=crop"
},
{
title: "Snorkeling",
description: "Discover vibrant marine life, swim through crystal-clear waters, and experience the wonder of ocean.",
image: "https://images.unsplash.com/photo-1544551763-46a013bb70d5?q=80&w=2070&auto=format&fit=crop"
},
{
title: "Paddle boarding",
description: "Balance your soul and body while gliding over calm morning waves on our premium paddle boards.",
image: "https://images.unsplash.com/photo-1517176641128-052478950337?q=80&w=1974&auto=format&fit=crop"
},
{
title: "Fishing trips",
description: "Join our local experts for an authentic Mediterranean fishing experience at sunrise.",
image: "https://images.unsplash.com/photo-1524704652723-21444983944d?q=80&w=1974&auto=format&fit=crop"
}
]
export default function Experiences() {
const [hoveredIdx, setHoveredIdx] = useState<number | null>(null)
return (
<section className="relative z-60 bg-[#FAF7F0] py-32 px-6 md:px-12">
<div className="max-w-[1400px] mx-auto space-y-20">
<div className="text-center space-y-4">
<motion.h2
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, ease: [0.33, 1, 0.68, 1] }}
className="text-4xl md:text-[64px] font-medium text-[#1A1A1A] leading-tight"
>
Discover experiences <br /> beyond the shore
</motion.h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-12">
{experiences.map((exp, idx) => (
<motion.div
key={idx}
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1, delay: idx * 0.1, ease: [0.33, 1, 0.68, 1] }}
onMouseEnter={() => setHoveredIdx(idx)}
onMouseLeave={() => setHoveredIdx(null)}
animate={{
filter: hoveredIdx !== null && hoveredIdx !== idx ? 'blur(4px)' : 'blur(0px)',
opacity: hoveredIdx !== null && hoveredIdx !== idx ? 0.5 : 1,
scale: hoveredIdx === idx ? 1.02 : 1
}}
className="group cursor-pointer flex flex-col relative"
>
<div className="relative aspect-[3/4] w-full overflow-hidden rounded-[2px] mb-6">
<Image
src={exp.image}
alt={exp.title}
fill
className="object-cover transition-transform duration-1000 group-hover:scale-110"
/>
{/* Overlay Description */}
<AnimatePresence>
{hoveredIdx === idx && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 bg-black/40 backdrop-blur-[2px] flex flex-col justify-end p-8"
>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="text-white text-base md:text-lg leading-relaxed font-medium"
>
{exp.description}
</motion.p>
</motion.div>
)}
</AnimatePresence>
</div>
<h3 className="text-2xl font-medium text-[#1A1A1A] group-hover:text-[#C88C4B] transition-colors">
{exp.title}
</h3>
</motion.div>
))}
</div>
</div>
</section>
)
}

126
components/Footer.tsx Normal file
View File

@@ -0,0 +1,126 @@
'use client'
import Link from 'next/link'
import Image from 'next/image'
import { Phone, Mail } from 'lucide-react'
export default function Footer({ lang, dict }: { lang: string, dict: any }) {
return (
<footer className="bg-[#141210] text-white pt-32 mt-48 pb-48 px-8 md:px-20 font-sans">
<div className="max-w-[1600px] mx-auto ">
{/* ROW 1: BRAND & CONTACT */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-20 mt-48">
<h2 className="text-7xl md:text-[100px] font-serif leading-none tracking-tight text-white uppercase">
AYRIS APART
</h2>
<div className="space-y-4 pt-10 md:pt-0">
<div className="flex items-center justify-end space-x-3 text-sm font-medium tracking-widest opacity-80">
<Phone size={15} className="opacity-60" /> <span>+90 543 231 87 13</span>
</div>
<div className="flex items-center justify-end space-x-3 text-sm font-medium tracking-widest opacity-80">
<Mail size={15} className="opacity-60" /> <span>hello@ayrisapart.com</span>
</div>
</div>
</div>
{/* THIN SEPARATOR LINE */}
<div className="w-full h-px bg-white/10 mb-20" />
{/* ROW 2: MAIN GRID */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-y-16 lg:gap-x-24 mb-32">
{/* LEFT CONTENT (CTA) */}
<div className="lg:col-span-5 space-y-10 max-w-sm">
<p className="text-white/70 text-[17px] leading-relaxed">
{lang === 'tr'
? "Konaklamanızı bugün ayırtın ve Ören kıyılarında deniz kenarı lüksünü, nefes kesen manzaraları ve unutulmaz anları keşfedin."
: "Book your stay today and discover seaside luxury, breathtaking views, and unforgettable moments on the shores of Oren."}
</p>
<Link
href={`/${lang}/reservation`}
className="inline-flex items-center space-x-2 border-b border-white/40 pb-1 group hover:border-white transition-all"
>
<span className="text-[#C88C4B] text-xl transform group-hover:translate-x-1 transition-transform"></span>
<span className="text-sm font-bold tracking-[0.3em] uppercase">{dict.footer.book}</span>
</Link>
</div>
{/* RIGHT LINKS */}
<div className="lg:col-span-7">
<div className="grid grid-cols-2 md:grid-cols-4 gap-12">
{/* EXPLORE */}
<div className="space-y-8">
<h4 className="text-[10px] font-bold tracking-[0.4em] opacity-40 uppercase">EXPLORE</h4>
<ul className="space-y-4 text-sm font-medium opacity-60 uppercase tracking-widest">
<li><Link href={`/${lang}`} className="hover:opacity-100 transition-opacity">{dict.nav.home}</Link></li>
<li><Link href={`/${lang}/about`} className="hover:opacity-100 transition-opacity">{dict.nav.about}</Link></li>
<li><Link href={`/${lang}/news`} className="hover:opacity-100 transition-opacity">{dict.nav.news}</Link></li>
<li><Link href={`/${lang}/suites`} className="hover:opacity-100 transition-opacity">{dict.nav.suites}</Link></li>
</ul>
</div>
{/* OTHERS */}
<div className="space-y-8">
<h4 className="text-[10px] font-bold tracking-[0.4em] opacity-40 uppercase">OTHERS</h4>
<ul className="space-y-4 text-sm font-medium opacity-60 uppercase tracking-widest">
<li><Link href={`/${lang}/services`} className="hover:opacity-100 transition-opacity">Services</Link></li>
<li><Link href={`/${lang}/activities`} className="hover:opacity-100 transition-opacity">Activities</Link></li>
<li><Link href={`/${lang}/gallery`} className="hover:opacity-100 transition-opacity">Gallery</Link></li>
<li><Link href={`/${lang}/contact`} className="hover:opacity-100 transition-opacity">{dict.nav.contact}</Link></li>
</ul>
</div>
{/* UTILITY */}
<div className="space-y-8">
<h4 className="text-[10px] font-bold tracking-[0.4em] opacity-40 uppercase">UTILITY</h4>
<ul className="space-y-4 text-sm font-medium opacity-60 uppercase tracking-widest">
<li><Link href={`/${lang}/license`} className="hover:opacity-100 transition-opacity">License</Link></li>
<li><Link href={`/${lang}/404`} className="hover:opacity-100 transition-opacity">404</Link></li>
</ul>
</div>
{/* SOCIALS */}
<div className="space-y-8">
<h4 className="text-[10px] font-bold tracking-[0.4em] opacity-40 uppercase">SOCIALS</h4>
<ul className="space-y-4 text-sm font-medium opacity-60 uppercase tracking-widest">
<li><a href="#" className="hover:opacity-100 transition-opacity">Instagram</a></li>
<li><a href="#" className="hover:opacity-100 transition-opacity">Facebook</a></li>
<li><a href="#" className="hover:opacity-100 transition-opacity">Twitter</a></li>
</ul>
</div>
</div>
</div>
</div>
{/* ROW 3: FINAL BOTTOM WITH CENTER ICON SPLIT */}
<div className="relative pt-12 flex flex-col md:flex-row justify-between items-center bg-[#141210]">
{/* The split line effect */}
<div className="absolute top-0 left-0 w-[45%] h-px bg-white/10 hidden lg:block" />
<div className="absolute top-0 right-0 w-[45%] h-px bg-white/10 hidden lg:block" />
<div className="text-[10px] font-bold tracking-[0.3em] opacity-40 uppercase items-center flex">
COPYRIGHT © AYRIS APART
</div>
{/* Central Decorative Logo Image */}
<div className="md:absolute md:left-1/2 md:-translate-x-1/2 md:-top-10 mb-8 md:mb-0">
<div className="relative w-24 h-24">
<Image
src="/logo.png"
alt="Ayris Apart Logo"
fill
className="object-contain filter brightness-0 invert"
/>
</div>
</div>
<div className="text-[10px] font-bold tracking-[0.3em] opacity-40 uppercase md:mt-0">
CREATED BY <a href="https://ayris.dev" target="_blank" className="hover:text-white transition-opacity">AYRISTECH</a>
</div>
</div>
</div>
</footer>
)
}

167
components/Hero.tsx Normal file
View File

@@ -0,0 +1,167 @@
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import Image from 'next/image'
const heroSlots = [
{
srcs: [
'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?q=80&w=2070&auto=format&fit=crop',
'https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2070&auto=format&fit=crop',
'https://images.unsplash.com/photo-1590490360182-c33d57733427?q=80&w=2070&auto=format&fit=crop',
],
rotate: -12,
top: '15%',
left: '22%',
size: 'w-48 h-64 md:w-64 md:h-80',
delay: 0.2
},
{
srcs: [
'https://images.unsplash.com/photo-1544124499-58912cbddaad?q=80&w=2127&auto=format&fit=crop',
'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?q=80&w=2127&auto=format&fit=crop',
'https://images.unsplash.com/photo-1571896349842-33c89424de2d?q=80&w=2127&auto=format&fit=crop',
],
rotate: 10,
top: '10%',
left: '58%',
size: 'w-52 h-64 md:w-72 md:h-80',
delay: 0.4
},
{
srcs: [
'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=2073&auto=format&fit=crop',
'https://images.unsplash.com/photo-1519046904884-53103b34b206?q=80&w=2073&auto=format&fit=crop',
'https://images.unsplash.com/photo-1506929199020-feee17651703?q=80&w=2073&auto=format&fit=crop',
],
rotate: -8,
top: '65%',
left: '18%',
size: 'w-48 h-60 md:w-64 md:h-72',
delay: 0.6
},
{
srcs: [
'https://images.unsplash.com/photo-1536935338218-422119932906?q=80&w=1964&auto=format&fit=crop',
'https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=1964&auto=format&fit=crop',
'https://images.unsplash.com/photo-1560185007-cde436f6a4d0?q=80&w=1964&auto=format&fit=crop',
],
rotate: 15,
top: '72%',
left: '62%',
size: 'w-52 h-64 md:w-72 md:h-88',
delay: 0.8
}
]
function FloatingSlot({ slot, idx }: { slot: typeof heroSlots[0], idx: number }) {
const [currentIdx, setCurrentIdx] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setCurrentIdx((prev) => (prev + 1) % slot.srcs.length)
}, 4000 + idx * 500)
return () => clearInterval(interval)
}, [slot.srcs.length, idx])
return (
<motion.div
initial={{ opacity: 0, scale: 0.8, rotate: 0, y: 50 }}
animate={{
opacity: 1,
scale: 1,
rotate: slot.rotate,
y: 0
}}
transition={{
duration: 1.2,
delay: slot.delay,
ease: [0.33, 1, 0.68, 1]
}}
style={{
top: slot.top,
left: slot.left,
zIndex: 5
}}
className={`absolute ${slot.size} hidden md:block overflow-hidden`}
>
<AnimatePresence initial={false}>
<motion.div
key={currentIdx}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 2, ease: "easeInOut" }}
className="absolute inset-0"
>
<Image
src={slot.srcs[currentIdx]}
alt="Luxury Experience"
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, 33vw"
priority={idx < 2}
/>
</motion.div>
</AnimatePresence>
</motion.div>
)
}
export default function Hero({ lang, dict }: { lang: string, dict: any }) {
return (
<section className="sticky top-0 z-[20] h-screen w-full bg-[#FAF7F0] flex items-center justify-center overflow-hidden">
{/* BACKGROUND TEXT */}
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 1.5, ease: [0.33, 1, 0.68, 1] }}
className="relative z-10 w-full text-center px-4"
>
<h1 className="text-[14vw] leading-[0.8] font-serif tracking-[-0.04em] text-[#1A1A1A] uppercase select-none drop-shadow-sm">
{dict.hero.title}
</h1>
</motion.div>
{/* FLOATING SLOTS */}
{heroSlots.map((slot, idx) => (
<FloatingSlot key={idx} slot={slot} idx={idx} />
))}
{/* BOTTOM CONTENT */}
<div className="absolute bottom-12 right-12 z-30 max-w-sm text-right flex flex-col items-end space-y-6">
<motion.p
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 1.2, duration: 0.8 }}
className="text-[#1A1A1A]/70 text-[14px] leading-relaxed font-medium"
>
{dict.hero.desc}
</motion.p>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.4, duration: 0.8 }}
>
<Link
href={`/${lang}/suites`}
className="inline-block px-8 py-4 bg-[#1A1A1A] text-white text-[12px] font-bold uppercase tracking-widest hover:bg-[#C88C4B] transition-colors duration-300 rounded-[2px] shadow-lg shadow-black/10"
>
{dict.hero.explore}
</Link>
</motion.div>
</div>
<motion.div
animate={{ y: [0, 10, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
className="absolute bottom-8 left-1/2 -translate-x-1/2 opacity-30"
>
<div className="w-[1px] h-12 bg-[#1A1A1A]" />
</motion.div>
</section>
)
}

167
components/Navbar.tsx Normal file
View File

@@ -0,0 +1,167 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { motion, AnimatePresence } from 'framer-motion'
import { usePathname, useRouter } from 'next/navigation'
import Image from 'next/image'
import { ArrowRight, ChevronDown, Menu, X, Plus } from 'lucide-react'
export default function Navbar({ lang, dict }: { lang: string, dict: any }) {
const router = useRouter()
const pathname = usePathname()
const [isVisible, setIsVisible] = useState(true)
const [lastScrollY, setLastScrollY] = useState(0)
const [isScrolled, setIsScrolled] = useState(false)
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const handleLangChange = (newLang: string) => {
const segments = pathname.split('/')
if (segments[1] === 'en' || segments[1] === 'tr') {
segments[1] = newLang
} else {
segments.splice(1, 0, newLang)
}
router.push(segments.join('/'))
}
const navLinks = [
{ name: dict.nav.about, href: `/${lang}/about` },
{ name: dict.nav.suites, href: `/${lang}/suites`, hasDropdown: true },
{ name: dict.nav.news, href: `/${lang}/news` },
]
useEffect(() => {
const controlNavbar = () => {
if (typeof window !== 'undefined') {
const currentScrollY = window.scrollY
if (currentScrollY > lastScrollY && currentScrollY > 100) {
setIsVisible(false)
} else {
setIsVisible(true)
}
setLastScrollY(currentScrollY)
setIsScrolled(currentScrollY > 50)
}
}
window.addEventListener('scroll', controlNavbar)
return () => window.removeEventListener('scroll', controlNavbar)
}, [lastScrollY])
return (
<>
<motion.nav
initial={{ y: -100 }}
animate={{ y: isVisible ? 0 : -100 }}
transition={{ duration: 0.6, ease: [0.33, 1, 0.68, 1] }}
className={`fixed top-0 left-0 right-0 z-[100] transition-all duration-500 flex justify-center py-4 px-6 md:px-12`}
>
<div className={`w-full flex items-center justify-between bg-white rounded-[2px] transition-all duration-500 border border-[#1A1A1A]/5 ${isScrolled ? 'py-3 px-10' : 'py-5 px-12'}`}>
{/* LOGO */}
<Link href={`/${lang}`} className="flex items-center group">
<span className="text-[24px] font-serif tracking-[0.25em] text-[#1A1A1A] uppercase">
AYRIS APART
</span>
</Link>
{/* DESKTOP NAV */}
<div className="hidden md:flex items-center space-x-12">
{navLinks.map((link) => (
<Link
key={link.name}
href={link.href}
className="group relative text-[11px] font-bold tracking-[0.35em] uppercase text-[#1A1A1A]/70 hover:text-[#1A1A1A] transition-colors overflow-hidden"
>
<span className="block">{link.name}</span>
<span className="absolute bottom-0 left-0 w-full h-[1.5px] bg-[#C88C4B] transform scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left" />
</Link>
))}
</div>
<div className="flex items-center space-x-8">
{/* LANGUAGE SWITCHER */}
<div className="hidden md:flex items-center space-x-3 text-[11px] font-bold tracking-widest mr-4">
<button
onClick={() => handleLangChange('tr')}
className={`transition-colors ${lang === 'tr' ? 'text-[#C88C4B]' : 'text-[#1A1A1A]/30 hover:text-[#1A1A1A]'}`}
>
TR
</button>
<span className="w-[1px] h-3 bg-[#1A1A1A]/10" />
<button
onClick={() => handleLangChange('en')}
className={`transition-colors ${lang === 'en' ? 'text-[#C88C4B]' : 'text-[#1A1A1A]/30 hover:text-[#1A1A1A]'}`}
>
EN
</button>
</div>
<Link
href={`/${lang}/contact`}
className="hidden lg:flex items-center space-x-3 group"
>
<span className="text-[11px] font-bold tracking-[0.35em] uppercase text-[#1A1A1A]">{dict.nav.contact}</span>
<div className="w-10 h-10 rounded-full border border-[#1A1A1A]/10 flex items-center justify-center group-hover:bg-[#1A1A1A] group-hover:border-[#1A1A1A] transition-all duration-500">
<ArrowRight size={16} className="text-[#1A1A1A] group-hover:text-white transition-colors" />
</div>
</Link>
{/* MOBILE MENU TOGGLE */}
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="md:hidden w-10 h-10 flex flex-col items-center justify-center space-y-1.5"
>
<div className={`w-6 h-[1.5px] bg-[#1A1A1A] transition-all duration-300 ${isMobileMenuOpen ? 'rotate-45 translate-y-2' : ''}`} />
<div className={`w-6 h-[1.5px] bg-[#1A1A1A] transition-all duration-300 ${isMobileMenuOpen ? 'opacity-0' : ''}`} />
<div className={`w-6 h-[1.5px] bg-[#1A1A1A] transition-all duration-300 ${isMobileMenuOpen ? '-rotate-45 -translate-y-2' : ''}`} />
</button>
</div>
</div>
</motion.nav>
{/* MOBILE MENU */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, x: '100%' }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: '100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
className="fixed inset-0 z-[150] bg-[#FAF7F0] flex flex-col md:hidden"
>
<div className="flex items-center justify-between p-8 border-b border-[#1A1A1A]/5">
<span className="text-[20px] font-serif tracking-[0.25em] text-[#1A1A1A] uppercase">AYRIS</span>
<button onClick={() => setIsMobileMenuOpen(false)} className="p-2"><X size={32} /></button>
</div>
<div className="flex flex-col flex-1 p-8 space-y-10">
{navLinks.map((link, idx) => (
<motion.div
key={link.name}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.1 * idx }}
>
<Link href={link.href} onClick={() => setIsMobileMenuOpen(false)} className="text-5xl font-serif text-[#1A1A1A] tracking-tighter">
{link.name}
</Link>
</motion.div>
))}
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: 0.4 }}>
<Link href={`/${lang}/contact`} onClick={() => setIsMobileMenuOpen(false)} className="text-5xl font-serif text-[#1A1A1A] tracking-tighter">
{dict.nav.contact}
</Link>
</motion.div>
{/* Lang Toggle Mobile */}
<div className="flex items-center space-x-6 pt-12">
<button onClick={() => { handleLangChange('tr'); setIsMobileMenuOpen(false); }} className={`text-xl font-bold tracking-widest ${lang === 'tr' ? 'text-[#C88C4B]' : 'text-[#1A1A1A]/30'}`}>TR</button>
<button onClick={() => { handleLangChange('en'); setIsMobileMenuOpen(false); }} className={`text-xl font-bold tracking-widest ${lang === 'en' ? 'text-[#C88C4B]' : 'text-[#1A1A1A]/30'}`}>EN</button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</>
)
}

View File

@@ -0,0 +1,19 @@
'use client'
import { motion } from 'framer-motion'
export default function QuoteSection() {
return (
<section className="relative z-60 bg-[#FAF7F0] py-40 flex items-center justify-center text-center px-6">
<motion.h2
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.5, ease: [0.33, 1, 0.68, 1] }}
className="text-4xl md:text-[80px] font-medium text-[#1A1A1A] leading-[1.1] max-w-5xl"
>
Luxury stays designed for ocean lovers
</motion.h2>
</section>
)
}

View File

@@ -0,0 +1,76 @@
'use client'
import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'
export default function ScrollReveal() {
const containerRef = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start start", "end end"]
})
// Text Animations - Sharp movement
const topTextY = useTransform(scrollYProgress, [0.1, 0.5], [0, -400])
const bottomTextY = useTransform(scrollYProgress, [0.1, 0.5], [0, 400])
// Video Animations - Sharp appearing.
// We use opacity as a "switch" at 0.1 to keep it hidden until then.
const videoVisible = useTransform(scrollYProgress, [0, 1], [0, 1])
const videoScale = useTransform(scrollYProgress, [0, 0.8], [0, 1])
const videoWidth = useTransform(scrollYProgress, [0, 0.8], ["20vw", "100vw"])
const videoHeight = useTransform(scrollYProgress, [0, 0.8], ["10vh", "100vh"])
const videoBorderRadius = useTransform(scrollYProgress, [0.6, 0.9], ["40px", "0px"])
return (
<section ref={containerRef} className="h-[300vh] relative bg-[#FAF7F0] z-60">
<div className="sticky top-0 h-screen w-full flex items-center justify-center overflow-hidden">
{/* TEXT MASK CONTAINER */}
<div className="absolute inset-0 z-30 flex items-center justify-center pointer-events-none">
<div className="h-[400px] w-full flex items-center justify-center overflow-hidden">
<motion.div className="text-center w-full">
<motion.h2
style={{ y: topTextY }}
className="text-7xl md:text-[140px] font-medium text-[#1A1A1A] leading-[0.8] mb-12"
>
Stay bliss
</motion.h2>
<motion.h2
style={{ y: bottomTextY }}
className="text-7xl md:text-[140px] font-medium text-[#1A1A1A] leading-[0.8]"
>
Enjoy more
</motion.h2>
</motion.div>
</div>
</div>
{/* EXPANDING VIDEO CONTAINER */}
<motion.div
style={{
width: videoWidth,
height: videoHeight,
scale: videoScale,
borderRadius: videoBorderRadius,
opacity: videoVisible, // Use as an on/off switch instead of a slow fade
zIndex: 10
}}
className="relative bg-black overflow-hidden flex items-center justify-center"
>
<div className="relative w-full h-full">
<iframe
className="absolute inset-x-0 w-full h-[140%] -top-[20%] pointer-events-none"
src="https://www.youtube.com/embed/-A4UceF6QpE?autoplay=1&mute=1&loop=1&playlist=-A4UceF6QpE&controls=0&showinfo=0&rel=0&iv_load_policy=3&modestbranding=1"
allow="autoplay; encrypted-media"
allowFullScreen
/>
<div className="absolute inset-0 z-10" />
</div>
</motion.div>
</div>
</section>
)
}

View File

@@ -0,0 +1,40 @@
'use client'
import { motion, useScroll, useTransform } from 'framer-motion'
import { useRef } from 'react'
import Image from 'next/image'
export default function ShowcaseImage() {
const containerRef = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start end", "end start"]
})
// Slightly increased scales for a better balance
const scale = useTransform(scrollYProgress, [0, 0.4], [0.8, 1.0])
const opacity = useTransform(scrollYProgress, [0, 0.2], [0, 1])
return (
<section ref={containerRef} className="relative z-60 bg-[#FAF7F0] pb-32">
<div className="max-w-[1400px] mx-auto px-6 overflow-hidden">
<motion.div
style={{
scale,
opacity
}}
className="relative aspect-[16/9] w-full overflow-hidden shadow-xl rounded-sm"
>
<Image
src="https://images.unsplash.com/photo-1571896349842-33c89424de2d?q=80&w=2080&auto=format&fit=crop"
alt="Luxury Cave Pool Experience"
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-black/5" />
</motion.div>
</div>
</section>
)
}

View File

@@ -0,0 +1,112 @@
'use client'
import { useRef } from 'react'
import { motion, useScroll, useTransform } from 'framer-motion'
import Image from 'next/image'
import Link from 'next/link'
interface Suite {
id: string
name: string
number: string
image: string
desc: string
}
export default function SuitesHighlights({ lang, dict }: { lang: string, dict: any }) {
const container = useRef(null)
const suites: Suite[] = [
{ id: 'iris', number: '01', name: dict.suites.s1.name, desc: dict.suites.s1.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606641/ayrisapart/Daire%201/photo_1_2024-04-05_12-32-09.jpg' },
{ id: 'electra', number: '02', name: dict.suites.s2.name, desc: dict.suites.s2.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606648/ayrisapart/Daire%202/photo_1_2024-04-05_16-04-34.jpg' },
{ id: 'arke', number: '03', name: dict.suites.s3.name, desc: dict.suites.s3.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606654/ayrisapart/Daire%203/photo_1_2024-04-05_16-06-09.jpg' },
{ id: 'harpy', number: '04', name: dict.suites.s4.name, desc: dict.suites.s4.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606661/ayrisapart/Daire%204/photo_1_2024-04-05_16-07-01.jpg' },
{ id: 'hydaspes', number: '05', name: dict.suites.s5.name, desc: dict.suites.s5.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606671/ayrisapart/Daire%205/photo_1_2024-05-04_15-32-44.jpg' },
{ id: 'zephyrus', number: '06', name: dict.suites.s6.name, desc: dict.suites.s6.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606681/ayrisapart/Daire%206/photo_1_2024-05-04_15-32-44.jpg' },
{ id: 'pothos', number: '07', name: dict.suites.s7.name, desc: dict.suites.s7.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606689/ayrisapart/Daire%207/photo_1_2024-05-04_15-33-34.jpg' },
{ id: 'thaumas', number: '08', name: dict.suites.s8.name, desc: dict.suites.s8.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606696/ayrisapart/Daire%208/photo_1_2024-05-04_15-33-34.jpg' },
]
return (
<section ref={container} className="relative bg-[#FAF7F0] z-60">
<div className="relative">
{suites.map((suite, idx) => (
<SuiteCard
key={suite.id}
suite={suite}
index={idx}
lang={lang}
dict={dict}
/>
))}
</div>
{/* Bottom spacer to allow the last item to scroll */}
<div className="h-[20vh] bg-[#FAF7F0]" />
</section>
)
}
function SuiteCard({ suite, index, lang, dict }: { suite: Suite, index: number, lang: string, dict: any }) {
const cardRef = useRef(null)
const { scrollYProgress } = useScroll({
target: cardRef,
offset: ["start end", "start start"]
})
// Stacking (Curtain) Effect: Each card is sticky and 100vh to cover the previous one
return (
<div
ref={cardRef}
className="sticky top-0 h-screen w-full flex items-center bg-[#FAF7F0] border-t border-[#1A1A1A]/5 shadow-[0_-20px_50px_rgba(0,0,0,0.05)]"
style={{ zIndex: index + 1 }}
>
<div className="max-w-[1400px] mx-auto px-6 w-full">
<div className="grid grid-cols-1 md:grid-cols-12 gap-12 items-center">
{/* Room Number & Info */}
<div className="md:col-span-4 space-y-10">
<div className="space-y-4">
<span className="text-[#1A1A1A]/20 text-6xl font-serif block">{suite.number}.</span>
<h3 className="text-4xl md:text-6xl font-serif text-[#1A1A1A] uppercase tracking-tight">{suite.name}</h3>
</div>
<p className="text-[#1A1A1A]/60 text-lg md:text-xl font-medium italic leading-relaxed max-w-sm">
{suite.desc}
</p>
<div className="pt-4">
<Link
href={`/${lang}/suites/${suite.id}`}
className="inline-flex items-center space-x-4 border-b-2 border-[#1A1A1A] pb-2 group"
>
<span className="text-[11px] font-bold tracking-[0.3em] uppercase">{dict.suites.btn.more}</span>
<span className="text-xl transform group-hover:translate-x-1 transition-transform"></span>
</Link>
</div>
</div>
{/* Image */}
<div className="md:col-span-8">
<motion.div
className="relative aspect-[16/10] md:aspect-[16/9] overflow-hidden rounded-[2px] shadow-2xl bg-white"
style={{
scale: useTransform(scrollYProgress, [0, 1], [0.9, 1]),
opacity: useTransform(scrollYProgress, [0, 0.5], [0, 1])
}}
>
<Image
src={suite.image}
alt={suite.name}
fill
className="object-cover"
/>
</motion.div>
</div>
</div>
</div>
</div>
)
}

70
components/Welcome.tsx Normal file
View File

@@ -0,0 +1,70 @@
'use client'
import { motion } from 'framer-motion'
import Link from 'next/link'
export default function Welcome({ lang, dict }: { lang: string, dict: any }) {
return (
<section className="relative z-[70] bg-[#FAF7F0] mt-24 md:mt-32 rounded-t-[40px] md:rounded-t-[100px] py-32 md:py-48 px-6 md:px-12 flex flex-col items-center text-center shadow-[0_-40px_100px_rgba(0,0,0,0.1)] overflow-hidden">
{/* Lotus/Floral Icon */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, ease: [0.33, 1, 0.68, 1] }}
className="mb-14"
>
<svg viewBox="0 0 60 60" className="w-16 h-16 fill-[#1A1A1A]">
<path d="M30 5C30 5 33 18 45 22C33 26 30 39 30 39C30 39 27 26 15 22C27 18 30 5 30 5Z" />
<path d="M30 25C30 25 42 22 55 10C45 15 35 25 30 25Z" fillOpacity="0.6" />
<path d="M30 25C30 25 18 22 5 10C15 15 25 25 30 25Z" fillOpacity="0.6" />
<path d="M30 39C30 39 42 42 55 54C45 49 35 39 30 39Z" fillOpacity="0.6" />
<path d="M30 39C30 39 18 42 5 54C15 49 25 39 30 39Z" fillOpacity="0.6" />
</svg>
</motion.div>
{/* Heading */}
<motion.h2
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, delay: 0.2, ease: [0.33, 1, 0.68, 1] }}
className="text-4xl md:text-[80px] font-serif text-[#1A1A1A] max-w-5xl leading-[1.1] tracking-tight mb-14"
>
{dict.welcome.title}
</motion.h2>
{/* Description */}
<motion.p
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, delay: 0.4, ease: [0.33, 1, 0.68, 1] }}
className="text-[#1A1A1A]/70 text-lg md:text-xl max-w-3xl leading-relaxed mb-14 font-medium"
>
{dict.welcome.desc}
</motion.p>
{/* Link */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1.2, delay: 0.6, ease: [0.33, 1, 0.68, 1] }}
>
<Link
href={`/${lang}/about`}
className="group flex items-center space-x-2 text-[14px] font-bold uppercase tracking-[0.2em] text-[#1A1A1A]"
>
<span className="border-b-[1.5px] border-[#1A1A1A] pb-1 font-sans">{dict.nav.about}</span>
<span className="transform group-hover:translate-x-1.5 group-hover:-translate-y-1.5 transition-all duration-500 ease-out">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M7 17l10-10" />
<path d="M17 17V7H7" />
</svg>
</span>
</Link>
</motion.div>
</section>
)
}