fb
142
app/[lang]/about/AboutClient.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import { Check, Hotel, Star, Globe } from 'lucide-react'
|
||||
|
||||
|
||||
|
||||
export default function AboutClient({ lang, dict }: { lang: string, dict: any }) {
|
||||
const whyUs = [
|
||||
{ title: dict.about.why['1'].t, desc: dict.about.why['1'].d },
|
||||
{ title: dict.about.why['2'].t, desc: dict.about.why['2'].d },
|
||||
{ title: dict.about.why['3'].t, desc: dict.about.why['3'].d },
|
||||
{ title: dict.about.why['4'].t, desc: dict.about.why['4'].d }
|
||||
]
|
||||
|
||||
const stats = [
|
||||
{ icon: <Hotel size={32} />, value: '8+', label: 'Premium Room' },
|
||||
{ icon: <Star size={32} />, value: '4.8', label: 'Guest Rating' },
|
||||
{ icon: <Globe size={32} />, value: '10+', label: 'Countries' }
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="pt-44 pb-20 px-6">
|
||||
<div className="max-w-4xl mx-auto text-center space-y-10">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1 }}
|
||||
className="text-7xl md:text-[120px] font-serif text-[#1A1A1A] leading-[0.9] tracking-tight uppercase"
|
||||
>
|
||||
{dict.about.title}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
className="text-[#1A1A1A]/70 text-lg md:text-xl max-w-2xl mx-auto font-medium leading-relaxed italic"
|
||||
>
|
||||
{dict.about.subtitle}
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* STORY & WHY US SECTION */}
|
||||
<section className="py-24 px-6 md:px-12 max-w-[1400px] mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 lg:gap-24 items-start">
|
||||
|
||||
{/* Left: Story */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="lg:col-span-7 space-y-10"
|
||||
>
|
||||
<h2 className="text-4xl md:text-5xl font-serif text-[#1A1A1A]">{dict.about.story.title}</h2>
|
||||
<div className="space-y-8 text-[#1A1A1A]/70 text-lg leading-relaxed italic border-l-2 border-[#C88C4B] pl-8">
|
||||
<p>{dict.about.story.p1}</p>
|
||||
<p>{dict.about.story.p2}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Right: Why Us Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="lg:col-span-5 bg-white p-10 md:p-12 rounded-[2px] shadow-2xl border border-[#1A1A1A]/5"
|
||||
>
|
||||
<h3 className="text-3xl font-serif text-[#1A1A1A] mb-8">{dict.about.why.title}</h3>
|
||||
<div className="space-y-8">
|
||||
{whyUs.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start space-x-4">
|
||||
<div className="mt-1 bg-[#C88C4B]/10 p-1 rounded-full text-[#C88C4B]">
|
||||
<Check size={16} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="font-bold text-[#1A1A1A] text-sm uppercase tracking-wider">{item.title}</p>
|
||||
<p className="text-[#1A1A1A]/60 text-sm italic">{item.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* STATS SECTION */}
|
||||
<section className="py-24 px-6 md:px-12 max-w-[1400px] mx-auto overflow-hidden">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{stats.map((stat, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: idx * 0.1 }}
|
||||
className="bg-white p-12 text-center space-y-6 rounded-[2px] shadow-sm hover:shadow-xl transition-shadow duration-500 border border-[#1A1A1A]/5 group"
|
||||
>
|
||||
<div className="flex justify-center text-[#C88C4B] group-hover:scale-110 transition-transform duration-500">
|
||||
{stat.icon}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-5xl font-serif text-[#1A1A1A]">{stat.value}</p>
|
||||
<p className="text-[11px] font-bold tracking-[0.3em] uppercase text-[#1A1A1A]/40">{stat.label}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* VISION SECTION */}
|
||||
<section className="py-32 px-6">
|
||||
<div className="max-w-4xl mx-auto bg-white p-16 md:p-24 text-center space-y-10 rounded-[2px] shadow-2xl border border-[#1A1A1A]/5">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-[12px] font-bold tracking-[0.5em] uppercase text-[#1A1A1A]/30"
|
||||
>
|
||||
{dict.about.vision.title}
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-2xl md:text-4xl font-serif leading-tight text-[#1A1A1A] italic"
|
||||
>
|
||||
{dict.about.vision.text}
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
18
app/[lang]/about/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import AboutClient from "./AboutClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
return {
|
||||
title: `${dict.about.title} - Ayris Apart`,
|
||||
description: dict.about.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function AboutPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <AboutClient lang={lang} dict={dict} />
|
||||
}
|
||||
184
app/[lang]/contact/ContactClient.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import { Mail, Phone, MapPin, Clock, ArrowUpRight, MessageCircle } from 'lucide-react'
|
||||
import Link from "next/link"
|
||||
|
||||
|
||||
|
||||
const InstagramIcon = ({ size = 20 }: { size?: number }) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect>
|
||||
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
|
||||
<line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default function ContactClient({ lang, dict }: { lang: string, dict: any }) {
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen text-[#1A1A1A]">
|
||||
|
||||
|
||||
{/* HERO SECTION */}
|
||||
<section className="pt-64 pb-32 px-6">
|
||||
<div className="max-w-[1400px] mx-auto">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, ease: [0.33, 1, 0.68, 1] }}
|
||||
className="text-[12vw] md:text-[150px] font-serif leading-[0.8] tracking-tight uppercase"
|
||||
>
|
||||
{dict.contact.title.split(' ')[0]} <br /> <span className="italic ml-[5vw]">{dict.contact.title.split(' ').slice(1).join(' ')}</span>
|
||||
</motion.h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* MINIMALIST INFO GRID */}
|
||||
<section className="py-24 px-6 md:px-12 max-w-[1400px] mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0.5 bg-[#1A1A1A]/10 border border-[#1A1A1A]/10">
|
||||
|
||||
{/* Address */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-[#FAF7F0] p-16 space-y-12 flex flex-col justify-between aspect-square"
|
||||
>
|
||||
<MapPin size={32} strokeWidth={1} className="text-[#C88C4B]" />
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-3xl font-serif">{dict.contact.visit.t}</h3>
|
||||
<p className="text-xl text-[#1A1A1A]/60 italic leading-relaxed">
|
||||
{dict.contact.visit.d}
|
||||
</p>
|
||||
<Link href="https://www.google.com/maps/search/?api=1&query=Ayris+Apart+Milas+Ören" target="_blank" className="inline-flex items-center space-x-2 text-xs font-bold uppercase tracking-[0.3em] border-b border-[#1A1A1A] pb-1">
|
||||
<span>{dict.contact.btn.map}</span>
|
||||
<ArrowUpRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Direct Connect */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-[#FAF7F0] p-16 space-y-12 flex flex-col justify-between aspect-square"
|
||||
>
|
||||
<Phone size={32} strokeWidth={1} className="text-[#C88C4B]" />
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-3xl font-serif">{dict.contact.call.t}</h3>
|
||||
<div className="space-y-2">
|
||||
<p className="text-2xl font-medium">+90 543 231 87 13</p>
|
||||
<p className="text-lg text-[#1A1A1A]/50 italic">{dict.contact.call.d}</p>
|
||||
</div>
|
||||
<Link href="tel:+905432318713" className="inline-flex items-center space-x-2 text-xs font-bold uppercase tracking-[0.3em] border-b border-[#1A1A1A] pb-1">
|
||||
<span>{dict.contact.btn.call}</span>
|
||||
<ArrowUpRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Email */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="bg-[#FAF7F0] p-16 space-y-12 flex flex-col justify-between aspect-square"
|
||||
>
|
||||
<Mail size={32} strokeWidth={1} className="text-[#C88C4B]" />
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-3xl font-serif">{dict.contact.write.t}</h3>
|
||||
<div className="space-y-2">
|
||||
<p className="text-2xl font-medium">hello@ayrisapart.com</p>
|
||||
<p className="text-lg text-[#1A1A1A]/50 italic">{dict.contact.write.d}</p>
|
||||
</div>
|
||||
<Link href="mailto:hello@ayrisapart.com" className="inline-flex items-center space-x-2 text-xs font-bold uppercase tracking-[0.3em] border-b border-[#1A1A1A] pb-1">
|
||||
<span>{dict.contact.btn.mail}</span>
|
||||
<ArrowUpRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* WhatsApp */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-[#FAF7F0] p-16 space-y-12 flex flex-col justify-between aspect-square"
|
||||
>
|
||||
<MessageCircle size={32} strokeWidth={1} className="text-[#C88C4B]" />
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-3xl font-serif">{dict.contact.wa.t}</h3>
|
||||
<p className="text-xl text-[#1A1A1A]/60 italic leading-relaxed">
|
||||
{dict.contact.wa.d}
|
||||
</p>
|
||||
<Link href="https://wa.me/905432318713" target="_blank" className="inline-flex items-center space-x-2 text-xs font-bold uppercase tracking-[0.3em] border-b border-[#1A1A1A] pb-1">
|
||||
<span>{dict.contact.btn.wa}</span>
|
||||
<ArrowUpRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Instagram */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-[#FAF7F0] p-16 space-y-12 flex flex-col justify-between aspect-square"
|
||||
>
|
||||
<InstagramIcon size={32} />
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-3xl font-serif">{dict.contact.ig.t}</h3>
|
||||
<p className="text-xl text-[#1A1A1A]/60 italic leading-relaxed">
|
||||
{dict.contact.ig.d}
|
||||
</p>
|
||||
<Link href="#" className="inline-flex items-center space-x-2 text-xs font-bold uppercase tracking-[0.3em] border-b border-[#1A1A1A] pb-1">
|
||||
<span>{dict.contact.btn.ig}</span>
|
||||
<ArrowUpRight size={14} />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Working Hours */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="bg-[#FAF7F0] p-16 space-y-12 flex flex-col justify-between aspect-square"
|
||||
>
|
||||
<Clock size={32} strokeWidth={1} className="text-[#C88C4B]" />
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-3xl font-serif">{dict.contact.hours.t}</h3>
|
||||
<div className="space-y-2">
|
||||
<p className="text-xl font-medium">{dict.contact.hours.d}</p>
|
||||
<p className="text-lg text-[#1A1A1A]/50 italic">{dict.contact.hours.sub}</p>
|
||||
</div>
|
||||
<div className="text-[10px] font-bold uppercase tracking-[0.4em] text-[#1A1A1A]/30">Open 24/7 for you</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FULL WIDTH MAP SECTION */}
|
||||
<section className="py-24">
|
||||
<div className="h-[600px] w-full relative grayscale hover:grayscale-0 transition-all duration-1000">
|
||||
<iframe
|
||||
src="https://maps.google.com/maps?q=Ayris+Apart+Milas+Ören&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
></iframe>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
18
app/[lang]/contact/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import ContactClient from "./ContactClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
return {
|
||||
title: `${dict.contact.title} - Ayris Apart`,
|
||||
description: dict.contact.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function ContactPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <ContactClient lang={lang} dict={dict} />
|
||||
}
|
||||
49
app/[lang]/layout.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Oranienbaum, Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
const oranienbaum = Oranienbaum({
|
||||
subsets: ["latin", "latin-ext"],
|
||||
weight: ["400"],
|
||||
variable: "--font-oranienbaum",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin", "latin-ext"],
|
||||
variable: "--font-inter",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Ayris - Luxury Accommodation",
|
||||
description: "Experience the ultimate comfort and luxury at Ayris.",
|
||||
};
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ lang: 'en' }, { lang: 'tr' }]
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang as 'en' | 'tr');
|
||||
|
||||
return (
|
||||
<html lang={lang} className={`${oranienbaum.variable} ${inter.variable} h-full antialiased`}>
|
||||
<body className="font-sans min-h-full flex flex-col bg-[#FAF7F0] text-[#1A1A1A]">
|
||||
<Navbar lang={lang} dict={dict} />
|
||||
{children}
|
||||
<Footer lang={lang} dict={dict} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
120
app/[lang]/news/NewsClient.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Calendar, ArrowRight } from 'lucide-react'
|
||||
|
||||
|
||||
export default function NewsClient({ lang, dict }: { lang: string, dict: any }) {
|
||||
const newsItems = [
|
||||
{
|
||||
id: 'hidden-gems-oren',
|
||||
title: dict.news_page.list.n1.title,
|
||||
excerpt: dict.news_page.list.n1.excerpt,
|
||||
image: 'https://images.unsplash.com/photo-1544124499-58912cbddaad?q=80&w=2127&auto=format&fit=crop',
|
||||
date: 'April 15, 2026',
|
||||
author: dict.news_page.list.n1.author
|
||||
},
|
||||
{
|
||||
id: 'summer-cocktails-retreat',
|
||||
title: dict.news_page.list.n2.title,
|
||||
excerpt: dict.news_page.list.n2.excerpt,
|
||||
image: 'https://images.unsplash.com/photo-1519046904884-53103b34b206?q=80&w=2073&auto=format&fit=crop',
|
||||
date: 'April 10, 2026',
|
||||
author: dict.news_page.list.n2.author
|
||||
},
|
||||
{
|
||||
id: 'luxury-interior-trends',
|
||||
title: dict.news_page.list.n3.title,
|
||||
excerpt: dict.news_page.list.n3.excerpt,
|
||||
image: 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=1964&auto=format&fit=crop',
|
||||
date: 'April 05, 2026',
|
||||
author: dict.news_page.list.n3.author
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
|
||||
|
||||
{/* HEADER SECTION */}
|
||||
<section className="pt-44 pb-24 px-6">
|
||||
<div className="max-w-5xl mx-auto text-center space-y-10">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1 }}
|
||||
className="text-7xl md:text-[120px] font-serif text-[#1A1A1A] leading-[0.9] tracking-tight uppercase"
|
||||
>
|
||||
{dict.news_page.title}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
className="text-[#1A1A1A]/70 text-lg md:text-xl max-w-3xl mx-auto font-medium leading-relaxed italic"
|
||||
>
|
||||
{dict.news_page.subtitle}
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ARTICLES GRID SECTION */}
|
||||
<section className="pb-44 px-6 md:px-12 max-w-[1400px] mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
{newsItems.map((article, idx) => (
|
||||
<motion.div
|
||||
key={article.id}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8, delay: idx * 0.15 }}
|
||||
className="group cursor-pointer"
|
||||
>
|
||||
<Link href={`/${lang}/news/${article.id}`}>
|
||||
<div className="space-y-8">
|
||||
{/* Image Card */}
|
||||
<div className="aspect-[4/5] relative overflow-hidden rounded-[2px] shadow-sm">
|
||||
<Image
|
||||
src={article.image}
|
||||
alt={article.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/5 group-hover:bg-transparent transition-colors duration-500" />
|
||||
</div>
|
||||
|
||||
{/* Metadata and Title */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center space-x-6 text-[11px] font-bold tracking-[0.3em] uppercase text-[#1A1A1A]/40 mb-2">
|
||||
<span className="flex items-center space-x-2">
|
||||
<Calendar size={14} className="opacity-50" />
|
||||
<span>{article.date}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl md:text-4xl font-serif text-[#1A1A1A] leading-tight group-hover:text-[#C88C4B] transition-colors duration-300">
|
||||
{article.title}
|
||||
</h2>
|
||||
|
||||
<p className="text-[#1A1A1A]/60 text-lg leading-relaxed italic line-clamp-2">
|
||||
{article.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="inline-flex items-center space-x-2 text-[12px] font-bold tracking-widest uppercase text-[#1A1A1A] pt-2 border-b-2 border-transparent group-hover:border-[#C88C4B] transition-all pb-1">
|
||||
<span>{dict.news_page.read}</span>
|
||||
<ArrowRight size={16} className="transform group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
157
app/[lang]/news/[slug]/NewsDetailClient.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Calendar, User, Share2, ArrowLeft } from 'lucide-react'
|
||||
|
||||
|
||||
export default function NewsDetailClient({ lang, slug, dict }: { lang: string, slug: string, dict: any }) {
|
||||
// Use dictionary instead of hardcoded data where possible
|
||||
// For demo, we still use a local map but referring to Ören
|
||||
const newsData: Record<string, any> = {
|
||||
'hidden-gems-oren': {
|
||||
title: dict.news_page.list.n1.title,
|
||||
date: 'April 15, 2026',
|
||||
author: dict.news_page.list.n1.author,
|
||||
category: 'Travel Guide',
|
||||
image: 'https://images.unsplash.com/photo-1544124499-58912cbddaad?q=80&w=2127&auto=format&fit=crop',
|
||||
content: [
|
||||
{ type: 'paragraph', text: dict.news_page.list.n1.excerpt + ' ' + (lang === 'tr' ? 'Ören’in saklı koyları, zamanın ötesinde bir huzur sunuyor. Turkuaz suların üzerinden sabah sisi kalkarken, standart haritaların ötesine bakmaya istekli olanlar için gizli sığınaklar kendilerini göstermeye başlıyor. Bugünkü yolculuğumuz bizi Ege’nin saklı kalbinde bir keşfe çıkarıyor.' : 'Beyond the crowded beaches, the secret coves of Oren offer a peace beyond time. As the morning mist lifts from the turquoise waters, hidden havens begin to reveal themselves to those willing to look beyond standard maps. Our journey today takes us on an exploration in the hidden heart of the Aegean.') },
|
||||
{ type: 'quote', text: lang === 'tr' ? 'Gerçek keşif yolculuğu yeni manzaralar aramak değil, yeni gözlere sahip olmaktan geçer.' : 'The real voyage of discovery consists not in seeking new landscapes, but in having new eyes.' },
|
||||
{ type: 'paragraph', text: lang === 'tr' ? 'Gemile koyunun sessiz kıyılarında, zeytinlikler arasında sessizce oturan antik kalıntılar bunlardan sadece biri. Bu sessiz taşların arasında yürürken, Bizanslı tüccarların yankılarını neredeyse duyabilirsiniz. Burası, tarihin, doğanın ve sessiz lüksün harmanlandığı, Ayris Apart\'ın gerçek özünü bulduğumuz yerdir.' : 'The ancient ruins sitting quietly amidst olive groves on the silent shores of Gemile Bay are just one of them. Walking among these silent stones, you can almost hear the echoes of Byzantine merchants. This is where we find the true essence of Ayris Apart, where history, nature, and silent luxury blend.' },
|
||||
{ type: 'image', url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=2073&auto=format&fit=crop' }
|
||||
]
|
||||
},
|
||||
'summer-cocktails-retreat': {
|
||||
title: dict.news_page.list.n2.title,
|
||||
date: 'April 10, 2026',
|
||||
author: dict.news_page.list.n2.author,
|
||||
category: 'Lifestyle',
|
||||
image: 'https://images.unsplash.com/photo-1519046904884-53103b34b206?q=80&w=2073&auto=format&fit=crop',
|
||||
content: [
|
||||
{ type: 'paragraph', text: dict.news_page.list.n2.excerpt }
|
||||
]
|
||||
},
|
||||
'luxury-interior-trends': {
|
||||
title: dict.news_page.list.n3.title,
|
||||
date: 'April 05, 2026',
|
||||
author: dict.news_page.list.n3.author,
|
||||
category: 'Design',
|
||||
image: 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=1964&auto=format&fit=crop',
|
||||
content: [
|
||||
{ type: 'paragraph', text: dict.news_page.list.n3.excerpt }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const post = newsData[slug] || newsData['hidden-gems-oren']
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
|
||||
|
||||
{/* HEADER SECTION */}
|
||||
<section className="pt-44 pb-20 px-6">
|
||||
<div className="max-w-4xl mx-auto text-center space-y-8">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="flex items-center justify-center space-x-6 text-[11px] font-bold tracking-[0.4em] uppercase text-[#1A1A1A]/40"
|
||||
>
|
||||
<span>{post.category}</span>
|
||||
<span className="w-1 h-1 bg-[#C88C4B] rounded-full" />
|
||||
<span>{post.date}</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-5xl md:text-7xl lg:text-[88px] font-serif text-[#1A1A1A] leading-[1.1] tracking-tight uppercase"
|
||||
>
|
||||
{post.title}
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
className="flex items-center justify-center space-x-4 pt-4"
|
||||
>
|
||||
<div className="w-12 h-12 rounded-full bg-[#1A1A1A]/5 flex items-center justify-center text-[#1A1A1A]/40 overflow-hidden">
|
||||
<User size={24} />
|
||||
</div>
|
||||
<div className="text-left leading-tight">
|
||||
<p className="text-[11px] font-bold tracking-widest uppercase text-[#1A1A1A]/30 mb-1">Written By</p>
|
||||
<p className="text-sm font-medium text-[#1A1A1A]">{post.author}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FEATURE IMAGE */}
|
||||
<section className="px-6 md:px-12 max-w-[1400px] mx-auto overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 1.05 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1.5 }}
|
||||
className="aspect-[21/9] relative rounded-[2px]"
|
||||
>
|
||||
<Image src={post.image} alt={post.title} fill className="object-cover" />
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* CONTENT SECTION */}
|
||||
<section className="py-24 px-6">
|
||||
<div className="max-w-2xl mx-auto space-y-12">
|
||||
{post.content.map((block: any, idx: number) => {
|
||||
if (block.type === 'paragraph') {
|
||||
return (
|
||||
<p key={idx} className={`text-[#1A1A1A]/80 text-xl leading-relaxed italic ${idx === 0 ? 'first-letter:text-7xl first-letter:font-serif first-letter:mr-3 first-letter:float-left first-letter:leading-[0.8] first-letter:mt-1' : ''}`}>
|
||||
{block.text}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
if (block.type === 'quote') {
|
||||
return (
|
||||
<blockquote key={idx} className="border-l-4 border-[#C88C4B] pl-8 py-4 my-16">
|
||||
<p className="text-3xl font-serif text-[#1A1A1A] leading-snug">
|
||||
“{block.text}”
|
||||
</p>
|
||||
</blockquote>
|
||||
)
|
||||
}
|
||||
if (block.type === 'image') {
|
||||
return (
|
||||
<div key={idx} className="my-16 -mx-6 md:-mx-24 aspect-video relative overflow-hidden rounded-[2px] shadow-2xl">
|
||||
<Image src={block.url} alt="Article imagery" fill className="object-cover" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
|
||||
{/* SHARE & BACK */}
|
||||
<div className="pt-16 border-t border-[#1A1A1A]/10 flex items-center justify-between">
|
||||
<Link
|
||||
href={`/${lang}/news`}
|
||||
className="inline-flex items-center space-x-2 text-[12px] font-bold tracking-widest uppercase text-[#1A1A1A]/40 hover:text-[#1A1A1A] transition-colors"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
<span>{lang === 'tr' ? 'Haberlere Dön' : 'Back to News'}</span>
|
||||
</Link>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-[11px] font-bold tracking-widest uppercase text-[#1A1A1A]/40">Share</span>
|
||||
<button className="w-10 h-10 rounded-full border border-[#1A1A1A]/10 flex items-center justify-center hover:bg-[#1A1A1A] hover:text-white transition-all">
|
||||
<Share2 size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
20
app/[lang]/news/[slug]/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import NewsDetailClient from "./NewsDetailClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string, slug: string }> }) {
|
||||
const { lang, slug } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
// Dynamic title based on slug if possible, or just section title
|
||||
return {
|
||||
title: `News - Ayris Apart`,
|
||||
description: dict.news_page.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function NewsDetailPage({ params }: { params: Promise<{ lang: string, slug: string }> }) {
|
||||
const { lang, slug } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <NewsDetailClient lang={lang} slug={slug} dict={dict} />
|
||||
}
|
||||
18
app/[lang]/news/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import NewsClient from "./NewsClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
return {
|
||||
title: `${dict.news_page.title} - Ayris Apart`,
|
||||
description: dict.news_page.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function NewsPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <NewsClient lang={lang} dict={dict} />
|
||||
}
|
||||
40
app/[lang]/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Hero from "@/components/Hero";
|
||||
import Welcome from "@/components/Welcome";
|
||||
import ShowcaseImage from "@/components/ShowcaseImage";
|
||||
import QuoteSection from "@/components/QuoteSection";
|
||||
import SuitesHighlights from "@/components/SuitesHighlights";
|
||||
import Experiences from "@/components/Experiences";
|
||||
import ScrollReveal from "@/components/ScrollReveal";
|
||||
import CallToAction from "@/components/CallToAction";
|
||||
import Amenities from "@/components/Amenities";
|
||||
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang as 'en' | 'tr');
|
||||
return {
|
||||
title: `${dict.hero.title} - ${dict.hero.desc.split('.')[0]}`,
|
||||
description: dict.hero.desc,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Home({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang as 'en' | 'tr');
|
||||
|
||||
return (
|
||||
<main className="relative w-full bg-[#FAF7F0]">
|
||||
<Hero lang={lang} dict={dict} />
|
||||
<Welcome lang={lang} dict={dict} />
|
||||
<ShowcaseImage />
|
||||
<QuoteSection />
|
||||
<SuitesHighlights lang={lang} dict={dict} />
|
||||
<Experiences />
|
||||
<ScrollReveal />
|
||||
<CallToAction lang={lang} dict={dict} />
|
||||
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
117
app/[lang]/suites/SuitesClient.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Users, BedDouble } from 'lucide-react'
|
||||
import Amenities from "@/components/Amenities"
|
||||
|
||||
export default function SuitesClient({ lang, dict }: { lang: string, dict: any }) {
|
||||
const suites = [
|
||||
{ id: 'iris', number: '01', name: dict.suites_page.list.s1.name, desc: dict.suites_page.list.s1.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606641/ayrisapart/Daire%201/photo_1_2024-04-05_12-32-09.jpg', bed: dict.suites_page.list.s1.bed, guests: dict.suites_page.list.s1.guests },
|
||||
{ id: 'electra', number: '02', name: dict.suites_page.list.s2.name, desc: dict.suites_page.list.s2.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606648/ayrisapart/Daire%202/photo_1_2024-04-05_16-04-34.jpg', bed: dict.suites_page.list.s2.bed, guests: dict.suites_page.list.s2.guests },
|
||||
{ id: 'arke', number: '03', name: dict.suites_page.list.s3.name, desc: dict.suites_page.list.s3.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606654/ayrisapart/Daire%203/photo_1_2024-04-05_16-06-09.jpg', bed: dict.suites_page.list.s3.bed, guests: dict.suites_page.list.s3.guests },
|
||||
{ id: 'harpy', number: '04', name: dict.suites_page.list.s4.name, desc: dict.suites_page.list.s4.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606661/ayrisapart/Daire%204/photo_1_2024-04-05_16-07-01.jpg', bed: dict.suites_page.list.s4.bed, guests: dict.suites_page.list.s4.guests },
|
||||
{ id: 'hydaspes', number: '05', name: dict.suites_page.list.s5.name, desc: dict.suites_page.list.s5.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606671/ayrisapart/Daire%205/photo_1_2024-05-04_15-32-44.jpg', bed: dict.suites_page.list.s5.bed, guests: dict.suites_page.list.s5.guests },
|
||||
{ id: 'zephyrus', number: '06', name: dict.suites_page.list.s6.name, desc: dict.suites_page.list.s6.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606681/ayrisapart/Daire%206/photo_1_2024-05-04_15-32-44.jpg', bed: dict.suites_page.list.s6.bed, guests: dict.suites_page.list.s6.guests },
|
||||
{ id: 'pothos', number: '07', name: dict.suites_page.list.s7.name, desc: dict.suites_page.list.s7.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606689/ayrisapart/Daire%207/photo_1_2024-05-04_15-33-34.jpg', bed: dict.suites_page.list.s7.bed, guests: dict.suites_page.list.s7.guests },
|
||||
{ id: 'thaumas', number: '08', name: dict.suites_page.list.s8.name, desc: dict.suites_page.list.s8.desc, image: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606696/ayrisapart/Daire%208/photo_1_2024-05-04_15-33-34.jpg', bed: dict.suites_page.list.s8.bed, guests: dict.suites_page.list.s8.guests },
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen z-60 ">
|
||||
|
||||
{/* HEADER SECTION */}
|
||||
<section className="pt-44 pb-20 px-6 ">
|
||||
<div className="max-w-4xl mx-auto text-center space-y-10">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
className="text-7xl md:text-[120px] font-serif text-[#1A1A1A] leading-[0.9] tracking-tight uppercase"
|
||||
>
|
||||
{dict.suites_page.title}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
className="text-[#1A1A1A]/60 text-lg md:text-xl max-w-2xl mx-auto font-medium leading-relaxed italic"
|
||||
>
|
||||
{dict.suites_page.subtitle}
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SUITES LIST SECTION */}
|
||||
<section className="pb-32 px-6 md:px-12 max-w-[1400px] mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-24">
|
||||
{suites.map((suite, idx) => (
|
||||
<motion.div
|
||||
key={suite.id}
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8, delay: (idx % 2) * 0.1 }}
|
||||
className="group cursor-pointer"
|
||||
>
|
||||
{/* Image Card */}
|
||||
<div className="aspect-[4/3] relative overflow-hidden rounded-[2px] bg-white p-0 shadow-sm">
|
||||
<Image
|
||||
src={suite.image}
|
||||
alt={suite.name}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div className="mt-10 space-y-6">
|
||||
<h2 className="text-4xl font-serif text-[#1A1A1A] uppercase tracking-tight">
|
||||
<span className="mr-4 font-sans text-xl opacity-20">{suite.number}.</span>
|
||||
{suite.name}
|
||||
</h2>
|
||||
|
||||
{/* Icons row */}
|
||||
<div className="flex items-center space-x-8 text-[#1A1A1A]/60 text-[15px] font-medium">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Users size={16} strokeWidth={1.5} className="text-[#C88C4B]" />
|
||||
<span>{suite.guests}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<BedDouble size={16} strokeWidth={1.5} className="text-[#C88C4B]" />
|
||||
<span>{suite.bed}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[#1A1A1A]/70 text-[17px] leading-relaxed italic max-w-lg">
|
||||
{suite.desc}
|
||||
</p>
|
||||
|
||||
{/* Link */}
|
||||
<Link
|
||||
href={`/${lang}/suites/${suite.id}`}
|
||||
className="inline-flex items-center space-x-3 text-[13px] font-bold tracking-[0.3em] uppercase border-b-2 border-[#1A1A1A] pb-2 group/btn"
|
||||
>
|
||||
<span className="bg-[#1A1A1A] text-white p-1 rounded-full group-hover/btn:bg-[#C88C4B] transition-colors">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" className="rotate-45">
|
||||
<line x1="12" y1="19" x2="12" y2="5" />
|
||||
<polyline points="5 12 12 5 19 12" />
|
||||
</svg>
|
||||
</span>
|
||||
<span>{dict.suites_page.details}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* GENERAL AMENITIES SECTION */}
|
||||
<section className="py-32 px-6 md:px-12 max-w-[1400px] mx-auto bg-white/30 rounded-[4px] mb-32 border border-[#1A1A1A]/5 shadow-sm">
|
||||
<Amenities dict={dict} />
|
||||
</section>
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
221
app/[lang]/suites/[id]/SuiteDetailClient.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
'use client'
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { Users, BedDouble, ArrowLeft } from 'lucide-react'
|
||||
import Amenities from "@/components/Amenities"
|
||||
|
||||
export default function SuiteDetailClient({ lang, id, dict }: { lang: string, id: string, dict: any }) {
|
||||
// Mapping the 8 mythological suites with their Cloudinary assets
|
||||
const suitesData: Record<string, any> = {
|
||||
'iris': {
|
||||
number: '01',
|
||||
name: dict.suites_page.list.s1.name,
|
||||
description: dict.suites_page.list.s1.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606641/ayrisapart/Daire%201/photo_1_2024-04-05_12-32-09.jpg',
|
||||
guests: dict.suites_page.list.s1.guests,
|
||||
bed: dict.suites_page.list.s1.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606642/ayrisapart/Daire%201/photo_2_2024-04-05_12-32-09.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606643/ayrisapart/Daire%201/photo_3_2024-04-05_12-32-09.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606644/ayrisapart/Daire%201/photo_4_2024-04-05_12-32-09.jpg'
|
||||
]
|
||||
},
|
||||
'electra': {
|
||||
number: '02',
|
||||
name: dict.suites_page.list.s2.name,
|
||||
description: dict.suites_page.list.s2.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606648/ayrisapart/Daire%202/photo_1_2024-04-05_16-04-34.jpg',
|
||||
guests: dict.suites_page.list.s2.guests,
|
||||
bed: dict.suites_page.list.s2.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606649/ayrisapart/Daire%202/photo_2_2024-04-05_16-04-34.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606650/ayrisapart/Daire%202/photo_3_2024-04-05_16-04-34.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606651/ayrisapart/Daire%202/photo_5_2024-04-05_16-04-34.jpg'
|
||||
]
|
||||
},
|
||||
'arke': {
|
||||
number: '03',
|
||||
name: dict.suites_page.list.s3.name,
|
||||
description: dict.suites_page.list.s3.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606654/ayrisapart/Daire%203/photo_1_2024-04-05_16-06-09.jpg',
|
||||
guests: dict.suites_page.list.s3.guests,
|
||||
bed: dict.suites_page.list.s3.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606655/ayrisapart/Daire%203/photo_2_2024-04-05_16-06-09.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606655/ayrisapart/Daire%203/photo_3_2024-04-05_16-06-09.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606656/ayrisapart/Daire%203/photo_4_2024-04-05_16-06-09.jpg'
|
||||
]
|
||||
},
|
||||
'harpy': {
|
||||
number: '04',
|
||||
name: dict.suites_page.list.s4.name,
|
||||
description: dict.suites_page.list.s4.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606661/ayrisapart/Daire%204/photo_1_2024-04-05_16-07-01.jpg',
|
||||
guests: dict.suites_page.list.s4.guests,
|
||||
bed: dict.suites_page.list.s4.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606662/ayrisapart/Daire%204/photo_2_2024-04-05_16-07-01.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606663/ayrisapart/Daire%204/photo_3_2024-04-05_16-07-01.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606664/ayrisapart/Daire%204/photo_5_2024-04-05_16-07-01.jpg'
|
||||
]
|
||||
},
|
||||
'hydaspes': {
|
||||
number: '05',
|
||||
name: dict.suites_page.list.s5.name,
|
||||
description: dict.suites_page.list.s5.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606671/ayrisapart/Daire%205/photo_1_2024-05-04_15-32-44.jpg',
|
||||
guests: dict.suites_page.list.s5.guests,
|
||||
bed: dict.suites_page.list.s5.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606672/ayrisapart/Daire%205/photo_2_2024-05-04_15-33-08.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606673/ayrisapart/Daire%205/photo_3_2024-05-04_15-33-08.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606674/ayrisapart/Daire%205/photo_4_2024-05-04_15-33-08.jpg'
|
||||
]
|
||||
},
|
||||
'zephyrus': {
|
||||
number: '06',
|
||||
name: dict.suites_page.list.s6.name,
|
||||
description: dict.suites_page.list.s6.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606681/ayrisapart/Daire%206/photo_1_2024-05-04_15-32-44.jpg',
|
||||
guests: dict.suites_page.list.s6.guests,
|
||||
bed: dict.suites_page.list.s6.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606682/ayrisapart/Daire%206/photo_2_2024-05-04_15-33-08.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606683/ayrisapart/Daire%206/photo_3_2024-05-04_15-33-08.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606684/ayrisapart/Daire%206/photo_5_2024-05-04_15-33-08.jpg'
|
||||
]
|
||||
},
|
||||
'pothos': {
|
||||
number: '07',
|
||||
name: dict.suites_page.list.s7.name,
|
||||
description: dict.suites_page.list.s7.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606689/ayrisapart/Daire%207/photo_1_2024-05-04_15-33-34.jpg',
|
||||
guests: dict.suites_page.list.s7.guests,
|
||||
bed: dict.suites_page.list.s7.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606690/ayrisapart/Daire%207/photo_2_2024-05-04_15-33-34.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606691/ayrisapart/Daire%207/photo_3_2024-05-04_15-33-34.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606692/ayrisapart/Daire%207/photo_5_2024-05-04_15-33-34.jpg'
|
||||
]
|
||||
},
|
||||
'thaumas': {
|
||||
number: '08',
|
||||
name: dict.suites_page.list.s8.name,
|
||||
description: dict.suites_page.list.s8.desc,
|
||||
mainImage: 'https://res.cloudinary.com/du7xohbct/image/upload/v1776606696/ayrisapart/Daire%208/photo_1_2024-05-04_15-33-34.jpg',
|
||||
guests: dict.suites_page.list.s8.guests,
|
||||
bed: dict.suites_page.list.s8.bed,
|
||||
gallery: [
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606697/ayrisapart/Daire%208/photo_2_2024-05-04_15-33-34.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606698/ayrisapart/Daire%208/photo_4_2024-05-04_15-33-34.jpg',
|
||||
'https://res.cloudinary.com/du7xohbct/image/upload/v1776606699/ayrisapart/Daire%208/photo_5_2024-05-04_15-33-34.jpg'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const suite = suitesData[id] || suitesData['iris']
|
||||
|
||||
return (
|
||||
<main className="bg-[#FAF7F0] min-h-screen">
|
||||
|
||||
{/* SECTION 1: HEADER & MAIN IMAGE */}
|
||||
<section className="pt-44 pb-20 px-6 md:px-16 max-w-[1400px] mx-auto">
|
||||
<div className="space-y-12">
|
||||
{/* Back Button */}
|
||||
<Link href={`/${lang}/suites`} className="inline-flex items-center space-x-2 text-[11px] font-bold tracking-[0.4em] uppercase text-[#1A1A1A]/40 hover:text-[#1A1A1A] transition-colors">
|
||||
<ArrowLeft size={16} />
|
||||
<span>{lang === 'tr' ? 'Tüm Odalar' : 'All Suites'}</span>
|
||||
</Link>
|
||||
|
||||
{/* Title and Brief Header */}
|
||||
<div className="space-y-8">
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-6xl md:text-[88px] font-serif text-[#1A1A1A] tracking-tight leading-none uppercase"
|
||||
>
|
||||
<span className="opacity-50 mr-4 font-sans text-4xl">{suite.number}.</span>
|
||||
{suite.name}
|
||||
</motion.h1>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-x-12 gap-y-4 text-[#1A1A1A]/70 text-[15px] font-medium">
|
||||
<div className="flex items-center space-x-2 uppercase tracking-widest">
|
||||
<Users size={18} className="text-[#C88C4B]" />
|
||||
<span>{suite.guests}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 uppercase tracking-widest">
|
||||
<BedDouble size={18} className="text-[#C88C4B]" />
|
||||
<span>{suite.bed}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Visual */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.98 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 1 }}
|
||||
className="aspect-[21/9] relative overflow-hidden rounded-[2px] shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
src={suite.mainImage}
|
||||
alt={suite.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SECTION 2: ABOUT & AMENITIES */}
|
||||
<section className="py-24 px-6 md:px-16 max-w-[1400px] mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-16 border-b border-[#1A1A1A]/10 pb-20">
|
||||
<div className="md:col-span-4">
|
||||
<h2 className="text-4xl font-serif text-[#1A1A1A] uppercase tracking-tight">{lang === 'tr' ? 'Suit Hakkında' : 'About Suite'}</h2>
|
||||
</div>
|
||||
<div className="md:col-span-8 space-y-12">
|
||||
<p className="text-[#1A1A1A]/70 text-2xl leading-relaxed italic max-w-2xl">
|
||||
{suite.description}
|
||||
</p>
|
||||
<Link
|
||||
href={`/${lang}/reservation`}
|
||||
className="inline-flex items-center space-x-3 text-[14px] font-bold tracking-[0.4em] uppercase border-b-2 border-[#1A1A1A] pb-2 group"
|
||||
>
|
||||
<span className="transform group-hover:translate-x-1 transition-transform">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="rotate-45">
|
||||
<line x1="12" y1="19" x2="12" y2="5" />
|
||||
<polyline points="5 12 12 5 19 12" />
|
||||
</svg>
|
||||
</span>
|
||||
<span>{dict.footer.book}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* INTEGRATED AMENITIES COMPONENT */}
|
||||
<div className="mt-20">
|
||||
<Amenities dict={dict} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* SECTION 3: GALLERY GRID */}
|
||||
<section className="py-24 px-6 md:px-16 max-w-[1400px] mx-auto pb-44">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="aspect-square relative overflow-hidden rounded-[2px] shadow-lg">
|
||||
<Image src={suite.gallery[0]} alt="Gallery" fill className="object-cover hover:scale-105 transition-transform duration-1000" />
|
||||
</div>
|
||||
<div className="aspect-square relative overflow-hidden rounded-[2px] shadow-lg">
|
||||
<Image src={suite.gallery[1]} alt="Gallery" fill className="object-cover hover:scale-105 transition-transform duration-1000" />
|
||||
</div>
|
||||
<div className="md:col-span-2 aspect-[21/9] relative overflow-hidden rounded-[2px] shadow-lg">
|
||||
<Image src={suite.gallery[2]} alt="Gallery" fill className="object-cover hover:scale-105 transition-transform duration-1000" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
)
|
||||
}
|
||||
19
app/[lang]/suites/[id]/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import SuiteDetailClient from "./SuiteDetailClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string, id: string }> }) {
|
||||
const { lang, id } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return {
|
||||
title: `Suites - Ayris Apart`,
|
||||
description: dict.suites_page.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SuiteDetailPage({ params }: { params: Promise<{ lang: string, id: string }> }) {
|
||||
const { lang, id } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <SuiteDetailClient lang={lang} id={id} dict={dict} />
|
||||
}
|
||||
18
app/[lang]/suites/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getDictionary } from "@/dictionaries/get-dictionary"
|
||||
import SuitesClient from "./SuitesClient"
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
return {
|
||||
title: `${dict.suites_page.title} - Ayris Apart`,
|
||||
description: dict.suites_page.subtitle,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function SuitesPage({ params }: { params: Promise<{ lang: string }> }) {
|
||||
const { lang } = await params
|
||||
const dict = await getDictionary(lang as 'en' | 'tr')
|
||||
|
||||
return <SuitesClient lang={lang} dict={dict} />
|
||||
}
|
||||
33
app/api/upload/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import cloudinary from '@/lib/cloudinary';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file') as File;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Convert file to base64
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
const base64File = `data:${file.type};base64,${buffer.toString('base64')}`;
|
||||
|
||||
// Upload to Cloudinary
|
||||
const result = await cloudinary.uploader.upload(base64File, {
|
||||
folder: 'ayris-apart', // Organize images in a specific folder
|
||||
resource_type: 'auto',
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
url: result.secure_url,
|
||||
public_id: result.public_id,
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Upload Error:', error);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-serif: var(--font-oranienbaum), serif;
|
||||
--font-sans: var(--font-inter), sans-serif;
|
||||
|
||||
--color-background: #faf7f0;
|
||||
--color-foreground: #1a1a1a;
|
||||
--color-brand: #c88c4b;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
--background: #faf7f0;
|
||||
--foreground: #1a1a1a;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-inter), sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, .font-serif {
|
||||
font-family: var(--font-oranienbaum), serif;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
65
app/page.tsx
@@ -1,65 +0,0 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
412
cloudinary-assets.json
Normal file
@@ -0,0 +1,412 @@
|
||||
[
|
||||
{
|
||||
"localPath": "Daire 1\\photo_1_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606641/ayrisapart/Daire%201/photo_1_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_1_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_2_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606642/ayrisapart/Daire%201/photo_2_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_2_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_3_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606643/ayrisapart/Daire%201/photo_3_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_3_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_4_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606644/ayrisapart/Daire%201/photo_4_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_4_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_5_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606644/ayrisapart/Daire%201/photo_5_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_5_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_6_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606645/ayrisapart/Daire%201/photo_6_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_6_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_7_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606646/ayrisapart/Daire%201/photo_7_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_7_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_8_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606646/ayrisapart/Daire%201/photo_8_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_8_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 1\\photo_9_2024-04-05_12-32-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606647/ayrisapart/Daire%201/photo_9_2024-04-05_12-32-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 1/photo_9_2024-04-05_12-32-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_1_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606648/ayrisapart/Daire%202/photo_1_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_1_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_2_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606649/ayrisapart/Daire%202/photo_2_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_2_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_3_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606650/ayrisapart/Daire%202/photo_3_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_3_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_4_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606650/ayrisapart/Daire%202/photo_4_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_4_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_5_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606651/ayrisapart/Daire%202/photo_5_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_5_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_6_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606652/ayrisapart/Daire%202/photo_6_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_6_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_7_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606652/ayrisapart/Daire%202/photo_7_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_7_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 2\\photo_8_2024-04-05_16-04-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606653/ayrisapart/Daire%202/photo_8_2024-04-05_16-04-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 2/photo_8_2024-04-05_16-04-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_1_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606654/ayrisapart/Daire%203/photo_1_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_1_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_2_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606655/ayrisapart/Daire%203/photo_2_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_2_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_3_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606655/ayrisapart/Daire%203/photo_3_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_3_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_4_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606656/ayrisapart/Daire%203/photo_4_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_4_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_5_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606657/ayrisapart/Daire%203/photo_5_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_5_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_6_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606657/ayrisapart/Daire%203/photo_6_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_6_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_7_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606658/ayrisapart/Daire%203/photo_7_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_7_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_8_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606659/ayrisapart/Daire%203/photo_8_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_8_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 3\\photo_9_2024-04-05_16-06-09.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606660/ayrisapart/Daire%203/photo_9_2024-04-05_16-06-09.jpg",
|
||||
"public_id": "ayrisapart/Daire 3/photo_9_2024-04-05_16-06-09"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_10_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606660/ayrisapart/Daire%204/photo_10_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_10_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_1_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606661/ayrisapart/Daire%204/photo_1_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_1_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_2_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606662/ayrisapart/Daire%204/photo_2_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_2_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_3_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606663/ayrisapart/Daire%204/photo_3_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_3_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_4_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606663/ayrisapart/Daire%204/photo_4_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_4_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_5_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606664/ayrisapart/Daire%204/photo_5_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_5_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_6_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606665/ayrisapart/Daire%204/photo_6_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_6_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_7_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606665/ayrisapart/Daire%204/photo_7_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_7_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_8_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606666/ayrisapart/Daire%204/photo_8_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_8_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 4\\photo_9_2024-04-05_16-07-01.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606667/ayrisapart/Daire%204/photo_9_2024-04-05_16-07-01.jpg",
|
||||
"public_id": "ayrisapart/Daire 4/photo_9_2024-04-05_16-07-01"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_10_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606668/ayrisapart/Daire%205/photo_10_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_10_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_11_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606669/ayrisapart/Daire%205/photo_11_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_11_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_12_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606669/ayrisapart/Daire%205/photo_12_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_12_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_1_2024-05-04_15-32-44.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606671/ayrisapart/Daire%205/photo_1_2024-05-04_15-32-44.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_1_2024-05-04_15-32-44"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_1_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606672/ayrisapart/Daire%205/photo_1_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_1_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_2_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606672/ayrisapart/Daire%205/photo_2_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_2_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_3_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606673/ayrisapart/Daire%205/photo_3_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_3_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_4_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606674/ayrisapart/Daire%205/photo_4_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_4_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_5_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606675/ayrisapart/Daire%205/photo_5_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_5_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_6_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606676/ayrisapart/Daire%205/photo_6_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_6_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_7_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606677/ayrisapart/Daire%205/photo_7_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_7_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_8_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606677/ayrisapart/Daire%205/photo_8_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_8_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 5\\photo_9_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606678/ayrisapart/Daire%205/photo_9_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 5/photo_9_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_10_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606679/ayrisapart/Daire%206/photo_10_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_10_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_11_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606679/ayrisapart/Daire%206/photo_11_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_11_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_12_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606680/ayrisapart/Daire%206/photo_12_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_12_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_1_2024-05-04_15-32-44.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606681/ayrisapart/Daire%206/photo_1_2024-05-04_15-32-44.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_1_2024-05-04_15-32-44"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_1_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606681/ayrisapart/Daire%206/photo_1_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_1_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_2_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606682/ayrisapart/Daire%206/photo_2_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_2_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_3_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606683/ayrisapart/Daire%206/photo_3_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_3_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_4_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606683/ayrisapart/Daire%206/photo_4_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_4_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_5_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606684/ayrisapart/Daire%206/photo_5_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_5_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_6_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606685/ayrisapart/Daire%206/photo_6_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_6_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_7_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606686/ayrisapart/Daire%206/photo_7_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_7_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_8_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606687/ayrisapart/Daire%206/photo_8_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_8_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 6\\photo_9_2024-05-04_15-33-08.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606688/ayrisapart/Daire%206/photo_9_2024-05-04_15-33-08.jpg",
|
||||
"public_id": "ayrisapart/Daire 6/photo_9_2024-05-04_15-33-08"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_10_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606688/ayrisapart/Daire%207/photo_10_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_10_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_1_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606689/ayrisapart/Daire%207/photo_1_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_1_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_2_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606690/ayrisapart/Daire%207/photo_2_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_2_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_3_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606691/ayrisapart/Daire%207/photo_3_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_3_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_4_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606691/ayrisapart/Daire%207/photo_4_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_4_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_5_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606692/ayrisapart/Daire%207/photo_5_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_5_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_6_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606693/ayrisapart/Daire%207/photo_6_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_6_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_7_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606693/ayrisapart/Daire%207/photo_7_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_7_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_8_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606694/ayrisapart/Daire%207/photo_8_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_8_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 7\\photo_9_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606695/ayrisapart/Daire%207/photo_9_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 7/photo_9_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_10_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606696/ayrisapart/Daire%208/photo_10_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_10_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_1_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606696/ayrisapart/Daire%208/photo_1_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_1_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_2_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606697/ayrisapart/Daire%208/photo_2_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_2_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_3_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606697/ayrisapart/Daire%208/photo_3_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_3_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_4_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606698/ayrisapart/Daire%208/photo_4_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_4_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_5_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606699/ayrisapart/Daire%208/photo_5_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_5_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_6_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606699/ayrisapart/Daire%208/photo_6_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_6_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_7_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606700/ayrisapart/Daire%208/photo_7_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_7_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_8_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606701/ayrisapart/Daire%208/photo_8_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_8_2024-05-04_15-33-34"
|
||||
},
|
||||
{
|
||||
"localPath": "Daire 8\\photo_9_2024-05-04_15-33-34.jpg",
|
||||
"url": "https://res.cloudinary.com/du7xohbct/image/upload/v1776606702/ayrisapart/Daire%208/photo_9_2024-05-04_15-33-34.jpg",
|
||||
"public_id": "ayrisapart/Daire 8/photo_9_2024-05-04_15-33-34"
|
||||
}
|
||||
]
|
||||
46
components/Amenities.tsx
Normal 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
@@ -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"
|
||||
>
|
||||
"{dict.footer.desc}"
|
||||
</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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
19
components/QuoteSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
76
components/ScrollReveal.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
40
components/ShowcaseImage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
112
components/SuitesHighlights.tsx
Normal 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
@@ -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>
|
||||
)
|
||||
}
|
||||
132
dictionaries/en.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"nav": {
|
||||
"about": "About",
|
||||
"suites": "Suites",
|
||||
"news": "News",
|
||||
"contact": "Contact",
|
||||
"home": "Home"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Ayris Apart",
|
||||
"desc": "In the heart of Muğla Milas Ören, discover refined suites and unforgettable comfort. Your luxury vacation awaits.",
|
||||
"explore": "Explore Suites"
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Welcome to luxury seaside suites on the shores of Oren",
|
||||
"desc": "On the shores of the Aegean Sea, we offer a perfect blend of timeless elegance and coastal serenity."
|
||||
},
|
||||
"footer": {
|
||||
"book": "Book Your Stay",
|
||||
"explore": "Explore",
|
||||
"others": "Others",
|
||||
"utility": "Utility",
|
||||
"socials": "Socials",
|
||||
"desc": "Book today and discover seaside luxury, breathtaking views, and unforgettable moments on the shores of Oren."
|
||||
},
|
||||
"contact": {
|
||||
"title": "Contact Us",
|
||||
"subtitle": "Let's Stay Connected",
|
||||
"visit": {
|
||||
"t": "Visit Us",
|
||||
"d": "Oren District, Orta Iskele Street No:51 Ayris Apart 48220 Milas/Mugla"
|
||||
},
|
||||
"call": {
|
||||
"t": "Call Us",
|
||||
"d": "We are at your service 24/7 for tranquility"
|
||||
},
|
||||
"write": {
|
||||
"t": "Write Us",
|
||||
"d": "We respond within 2 hours"
|
||||
},
|
||||
"wa": {
|
||||
"t": "WhatsApp",
|
||||
"d": "Connect with us instantly for reservations or local tips."
|
||||
},
|
||||
"ig": {
|
||||
"t": "Experience",
|
||||
"d": "Follow our daily stories and coastal inspirations."
|
||||
},
|
||||
"hours": {
|
||||
"t": "Hours",
|
||||
"d": "Monday — Sunday",
|
||||
"sub": "Check-in: 14:00 | Check-out: 11:00"
|
||||
},
|
||||
"btn": {
|
||||
"map": "Open Map",
|
||||
"call": "Call Now",
|
||||
"mail": "Send Email",
|
||||
"wa": "Start Chat",
|
||||
"ig": "Follow Us"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "About Us",
|
||||
"subtitle": "Experience unique accommodation where comfort and luxury meet with Ayris Apart.",
|
||||
"story": {
|
||||
"title": "Our Story",
|
||||
"p1": "Founded in 2021, AYRIS APART set out with the vision of offering our guests an accommodation experience in the comfort of home. Located in the quietest part of Oren, we blend our modern design approach with traditional Turkish hospitality.",
|
||||
"p2": "In our rooms where every detail is carefully thought out, we aim to provide unforgettable moments to our guests traveling for business and leisure by combining technology and comfort."
|
||||
},
|
||||
"why": {
|
||||
"title": "Why Ayris Apart?",
|
||||
"1": { "t": "Premium Location", "d": "Easy access to the sea and everywhere in the quietest part of Oren" },
|
||||
"2": { "t": "Modern Design", "d": "Comfortable and stylish rooms with contemporary architecture" },
|
||||
"3": { "t": "24/7 Service", "d": "Our experienced team is with you at every moment" },
|
||||
"4": { "t": "Secure Payment", "d": "SSL certified secure reservation system" }
|
||||
},
|
||||
"vision": {
|
||||
"title": "Our Vision",
|
||||
"text": "\"Determining the quality standard in the sector and creating memories that will add value to the lives of every guest by transforming into Turkey's most reliable apart hotel chain.\""
|
||||
}
|
||||
},
|
||||
"suites": {
|
||||
"s1": { "name": "Iris", "desc": "Our most special suite carrying the colors of the rainbow, shining with uninterrupted ocean views." },
|
||||
"s2": { "name": "Electra", "desc": "A bright living space adorned with amber tones, where peace and elegance meet." },
|
||||
"s3": { "name": "Arke", "desc": "A sanctuary that rests your soul with its design giving a sense of lightness and freedom." },
|
||||
"s4": { "name": "Harpy", "desc": "The suite that combines the power of the storm and the energy of the sea in a single panoramic view." },
|
||||
"s5": { "name": "Hydaspes", "desc": "A room designed for those seeking serenity, reflecting the fluidity and purity of water." },
|
||||
"s6": { "name": "Zephyrus", "desc": "Our breeze suite that brings the coolness of the west wind and the freshness of the garden to your balcony." },
|
||||
"s7": { "name": "Pothos", "desc": "Accommodation where you will feel luxury in every detail, where desire and passion meet aesthetics." },
|
||||
"s8": { "name": "Thaumas", "desc": "The suite with the widest-angle coastal view of Oren, where you will witness the wonders of the sea." },
|
||||
"btn": {
|
||||
"more": "Learn More"
|
||||
}
|
||||
},
|
||||
"suites_page": {
|
||||
"title": "Our Suites",
|
||||
"subtitle": "Enjoy characterful design, peaceful ocean views, and a mythological atmosphere. Each of our rooms tells its own unique story.",
|
||||
"list": {
|
||||
"s1": { "name": "Iris", "desc": "Named after the rainbow messenger, this suite comes alive every morning with colors rising from the sea.", "bed": "1 King-size bed", "guests": "2 Guests" },
|
||||
"s2": { "name": "Electra", "desc": "A comfort zone that embraces golden sunsets, enchanting with its brilliance.", "bed": "1 King-size bed", "guests": "2 Guests" },
|
||||
"s3": { "name": "Arke", "desc": "A suite that invites the light breeze of the Aegean in, standing out with its minimalist and spacious design.", "bed": "1 Queen-size bed", "guests": "2 Guests" },
|
||||
"s4": { "name": "Harpy", "desc": "A room with a dynamic atmosphere where you can listen to the song of sea waves and the wind.", "bed": "1 King-size bed", "guests": "2 Guests" },
|
||||
"s5": { "name": "Hydaspes", "desc": "Peaceful interiors adorned with the sounds of water, reflecting the stillness of ancient rivers.", "bed": "1 King-size bed", "guests": "2 Guests" },
|
||||
"s6": { "name": "Zephyrus", "desc": "An experience intertwined with nature, offering the scent of garden flowers and the coolness of the west wind.", "bed": "1 King-size bed", "guests": "2 Guests" },
|
||||
"s7": { "name": "Pothos", "desc": "A special living space designed with an aesthetic longing, bringing romance and elegance together.", "bed": "1 King-size bed", "guests": "2 Guests" },
|
||||
"s8": { "name": "Thaumas", "desc": "Our most comfortable and spacious suite, laying the wonders of the sea at your feet.", "bed": "2 King-size beds", "guests": "4 Guests" }
|
||||
},
|
||||
"details": "Explore Details"
|
||||
},
|
||||
"news_page": {
|
||||
"title": "News & Stories",
|
||||
"excerpt": "Discover stories filled with coastal charm, travel inspiration, and insider tips.",
|
||||
"read": "Read More",
|
||||
"list": {
|
||||
"n1": { "title": "Hidden Gems of Oren", "excerpt": "Discover secret coves known only to locals.", "author": "Ayris Team" },
|
||||
"n2": { "title": "Summer Cocktails", "excerpt": "Meet our new season menu.", "author": "Chef Rez" },
|
||||
"n3": { "title": "Luxury Interior Trends", "excerpt": "The design story of Ayris Apart.", "author": "Design Team" }
|
||||
}
|
||||
},
|
||||
"amenities": {
|
||||
"title": "Amenities",
|
||||
"cctv": "CCTV Security",
|
||||
"fire_alarm": "Fire Alarm",
|
||||
"parking": "Free Parking",
|
||||
"double_bed": "Double Bed",
|
||||
"kitchen": "Kitchen",
|
||||
"ac": "Air Conditioning",
|
||||
"single_bed": "Single Bed",
|
||||
"fire_ext": "Fire Extinguisher",
|
||||
"wifi": "Free Wi-Fi"
|
||||
}
|
||||
}
|
||||
9
dictionaries/get-dictionary.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'server-only'
|
||||
|
||||
const dictionaries = {
|
||||
en: () => import('./en.json').then((module) => module.default),
|
||||
tr: () => import('./tr.json').then((module) => module.default),
|
||||
}
|
||||
|
||||
export const getDictionary = async (locale: 'en' | 'tr') =>
|
||||
dictionaries[locale]?.() ?? dictionaries.en()
|
||||
132
dictionaries/tr.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"nav": {
|
||||
"about": "Hakkımızda",
|
||||
"suites": "Odalar",
|
||||
"news": "Haberler",
|
||||
"contact": "İletişim",
|
||||
"home": "Anasayfa"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Ayris Apart",
|
||||
"desc": "Muğla Milas Ören'in kalbinde, rafine suitler ve unutulmaz bir konforu keşfedin. Lüks tatiliniz sizi bekliyor.",
|
||||
"explore": "Odaları Keşfedin"
|
||||
},
|
||||
"welcome": {
|
||||
"title": "Ören'in kıyısında lüks deniz suitlerine hoş geldiniz",
|
||||
"desc": "Ege Denizi'nin kıyısında, zamansız zarafet ve sahil huzurunun mükemmel bir karışımını sunuyoruz."
|
||||
},
|
||||
"footer": {
|
||||
"book": "Rezervasyon Yapın",
|
||||
"explore": "Keşfedin",
|
||||
"others": "Diğer",
|
||||
"utility": "Yardımcı",
|
||||
"socials": "Sosyal Medya",
|
||||
"desc": "Bugün rezervasyon yapın ve Ören kıyılarında deniz kenarı lüksünü, nefes kesen manzaraları ve unutulmaz anları keşfedin."
|
||||
},
|
||||
"contact": {
|
||||
"title": "Bize Ulaşın",
|
||||
"subtitle": "Bağlantıda Kalalım",
|
||||
"visit": {
|
||||
"t": "Bizi Ziyaret Edin",
|
||||
"d": "Ören Mahallesi, Orta İskele Caddesi No:51 Ayris Apart 48220 Milas/Muğla"
|
||||
},
|
||||
"call": {
|
||||
"t": "Bizi Arayın",
|
||||
"d": "Sakiniyet için 7/24 hizmetinizdeyiz"
|
||||
},
|
||||
"write": {
|
||||
"t": "Bize Yazın",
|
||||
"d": "2 saat içinde yanıtlıyoruz"
|
||||
},
|
||||
"wa": {
|
||||
"t": "WhatsApp",
|
||||
"d": "Rezervasyon veya yerel ipuçları için anında bize ulaşın."
|
||||
},
|
||||
"ig": {
|
||||
"t": "Deneyim",
|
||||
"d": "Günlük hikayelerimizi ve sahil ilhamlarımızı takip edin."
|
||||
},
|
||||
"hours": {
|
||||
"t": "Saatler",
|
||||
"d": "Pazartesi — Pazar",
|
||||
"sub": "Giriş: 14:00 | Çıkış: 11:00"
|
||||
},
|
||||
"btn": {
|
||||
"map": "Haritayı Aç",
|
||||
"call": "Hemen Ara",
|
||||
"mail": "E-posta Gönder",
|
||||
"wa": "Sohbeti Başlat",
|
||||
"ig": "Bizi Takip Et"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Hakkımızda",
|
||||
"subtitle": "Ayris Apart ile konfor ve lüksün buluştuğu eşsiz konaklama deneyimi yaşayın.",
|
||||
"story": {
|
||||
"title": "Bizim Hikayemiz",
|
||||
"p1": "2021 yılında kurulan AYRİS APART, misafirlerimize ev konforunda konaklama deneyimi sunma vizyonuyla yola çıktı. Ören'in en sakin bölgesinde yer alan konumu ile, modern tasarım anlayışımızı geleneksel Türk misafirperverliği ile harmanlıyoruz.",
|
||||
"p2": "Her detayın özenle düşünüldüğü odalarımızda, teknoloji ve konforu bir araya getirerek, iş ve tatil amaçlı seyahat eden misafirlerimize unutulmaz anlar yaşatmayı hedefliyoruz."
|
||||
},
|
||||
"why": {
|
||||
"title": "Neden Ayris Apart?",
|
||||
"1": { "t": "Premium Konum", "d": "Ören'in en sakin bölgesinde, denize ve her yere kolay ulaşım imkanı" },
|
||||
"2": { "t": "Modern Tasarım", "d": "Çağdaş mimariye sahip, konforlu ve şık odalar" },
|
||||
"3": { "t": "24/7 Hizmet", "d": "Her an yanınızda olan deneyimli ekibimiz" },
|
||||
"4": { "t": "Güvenli Ödeme", "d": "SSL sertifikalı güvenli rezervasyon sistemi" }
|
||||
},
|
||||
"vision": {
|
||||
"title": "Vizyonumuz",
|
||||
"text": "“Türkiye'nin en güvenilir apart otel zincirine dönüşerek, her misafirimizin hayatına değer katacak anılar oluşturmak ve sektörde kalite standardını belirlemek.”"
|
||||
}
|
||||
},
|
||||
"suites": {
|
||||
"s1": { "name": "Iris", "desc": "Gökkuşağının renklerini taşıyan, kesintisiz deniz manzarasıyla parlayan en özel suitimiz." },
|
||||
"s2": { "name": "Electra", "desc": "Kehribar tonlarıyla bezenmiş, huzur ve zarafetin buluştuğu aydınlık bir yaşam alanı." },
|
||||
"s3": { "name": "Arke", "desc": "Hafiflik ve özgürlük hissi veren tasarımıyla, ruhunuzu dinlendiren bir sığınak." },
|
||||
"s4": { "name": "Harpy", "desc": "Fırtınanın gücünü ve denizin enerjisini tek bir panoramik manzarada birleştiren suit." },
|
||||
"s5": { "name": "Hydaspes", "desc": "Suyun akışkanlığını ve saflığını yansıtan, dinginlik arayanlar için tasarlanmış oda." },
|
||||
"s6": { "name": "Zephyrus", "desc": "Batı rüzgarının serinliğini ve bahçenin ferahlığını balkonunuza taşıyan meltem suitimiz." },
|
||||
"s7": { "name": "Pothos", "desc": "Arzu ve tutkunun estetikle buluştuğu, her detayında lüksü hissedeceğiniz konaklama." },
|
||||
"s8": { "name": "Thaumas", "desc": "Denizin mucizelerine tanıklık edeceğiniz, Ören'in en geniş açılı sahil manzarasına sahip suit." },
|
||||
"btn": {
|
||||
"more": "Daha Fazla"
|
||||
}
|
||||
},
|
||||
"suites_page": {
|
||||
"title": "Odalarımız",
|
||||
"subtitle": "Karakterli tasarımın, huzurlu okyanus manzaralarının ve mitolojik bir atmosferin tadını çıkarın. Her odamız kendine has bir hikaye anlatır.",
|
||||
"list": {
|
||||
"s1": { "name": "Iris", "desc": "Gökkuşağı postacısının adını taşıyan bu suit, her sabah denizden doğan renklerle canlanır.", "bed": "1 King-size yatak", "guests": "2 Misafir" },
|
||||
"s2": { "name": "Electra", "desc": "Altın sarısı gün batımlarını kucaklayan, parlaklığıyla büyüleyen bir konfor alanı.", "bed": "1 King-size yatak", "guests": "2 Misafir" },
|
||||
"s3": { "name": "Arke", "desc": "Ege'nin hafif meltemini içeri davet eden, minimalist ve ferah tasarımıyla öne çıkan suit.", "bed": "1 Queen-size yatak", "guests": "2 Misafir" },
|
||||
"s4": { "name": "Harpy", "desc": "Deniz dalgalarının ve rüzgarın şarkısını dinleyebileceğiniz, dinamik bir atmosfere sahip oda.", "bed": "1 King-size yatak", "guests": "2 Misafir" },
|
||||
"s5": { "name": "Hydaspes", "desc": "Antik nehirlerin dinginliğini yansıtan, huzur dolu su sesleriyle bezenmiş iç mekanlar.", "bed": "1 King-size yatak", "guests": "2 Misafir" },
|
||||
"s6": { "name": "Zephyrus", "desc": "Bahçe çiçeklerinin kokusunu ve batı rüzgarının serinliğini sunan, doğayla iç içe bir deneyim.", "bed": "1 King-size yatak", "guests": "2 Misafir" },
|
||||
"s7": { "name": "Pothos", "desc": "Estetik bir özlemle tasarlanmış, romantizm ve şıklığı bir araya getiren özel yaşam alanı.", "bed": "1 King-size yatak", "guests": "2 Misafir" },
|
||||
"s8": { "name": "Thaumas", "desc": "Denizin harikalarını ayaklarınızın altına seren, en üst düzey konforlu ve geniş suitimiz.", "bed": "2 King-size yatak", "guests": "4 Misafir" }
|
||||
},
|
||||
"details": "Detayları Keşfet"
|
||||
},
|
||||
"news_page": {
|
||||
"title": "Haberler & Yazılar",
|
||||
"excerpt": "Sahil cazibesi, seyahat ilhamı ve içeriden ipuçları ile dolu hikayeleri keşfedin.",
|
||||
"read": "Yazıyı Oku",
|
||||
"list": {
|
||||
"n1": { "title": "Ören'in Gizli Cevherleri", "excerpt": "Sadece yerellerin bildiği gizli koyları keşfedin.", "author": "Ayris Ekibi" },
|
||||
"n2": { "title": "Yaz Kokteylleri", "excerpt": "Yeni sezon menümüzle tanışın.", "author": "Şef Rez" },
|
||||
"n3": { "title": "Lüks İç Mekan Trendleri", "excerpt": "Ayris Apart'ın tasarım hikayesi.", "author": "Tasarım Ekibi" }
|
||||
}
|
||||
},
|
||||
"amenities": {
|
||||
"title": "Sunulan Olanaklar",
|
||||
"cctv": "Güvenlik Kamerası",
|
||||
"fire_alarm": "Yangın Dedektörü",
|
||||
"parking": "Ücretsiz Otopark",
|
||||
"double_bed": "Çift Kişilik Yatak",
|
||||
"kitchen": "Mutfak",
|
||||
"ac": "Klima",
|
||||
"single_bed": "Tek Kişilik Yatak",
|
||||
"fire_ext": "Yangın Tüpü",
|
||||
"wifi": "Ücretsiz Wi-Fi"
|
||||
}
|
||||
}
|
||||
11
lib/cloudinary.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { v2 as cloudinary } from 'cloudinary';
|
||||
|
||||
// Configure Cloudinary with environment variables
|
||||
cloudinary.config({
|
||||
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
|
||||
api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
|
||||
api_secret: process.env.NEXT_PUBLIC_CLOUDINARY_API_SECRET,
|
||||
secure: true,
|
||||
});
|
||||
|
||||
export default cloudinary;
|
||||
26
middleware.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { NextResponse, type NextRequest } from 'next/server'
|
||||
|
||||
let locales = ['en', 'tr']
|
||||
let defaultLocale = 'tr'
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
// Check if there is any supported locale in the pathname
|
||||
const { pathname } = request.nextUrl
|
||||
const pathnameHasLocale = locales.some(
|
||||
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
||||
)
|
||||
|
||||
if (pathnameHasLocale) return
|
||||
|
||||
// Redirect if there is no locale
|
||||
request.nextUrl.pathname = `/${defaultLocale}${pathname}`
|
||||
// e.g. incoming is /about -> /tr/about
|
||||
return NextResponse.redirect(request.nextUrl)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Skip all internal paths (_next)
|
||||
'/((?!api|_next/static|_next/image|favicon.ico|logo.png|images|.*\\..*).*)',
|
||||
],
|
||||
}
|
||||
@@ -1,7 +1,18 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "res.cloudinary.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
158
package-lock.json
generated
@@ -8,7 +8,12 @@
|
||||
"name": "ayrisapart",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"cloudinary": "^2.9.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^1.8.0",
|
||||
"next": "16.2.4",
|
||||
"next-cloudinary": "^6.17.5",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
@@ -276,6 +281,63 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudinary-util/types": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@cloudinary-util/types/-/types-1.5.10.tgz",
|
||||
"integrity": "sha512-n5lrm7SdAXhgWEbkSJKHZGnaoO9G/g4WYS6HYnq/k4nLj79sYfQZOoKjyR8hF2iyLRdLkT+qlk68RNFFv5tKew==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cloudinary-util/url-loader": {
|
||||
"version": "5.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@cloudinary-util/url-loader/-/url-loader-5.10.4.tgz",
|
||||
"integrity": "sha512-gHkdvOaV+rlcwuIT7Vqd0ts/H5bsH4+bwFten/gIZ8oRjzdTBvgIY3R6F8bbJt0pFIEfpFEQLe4rPkl0NNqEWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cloudinary-util/types": "1.5.10",
|
||||
"@cloudinary-util/util": "3.3.2",
|
||||
"@cloudinary/url-gen": "1.15.0",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudinary-util/url-loader/node_modules/@cloudinary-util/util": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@cloudinary-util/util/-/util-3.3.2.tgz",
|
||||
"integrity": "sha512-Cc0iFxzfl7fcOXuznpeZFGYC885Of/vDgccRDnhTe/8Rf8YKv2PjLtezyo0VgmdA/CpeZy29NCXAsf6liokbwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cloudinary-util/url-loader/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudinary-util/util": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudinary-util/util/-/util-4.0.0.tgz",
|
||||
"integrity": "sha512-S4xcou/3A7l5o+bcKlw2VHBNgwups7/0lbVDT/cO5YmtrcEYXgj6LGmwnjvpTm/x571VPVN8x5jWdT3rLZiKJQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cloudinary/transformation-builder-sdk": {
|
||||
"version": "1.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@cloudinary/transformation-builder-sdk/-/transformation-builder-sdk-1.21.2.tgz",
|
||||
"integrity": "sha512-ehOgKUaP+Nvuf7B0TosmB8iilL0kdiVjzjl8tIK06cjvsNnwSJI3xP9nEJmKkvqNxwwFwvYXT+mxUTqnSv9JOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cloudinary/url-gen": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudinary/url-gen": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudinary/url-gen/-/url-gen-1.15.0.tgz",
|
||||
"integrity": "sha512-bjU67eZxLUgoRy/Plli4TQio7q6P31OYqnEgXxeN9TKXrzr6h0DeEdIUhKI9gy3HkEBWXWWJIPh7j7gkOJPnyA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cloudinary/transformation-builder-sdk": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
@@ -2715,6 +2777,18 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cloudinary": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.9.0.tgz",
|
||||
"integrity": "sha512-F3iKMOy4y0zy0bi5JBp94SC7HY7i/ImfTPSUV07iJmRzH1Iz8WavFfOlJTR1zvYM/xKGoiGZ3my/zy64In0IQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=9"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -2916,6 +2990,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -3706,6 +3792,33 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
||||
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.38.0",
|
||||
"motion-utils": "^12.36.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -4936,6 +5049,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@@ -4966,6 +5085,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz",
|
||||
"integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -5033,6 +5161,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
||||
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.36.0"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.36.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
||||
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -5134,6 +5277,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-cloudinary": {
|
||||
"version": "6.17.5",
|
||||
"resolved": "https://registry.npmjs.org/next-cloudinary/-/next-cloudinary-6.17.5.tgz",
|
||||
"integrity": "sha512-YIyIWw5Ds30f4rnED+E9ssLUd94FOPTtbkO2KUvOcw9z4irOEIpb1goxMxbn2EUBnIGQOd6uUf1gFizjME7pMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cloudinary-util/types": "1.5.10",
|
||||
"@cloudinary-util/url-loader": "5.10.4",
|
||||
"@cloudinary-util/util": "4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12 || ^13 || ^14 || >=15.0.0-rc || ^15",
|
||||
"react": "^17 || ^18 || >=19.0.0-beta || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"cloudinary": "^2.9.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^1.8.0",
|
||||
"next": "16.2.4",
|
||||
"next-cloudinary": "^6.17.5",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
|
||||
BIN
public/ayrisapart/Daire 1/photo_1_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/ayrisapart/Daire 1/photo_2_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
public/ayrisapart/Daire 1/photo_3_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
public/ayrisapart/Daire 1/photo_4_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
public/ayrisapart/Daire 1/photo_5_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
public/ayrisapart/Daire 1/photo_6_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 1/photo_7_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
public/ayrisapart/Daire 1/photo_8_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
public/ayrisapart/Daire 1/photo_9_2024-04-05_12-32-09.jpg
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
public/ayrisapart/Daire 2/photo_1_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
public/ayrisapart/Daire 2/photo_2_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
public/ayrisapart/Daire 2/photo_3_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
public/ayrisapart/Daire 2/photo_4_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
public/ayrisapart/Daire 2/photo_5_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/ayrisapart/Daire 2/photo_6_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
public/ayrisapart/Daire 2/photo_7_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
public/ayrisapart/Daire 2/photo_8_2024-04-05_16-04-34.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
public/ayrisapart/Daire 3/photo_1_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
public/ayrisapart/Daire 3/photo_2_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/ayrisapart/Daire 3/photo_3_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
public/ayrisapart/Daire 3/photo_4_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/ayrisapart/Daire 3/photo_5_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
public/ayrisapart/Daire 3/photo_6_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
public/ayrisapart/Daire 3/photo_7_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
public/ayrisapart/Daire 3/photo_8_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
public/ayrisapart/Daire 3/photo_9_2024-04-05_16-06-09.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 4/photo_10_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/ayrisapart/Daire 4/photo_1_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
public/ayrisapart/Daire 4/photo_2_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/ayrisapart/Daire 4/photo_3_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/ayrisapart/Daire 4/photo_4_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/ayrisapart/Daire 4/photo_5_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
public/ayrisapart/Daire 4/photo_6_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 4/photo_7_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
public/ayrisapart/Daire 4/photo_8_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
public/ayrisapart/Daire 4/photo_9_2024-04-05_16-07-01.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
public/ayrisapart/Daire 5/photo_10_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 5/photo_11_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/ayrisapart/Daire 5/photo_12_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
public/ayrisapart/Daire 5/photo_1_2024-05-04_15-32-44.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 5/photo_1_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
public/ayrisapart/Daire 5/photo_2_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/ayrisapart/Daire 5/photo_3_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/ayrisapart/Daire 5/photo_4_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
public/ayrisapart/Daire 5/photo_5_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
public/ayrisapart/Daire 5/photo_6_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
public/ayrisapart/Daire 5/photo_7_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
public/ayrisapart/Daire 5/photo_8_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
public/ayrisapart/Daire 5/photo_9_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/ayrisapart/Daire 6/photo_10_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 6/photo_11_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/ayrisapart/Daire 6/photo_12_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
public/ayrisapart/Daire 6/photo_1_2024-05-04_15-32-44.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/ayrisapart/Daire 6/photo_1_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
public/ayrisapart/Daire 6/photo_2_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/ayrisapart/Daire 6/photo_3_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/ayrisapart/Daire 6/photo_4_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
public/ayrisapart/Daire 6/photo_5_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
public/ayrisapart/Daire 6/photo_6_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
public/ayrisapart/Daire 6/photo_7_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
public/ayrisapart/Daire 6/photo_8_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
public/ayrisapart/Daire 6/photo_9_2024-05-04_15-33-08.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |