feat: complete initial project setup with i18n and standalone config

This commit is contained in:
2026-04-15 22:36:48 +03:00
parent 66f0657fc2
commit de89099b4f
154 changed files with 3350 additions and 119 deletions

61
Dockerfile Normal file
View File

@@ -0,0 +1,61 @@
# 1. Base image
FROM node:20-alpine AS base
# 2. Dependencies
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci --legacy-peer-deps
# 3. Builder
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Environment variables must be present at build time for Next.js
ARG NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
ARG CLOUDINARY_API_KEY
ARG CLOUDINARY_API_SECRET
ENV NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=$NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME
ENV CLOUDINARY_API_KEY=$CLOUDINARY_API_KEY
ENV CLOUDINARY_API_SECRET=$CLOUDINARY_API_SECRET
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# 4. Runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
# set hostname to localhost
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@@ -0,0 +1,82 @@
import { useTranslations } from 'next-intl';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import Image from 'next/image';
export default function AboutPage() {
const t = useTranslations('About');
return (
<main className="min-h-screen bg-bone">
<Navbar />
{/* Hero Section */}
<section className="pt-40 pb-20 px-6 max-w-7xl mx-auto">
<div className="text-center mb-16">
<span className="text-salmakis-blue font-bold tracking-[0.3em] uppercase text-xs mb-4 block">
Salmakis Villas
</span>
<h1 className="text-5xl md:text-7xl font-serif text-aegean-dark mb-8">
{t('title')}
</h1>
<div className="w-24 h-[1px] bg-aegean-dark/20 mx-auto" />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8 text-aegean-dark/80 text-lg leading-relaxed font-sans">
<p className="font-bold text-aegean-dark">
{t('paragraph1')}
</p>
<p>
{t('paragraph2')}
</p>
<p>
{t('paragraph3')}
</p>
<p className="bg-sand/10 p-6 rounded-2xl border border-sand/30 italic text-aegean-dark">
{t('paragraph4')}
</p>
</div>
<div className="relative aspect-square rounded-3xl overflow-hidden shadow-2xl">
<Image
src="https://images.unsplash.com/photo-1542718610-a1d656d1884c?q=80&w=2070&auto=format&fit=crop"
alt="Dereköy Villas"
fill
className="object-cover"
/>
</div>
</div>
</section>
{/* Location Details Section */}
<section className="py-20 bg-aegean-dark text-bone">
<div className="max-w-7xl mx-auto px-6 grid grid-cols-1 md:grid-cols-3 gap-12 text-center">
<div className="space-y-4">
<div className="text-4xl font-serif text-sand">01</div>
<h3 className="text-xl font-serif">{t('location_title')}</h3>
<p className="text-sm opacity-60 leading-relaxed font-sans">
Ortakent, Turgutreis ve Gümüşlük üçgeninin tam merkezinde huzurlu bir konum.
</p>
</div>
<div className="space-y-4">
<div className="text-4xl font-serif text-sand">02</div>
<h3 className="text-xl font-serif">{t('tradition_title')}</h3>
<p className="text-sm opacity-60 leading-relaxed font-sans">
Geleneksel Bodrum mimarisi ile modern konforun buluştuğu taş yapılar.
</p>
</div>
<div className="space-y-4">
<div className="text-4xl font-serif text-sand">03</div>
<h3 className="text-xl font-serif">{t('service_title')}</h3>
<p className="text-sm opacity-60 leading-relaxed font-sans">
Aynı arazide ikamet eden villa görevlisi ile 24 saat kesintisiz hizmet.
</p>
</div>
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -0,0 +1,143 @@
'use client';
import { useTranslations } from 'next-intl';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import { MapPin, Phone, Mail, Fax, Smartphone, Send } from 'lucide-react';
export default function ContactPage() {
const t = useTranslations('Contact');
return (
<main className="min-h-screen bg-bone">
<Navbar />
{/* Hero Header */}
<section className="pt-40 pb-20 px-6 max-w-7xl mx-auto text-center">
<span className="text-salmakis-blue font-bold tracking-[0.3em] uppercase text-xs mb-4 block">
Salmakis Villas
</span>
<h1 className="text-5xl md:text-7xl font-serif text-aegean-dark mb-6">
{t('title')}
</h1>
<p className="text-aegean-dark/60 max-w-2xl mx-auto text-lg leading-relaxed">
{t('description')}
</p>
<div className="w-24 h-[1px] bg-aegean-dark/20 mx-auto mt-12" />
</section>
{/* Contact Content */}
<section className="pb-24 px-6 max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-20">
{/* Contact info cards */}
<div className="space-y-12">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="p-8 bg-white rounded-3xl border border-aegean-dark/5 shadow-sm space-y-4">
<div className="w-10 h-10 rounded-xl bg-salmakis-blue/10 flex items-center justify-center text-salmakis-blue">
<MapPin className="w-5 h-5" />
</div>
<h3 className="text-lg font-serif text-aegean-dark">{t('address')}</h3>
<p className="text-xs uppercase font-bold tracking-widest text-aegean-dark/50 leading-loose">
Dereköy yolu, Bademlik Mevkii Küme evleri No:24 Gürece Ortakent Bodrum/Muğla Türkiye
</p>
</div>
<div className="p-8 bg-white rounded-3xl border border-aegean-dark/5 shadow-sm space-y-4">
<div className="w-10 h-10 rounded-xl bg-salmakis-blue/10 flex items-center justify-center text-salmakis-blue">
<Phone className="w-5 h-5" />
</div>
<h3 className="text-lg font-serif text-aegean-dark">{t('phone')}</h3>
<div className="text-sm font-medium text-aegean-dark/70 space-y-1">
<p>+90 252 316 28 77</p>
<p>+90 252 316 27 38</p>
<p>+90 252 316 65 09</p>
</div>
</div>
<div className="p-8 bg-white rounded-3xl border border-aegean-dark/5 shadow-sm space-y-4">
<div className="w-10 h-10 rounded-xl bg-salmakis-blue/10 flex items-center justify-center text-salmakis-blue">
<Smartphone className="w-5 h-5" />
</div>
<h3 className="text-lg font-serif text-aegean-dark">{t('mobile')}</h3>
<p className="text-sm font-medium text-aegean-dark/70">
+90 532 731 78 04
</p>
</div>
<div className="p-8 bg-white rounded-3xl border border-aegean-dark/5 shadow-sm space-y-4">
<div className="w-10 h-10 rounded-xl bg-salmakis-blue/10 flex items-center justify-center text-salmakis-blue">
<Mail className="w-5 h-5" />
</div>
<h3 className="text-lg font-serif text-aegean-dark">{t('email')}</h3>
<p className="text-sm font-medium text-aegean-dark/70">
info@salmakisvillas.com
</p>
</div>
</div>
<div className="p-8 bg-aegean-dark text-bone rounded-3xl shadow-xl flex items-center justify-between">
<div className="space-y-2">
<h3 className="text-xl font-serif text-sand">{t('fax')}</h3>
<p className="text-sm opacity-60">+90 252 316 27 37</p>
</div>
<Fax className="w-8 h-8 opacity-20" />
</div>
</div>
{/* Contact Form */}
<div className="bg-white p-10 md:p-12 rounded-[2rem] shadow-2xl border border-aegean-dark/5">
<h2 className="text-3xl font-serif text-aegean-dark mb-8">Bize Mesaj Gönderin</h2>
<form className="space-y-6">
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-aegean-dark/40 ml-1">
{t('form_name')}
</label>
<input
type="text"
className="w-full bg-bone/50 border border-aegean-dark/5 rounded-2xl px-6 py-4 focus:outline-none focus:ring-2 focus:ring-salmakis-blue/20 transition-all font-sans"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-aegean-dark/40 ml-1">
{t('form_email')}
</label>
<input
type="email"
className="w-full bg-bone/50 border border-aegean-dark/5 rounded-2xl px-6 py-4 focus:outline-none focus:ring-2 focus:ring-salmakis-blue/20 transition-all font-sans"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-bold uppercase tracking-widest text-aegean-dark/40 ml-1">
{t('form_message')}
</label>
<textarea
rows={5}
className="w-full bg-bone/50 border border-aegean-dark/5 rounded-2xl px-6 py-4 focus:outline-none focus:ring-2 focus:ring-salmakis-blue/20 transition-all font-sans resize-none"
/>
</div>
<button
type="submit"
className="w-full bg-aegean-dark hover:bg-salmakis-blue text-bone py-5 rounded-2xl font-bold uppercase tracking-[0.2em] text-xs flex items-center justify-center space-x-3 transition-all transform hover:-translate-y-1"
>
<span>{t('form_submit')}</span>
<Send className="w-4 h-4" />
</button>
</form>
</div>
</section>
{/* Map Section */}
<section className="h-[500px] w-full bg-aegean-dark/10 relative">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m13!1d3183.1!2d27.35!3d37.05!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zMzfCsDAzJzAwLjAiTiAyN8KwMjEnMDAuMCJF!5e0!3m2!1sen!2str!4v1650000000000!5m2!1sen!2str"
className="w-full h-full grayscale opacity-80"
style={{ border: 0 }}
allowFullScreen={true}
loading="lazy"
/>
<div className="absolute inset-0 pointer-events-none shadow-[inset_0_0_100px_rgba(0,0,0,0.1)]" />
</section>
<Footer />
</main>
);
}

66
app/[locale]/layout.tsx Normal file
View File

@@ -0,0 +1,66 @@
import type { Metadata } from "next";
import { Cormorant_Garamond, Oswald } from "next/font/google";
import "../globals.css";
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, getTranslations } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
const cormorant = Cormorant_Garamond({
subsets: ["latin", "latin-ext"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-serif",
display: 'swap',
});
const oswald = Oswald({
subsets: ["latin", "latin-ext"],
variable: "--font-sans",
display: 'swap',
});
export async function generateMetadata({
params
}: {
params: Promise<{ locale: string }>
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Index' });
return {
title: {
default: t('title') + " | Salmakis Villas",
template: "%s | Salmakis Villas"
},
description: t('description'),
};
}
export default async function RootLayout({
children,
params
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
// Ensure that the incoming `locale` is valid
if (!routing.locales.includes(locale as "en" | "tr")) {
notFound();
}
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
return (
<html lang={locale} className={`${cormorant.variable} ${oswald.variable} scroll-smooth`}>
<body className="font-sans bg-bone text-aegean-dark min-h-screen flex flex-col">
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}

78
app/[locale]/page.tsx Normal file
View File

@@ -0,0 +1,78 @@
import Navbar from '@/components/Navbar';
import Hero from '@/components/Hero';
import VillaCard from '@/components/VillaCard';
import Footer from '@/components/Footer';
import { villas } from '@/data/villas';
export default function HomePage() {
return (
<main className="min-h-screen">
<Navbar />
<Hero />
{/* Featured Villas Section */}
<section id="villas" className="py-24 px-6 bg-bone">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<span className="text-salmakis-blue font-bold tracking-[0.3em] uppercase text-xs mb-4 block">
Özel Seçki
</span>
<h2 className="text-4xl md:text-6xl font-serif text-aegean-dark mb-6">
Lüks Konaklama Seçenekleri
</h2>
<div className="w-24 h-[1px] bg-aegean-dark/20 mx-auto" />
</div>
<div className="flex flex-wrap justify-center gap-8 md:gap-12">
{villas.map((villa, index) => (
<div key={villa.id} className="w-full md:w-[calc(50%-1.5rem)] lg:w-[calc(33.33%-2.5rem)] max-w-sm">
<VillaCard villa={villa} index={index} />
</div>
))}
</div>
</div>
</section>
{/* About Preview Section */}
<section className="py-24 px-6 bg-aegean-dark text-bone overflow-hidden relative">
<div className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
<div>
<h2 className="text-4xl md:text-6xl font-serif mb-8 leading-tight">
Ege'nin Kalbinde <br />
Unutulmaz Bir Tatil
</h2>
<p className="text-bone/60 text-lg mb-10 leading-relaxed max-w-lg">
Salmakis Villas olarak, Bodrum'un en özel koylarında, size özel havuzlu ve lüks donanımlı villalarımızla hayalinizdeki tatili gerçeğe dönüştürüyoruz.
</p>
<div className="flex space-x-12">
<div>
<span className="block text-3xl font-serif text-salmakis-blue mb-1">10+</span>
<span className="text-[10px] uppercase font-bold tracking-widest opacity-50">Özel Villa</span>
</div>
<div>
<span className="block text-3xl font-serif text-salmakis-blue mb-1">500+</span>
<span className="text-[10px] uppercase font-bold tracking-widest opacity-50">Mutlu Misafir</span>
</div>
<div>
<span className="block text-3xl font-serif text-salmakis-blue mb-1">12 Ay</span>
<span className="text-[10px] uppercase font-bold tracking-widest opacity-50">Kesintisiz Hizmet</span>
</div>
</div>
</div>
<div className="relative">
<div className="aspect-video rounded-2xl overflow-hidden border border-bone/10 shadow-2xl skew-y-3 hover:skew-y-0 transition-transform duration-700">
<img
src="https://images.unsplash.com/photo-1512917774080-9991f1c4c750?q=80&w=2070&auto=format&fit=crop"
className="w-full h-full object-cover"
alt="Background"
/>
</div>
<div className="absolute -bottom-8 -left-8 w-32 h-32 bg-salmakis-blue/20 rounded-full blur-3xl" />
</div>
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -0,0 +1,147 @@
import { villas } from '@/data/villas';
import { notFound } from 'next/navigation';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
import { Bed, Users, Droplets, Wind, Wifi, Utensils, MapPin, Calendar, MessageCircle, Mail, Phone } from 'lucide-react';
import VillaGallery from '@/components/VillaGallery';
export default async function VillaDetailPage({
params
}: {
params: Promise<{ locale: string, slug: string }>
}) {
const { slug, locale } = await params;
const villa = villas.find((v) => v.slug === slug);
if (!villa) {
notFound();
}
const l = locale as 'tr' | 'en';
return (
<main className="bg-bone min-h-screen">
<Navbar />
{/* Hero / Header */}
<section className="pt-32 pb-12 px-6 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6 mb-8">
<div>
<div className="flex items-center text-salmakis-blue font-bold tracking-widest text-xs mb-2 uppercase">
<MapPin className="w-3 h-3 mr-1" />
{villa.location}
</div>
<h1 className="text-4xl md:text-6xl font-serif text-aegean-dark">{villa.name}</h1>
</div>
<div className="text-right">
<span className="text-[10px] uppercase font-bold text-aegean-dark/40 block mb-1">Gecelik Başlangıç</span>
<span className="text-3xl font-serif font-bold text-salmakis-blue">{villa.priceLow}</span>
</div>
</div>
{/* Dynamic Gallery with Lightbox */}
<VillaGallery images={villa.images} name={villa.name} />
</section>
{/* Main Content Area */}
<section className="py-12 px-6 max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-16">
{/* Description & Specs */}
<div className="lg:col-span-2">
<div className="prose prose-lg max-w-none text-aegean-dark/80 mb-16 font-sans">
<h2 className="text-3xl font-serif text-aegean-dark mb-6">Villa Hakkında</h2>
<p className="leading-relaxed whitespace-pre-line">
{villa.description[l]}
</p>
</div>
<div className="mb-16">
<h2 className="text-3xl font-serif text-aegean-dark mb-8">Özellikler & Olanaklar</h2>
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
{villa.amenities[l].map((amenity, idx) => (
<div key={idx} className="flex items-center space-x-3 text-aegean-dark/70">
<div className="w-8 h-8 rounded-lg bg-salmakis-blue/10 flex items-center justify-center text-salmakis-blue">
<Droplets className="w-4 h-4" />
</div>
<span className="text-sm font-medium">{amenity}</span>
</div>
))}
</div>
</div>
{/* Pricing Table */}
<div className="bg-white rounded-3xl p-8 border border-aegean-dark/5 shadow-sm">
<h2 className="text-2xl font-serif text-aegean-dark mb-6 flex items-center">
<Calendar className="w-5 h-5 mr-3 text-salmakis-blue" />
Sezonluk Fiyatlandırma
</h2>
<div className="overflow-x-auto">
<table className="w-full text-left">
<thead>
<tr className="border-b border-aegean-dark/5">
<th className="py-4 text-xs font-bold uppercase tracking-[0.2em] text-aegean-dark/40">Dönem</th>
<th className="py-4 text-xs font-bold uppercase tracking-[0.2em] text-aegean-dark/40 text-right">Gecelik</th>
</tr>
</thead>
<tbody className="text-sm">
<tr className="border-b border-aegean-dark/5">
<td className="py-4 font-medium text-aegean-dark/70">Nisan - Mayıs (Düşük Sezon)</td>
<td className="py-4 text-right font-bold text-aegean-dark">{villa.priceLow}</td>
</tr>
<tr className="border-b border-aegean-dark/5">
<td className="py-4 font-medium text-aegean-dark/70">Haziran - Eylül (Yüksek Sezon)</td>
<td className="py-4 text-right font-bold text-aegean-dark">{villa.priceHigh}</td>
</tr>
<tr>
<td className="py-4 font-medium text-aegean-dark/70">Ekim - Kasım (Orta Sezon)</td>
<td className="py-4 text-right font-bold text-aegean-dark">{Math.round((villa.priceLow + villa.priceHigh) / 1.5 / 50) * 50}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{/* Sidebar / Booking */}
<div className="lg:col-span-1">
<div className="sticky top-32 space-y-6">
<div className="bg-aegean-dark text-bone p-8 rounded-3xl shadow-xl overflow-hidden relative">
<div className="relative z-10 text-center">
<h3 className="text-2xl font-serif mb-4 text-sand">Rezervasyon Talebi</h3>
<p className="text-bone/70 text-sm mb-8 leading-relaxed">Bu villa için uygunluk durumunu sorgulamak ve detaylı bilgi almak için bizimle iletişime geçin.</p>
<div className="space-y-4">
<a
href={`https://wa.me/905000000000?text=${encodeURIComponent(`${villa.name} hakkında bilgi almak istiyorum.`)}`}
className="flex items-center justify-center space-x-3 w-full py-4 bg-[#25D366] hover:bg-[#20bd5a] text-white rounded-2xl font-bold transition-all shadow-lg shadow-green-900/20"
>
<MessageCircle className="w-5 h-5" />
<span>WhatsApp'tan Sorun</span>
</a>
<a
href="tel:+902523162877"
className="flex items-center justify-center space-x-3 w-full py-4 bg-white/10 hover:bg-white/20 text-white rounded-2xl font-bold border border-white/10 transition-all"
>
<Phone className="w-5 h-5" />
<span>Hemen Arayın</span>
</a>
</div>
</div>
{/* Decorative background circle */}
<div className="absolute -bottom-10 -right-10 w-40 h-40 bg-salmakis-blue opacity-10 rounded-full blur-3xl" />
</div>
<div className="bg-sand/10 p-6 rounded-3xl border border-sand/20">
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] text-aegean-dark/60 mb-4">Minimum Konaklama</h4>
<p className="text-sm font-medium text-aegean-dark/80 leading-relaxed">
Yüksek sezonda <span className="text-aegean-dark font-bold underline decoration-sand/50 decoration-2 underline-offset-4">7 gece</span>, diğer dönemlerde <span className="text-aegean-dark font-bold underline decoration-sand/50 decoration-2 underline-offset-4">3 gecedir</span>.
</p>
</div>
</div>
</div>
</section>
<Footer />
</main>
);
}

View File

@@ -1,26 +1,46 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
@theme {
--color-bone: #F9F8F6;
--color-sand: #E6D5B8;
--color-salmakis-blue: #20B2AA;
--color-aegean-dark: #1A1A1A;
--font-serif: "Cormorant Garamond", serif;
--font-sans: "Oswald", sans-serif;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
:root {
--background: #F9F8F6;
--foreground: #1A1A1A;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--background: #121212;
--foreground: #F9F8F6;
}
}
body {
background: var(--background);
background-color: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, .font-serif {
font-family: var(--font-serif);
}
.glass-nav {
@apply backdrop-blur-md bg-white/70 border-b border-white/20;
}
.villa-card {
@apply transition-all duration-500 hover:shadow-2xl hover:-translate-y-2;
}

View File

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

View File

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

123
components/Footer.tsx Normal file
View File

@@ -0,0 +1,123 @@
'use client';
import { Link } from '@/i18n/routing';
export default function Footer() {
return (
<footer className="bg-aegean-dark text-bone py-20 px-6 font-sans">
<div className="max-w-7xl mx-auto">
{/* Top Centered Section */}
<div className="text-center mb-20">
<h2 className="text-5xl md:text-7xl font-serif tracking-[0.2em] text-sand mb-4">SALMAKIS</h2>
<p className="text-[10px] tracking-[0.4em] uppercase opacity-60 mb-10">
A Heritage of Excellence Since 1980.
</p>
<div className="space-y-2 text-sm opacity-80 font-medium">
<p>+90 252 316 65 06</p>
<p>08:00 - 20:00</p>
<p>salmakis@salmakis.com.tr</p>
</div>
</div>
{/* Three Columns Section */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-16 text-center mb-24">
{/* Column 1: Resort */}
<div className="space-y-6">
<h3 className="text-xl font-serif text-sand tracking-wide">Salmakis Resort</h3>
<div className="text-[10px] leading-relaxed tracking-widest uppercase opacity-60 space-y-1">
<p>BARDAKÇI KOYU</p>
<p>BODRUM/MUĞLA/TURKEY</p>
</div>
<div className="text-xs space-y-1 opacity-80">
<p>+90 252 316 65 06</p>
<p>+90 252 316 65 07</p>
<p>+90 252 316 65 11</p>
</div>
<p className="text-[10px] opacity-40">salmakis@salmakis.com.tr</p>
</div>
{/* Column 2: Villas */}
<div className="space-y-6">
<h3 className="text-xl font-serif text-sand tracking-wide">Salmakis Villas</h3>
<div className="text-[10px] leading-relaxed tracking-widest uppercase opacity-60 space-y-1">
<p>BADEMLİK MEVKİİ</p>
<p>KÜME EVLERİ NO 24</p>
<p>BODRUM/MUĞLA/TURKEY</p>
</div>
<div className="text-xs space-y-1 opacity-80">
<p>+90 252 316 27 38</p>
<p>+90 252 316 28 77</p>
<p>+90 532 731 78 04</p>
<p>+90 252 316 27 37</p>
</div>
<p className="text-[10px] opacity-40">info@salmakisvillas.com</p>
</div>
{/* Column 3: Yachting */}
<div className="space-y-6">
<h3 className="text-xl font-serif text-sand tracking-wide">Salmakis Yachting</h3>
<div className="text-[10px] leading-relaxed tracking-widest uppercase opacity-60 space-y-1">
<p>KUMBAHÇE MAH. İÇMELER CAD.</p>
<p>NO 28/1</p>
<p>BODRUM/MUĞLA/TURKEY</p>
</div>
<div className="text-xs space-y-1 opacity-80">
<p>+90 252 316 27 38</p>
<p>+90 252 316 28 77</p>
<p>+90 252 316 27 37</p>
</div>
<p className="text-[10px] opacity-40">info@salmakisyachting.com</p>
</div>
</div>
{/* Socials Section */}
<div className="flex flex-col md:flex-row justify-end items-center gap-6 mb-12 border-t border-white/5 pt-12">
<span className="text-[10px] font-bold tracking-[0.3em] uppercase opacity-60">FOLLOW US</span>
<div className="flex items-center space-x-6">
{/* Instagram */}
<svg className="w-5 h-5 opacity-80 hover:opacity-100 cursor-pointer transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" 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>
{/* Facebook */}
<svg className="w-5 h-5 opacity-80 hover:opacity-100 cursor-pointer transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
</svg>
{/* Play/Video */}
<svg className="w-5 h-5 opacity-80 hover:opacity-100 cursor-pointer transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polygon points="10 8 16 12 10 16 10 8"></polygon>
</svg>
{/* LinkedIn */}
<svg className="w-5 h-5 opacity-80 hover:opacity-100 cursor-pointer transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path>
<rect x="2" y="9" width="4" height="12"></rect>
<circle cx="4" cy="4" r="2"></circle>
</svg>
</div>
</div>
{/* Bottom Bar */}
<div className="flex flex-col md:flex-row justify-between items-center gap-8 pt-12 border-t border-white/5">
<div className="flex flex-wrap justify-center gap-8 text-[9px] font-bold tracking-[0.2em] uppercase opacity-50">
<Link href="#" className="hover:opacity-100 transition-opacity">LEGAL & PRIVACY</Link>
<Link href="#" className="hover:opacity-100 transition-opacity">COOKIES</Link>
<Link href="#" className="hover:opacity-100 transition-opacity">SITEMAP</Link>
<Link href="#" className="hover:opacity-100 transition-opacity">COOKIES SETTINGS</Link>
</div>
<div className="text-right space-y-2">
<p className="text-[9px] font-bold tracking-[0.2em] uppercase opacity-40">
© SALMAKIS 2026 ALL RIGHTS RESERVED
</p>
<p className="text-[9px] font-bold tracking-[0.2em] uppercase opacity-40">
CREATED BY <span className="text-sand/80">AYRISTECH</span>
</p>
</div>
</div>
</div>
</footer>
);
}

74
components/Hero.tsx Normal file
View File

@@ -0,0 +1,74 @@
'use client';
import { motion } from 'framer-motion';
import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/routing';
import { ChevronRight } from 'lucide-react';
import Image from 'next/image';
export default function Hero() {
const t = useTranslations('Index');
return (
<section className="relative h-screen flex items-center justify-center overflow-hidden">
{/* Background with Overlay */}
<div className="absolute inset-0 z-0">
<Image
src="https://images.unsplash.com/photo-1542718610-a1d656d1884c?q=80&w=2070&auto=format&fit=crop"
alt="Luxury Villa"
fill
priority
className="object-cover scale-105"
/>
<div className="absolute inset-0 bg-black/30 bg-gradient-to-b from-black/40 via-transparent to-bone/10" />
</div>
{/* Content */}
<div className="relative z-10 text-center px-6 max-w-4xl">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<span className="inline-block mb-4 text-bone text-sm font-bold tracking-[0.3em] uppercase opacity-90">
Salmakis Collection
</span>
<h1 className="text-5xl md:text-8xl font-serif text-bone leading-tight mb-6">
{t('title')}
</h1>
<p className="text-lg md:text-xl text-bone/90 font-sans max-w-2xl mx-auto mb-10 leading-relaxed">
{t('description')}
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
href="/#villas"
className="group relative px-8 py-4 bg-salmakis-blue text-white rounded-full font-bold overflow-hidden transition-all duration-300 hover:shadow-xl hover:shadow-salmakis-blue/20"
>
<span className="relative z-10 flex items-center space-x-2">
<span>{t('explore')}</span>
<ChevronRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</span>
</Link>
<Link
href="/contact"
className="px-8 py-4 bg-white/10 backdrop-blur-md border border-white/20 text-white rounded-full font-bold hover:bg-white/20 transition-all duration-300"
>
İletişime Geç
</Link>
</div>
</motion.div>
</div>
{/* Scroll indicator */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1, duration: 1 }}
className="absolute bottom-10 left-1/2 -translate-x-1/2"
>
<div className="w-[1px] h-20 bg-gradient-to-b from-white to-transparent" />
</motion.div>
</section>
);
}

109
components/Navbar.tsx Normal file
View File

@@ -0,0 +1,109 @@
'use client';
import { useTranslations, useLocale } from 'next-intl';
import { Link, usePathname, useRouter } from '@/i18n/routing';
import { useState, useEffect } from 'react';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { Menu, X, Globe } from 'lucide-react';
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export default function Navbar() {
const t = useTranslations('Navbar');
const locale = useLocale();
const pathname = usePathname();
const router = useRouter();
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const toggleLanguage = () => {
const nextLocale = locale === 'tr' ? 'en' : 'tr';
router.replace(pathname, { locale: nextLocale });
};
const navLinks = [
{ href: '/', label: t('villas') },
{ href: '/about', label: t('about') },
{ href: '/contact', label: t('contact') },
];
return (
<nav
className={cn(
'fixed top-0 left-0 right-0 z-50 transition-all duration-300 px-6 py-4',
isScrolled ? 'glass-nav py-3' : 'bg-transparent'
)}
>
<div className="max-w-7xl mx-auto flex items-center justify-between">
<Link href="/" className="text-2xl font-serif font-bold tracking-tight text-aegean-dark">
SALMAKIS <span className="text-salmakis-blue">VILLAS</span>
</Link>
{/* Desktop Nav */}
<div className="hidden md:flex items-center space-x-8">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href as any}
className="text-sm font-medium hover:text-salmakis-blue transition-colors"
>
{link.label}
</Link>
))}
<button
onClick={toggleLanguage}
className="flex items-center space-x-1 text-xs font-bold uppercase tracking-widest border border-aegean-dark/20 px-3 py-1 rounded-full hover:bg-aegean-dark hover:text-bone transition-all"
>
<Globe className="w-3 h-3" />
<span>{locale === 'tr' ? 'EN' : 'TR'}</span>
</button>
</div>
{/* Mobile Toggle */}
<button
className="md:hidden text-aegean-dark"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
{isMobileMenuOpen ? <X /> : <Menu />}
</button>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="md:hidden absolute top-full left-0 right-0 bg-bone border-b border-aegean-dark/10 p-6 flex flex-col space-y-4 animate-in fade-in slide-in-from-top-4">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href as any}
className="text-lg font-serif"
onClick={() => setIsMobileMenuOpen(false)}
>
{link.label}
</Link>
))}
<button
onClick={() => {
toggleLanguage();
setIsMobileMenuOpen(false);
}}
className="flex items-center space-x-2 text-sm font-bold uppercase py-2"
>
<Globe className="w-4 h-4" />
<span>{locale === 'tr' ? 'English' : 'Türkçe'}</span>
</button>
</div>
)}
</nav>
);
}

76
components/VillaCard.tsx Normal file
View File

@@ -0,0 +1,76 @@
'use client';
import { motion } from 'framer-motion';
import { Villa } from '@/data/villas';
import { Link } from '@/i18n/routing';
import { Users, Bed, Droplets, MapPin } from 'lucide-react';
import Image from 'next/image';
interface VillaCardProps {
villa: Villa;
index: number;
}
export default function VillaCard({ villa, index }: VillaCardProps) {
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: index * 0.1 }}
className="villa-card group bg-white rounded-2xl overflow-hidden shadow-sm"
>
<Link href={`/villas/${villa.slug}` as any}>
<div className="relative aspect-[4/5] overflow-hidden">
<Image
src={villa.images[0]}
alt={villa.name}
fill
className="object-cover transition-transform duration-700 group-hover:scale-110"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
<div className="absolute top-4 left-4 bg-white/90 backdrop-blur-sm px-3 py-1 rounded-full text-[10px] font-bold tracking-widest uppercase text-aegean-dark">
Portfolio
</div>
<div className="absolute bottom-0 left-0 right-0 p-6 bg-gradient-to-t from-black/60 to-transparent">
<div className="flex items-center text-white/90 text-xs mb-1">
<MapPin className="w-3 h-3 mr-1" />
{villa.location}
</div>
<h3 className="text-2xl font-serif text-white">{villa.name}</h3>
</div>
</div>
</Link>
<div className="p-6">
<div className="flex justify-between items-center mb-6 border-b border-aegean-dark/5 pb-4">
<div className="flex items-center space-x-4">
<div className="flex flex-col items-center">
<Users className="w-4 h-4 text-salmakis-blue mb-1" />
<span className="text-[10px] uppercase font-bold text-aegean-dark/40">{villa.capacity} Kişi</span>
</div>
<div className="flex flex-col items-center">
<Bed className="w-4 h-4 text-salmakis-blue mb-1" />
<span className="text-[10px] uppercase font-bold text-aegean-dark/40">{villa.bedrooms} Oda</span>
</div>
<div className="flex flex-col items-center">
<Droplets className="w-4 h-4 text-salmakis-blue mb-1" />
<span className="text-[10px] uppercase font-bold text-aegean-dark/40">Havuz</span>
</div>
</div>
<div className="text-right">
<span className="text-[10px] uppercase font-bold text-aegean-dark/40 block">Gecelik</span>
<span className="text-lg font-serif font-bold text-salmakis-blue">{villa.priceLow} - {villa.priceHigh}</span>
</div>
</div>
<Link
href={`/villas/${villa.slug}` as any}
className="w-full block py-3 text-center rounded-xl border border-aegean-dark/10 text-sm font-bold uppercase tracking-widest bg-aegean-dark text-white hover:bg-salmakis-blue transition-all duration-300 shadow-lg shadow-aegean-dark/10"
>
Detayları İncele
</Link>
</div>
</motion.div>
);
}

168
components/VillaGallery.tsx Normal file
View File

@@ -0,0 +1,168 @@
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { X, ChevronLeft, ChevronRight, Maximize2 } from 'lucide-react';
import Image from 'next/image';
interface VillaGalleryProps {
images: string[];
name: string;
}
export default function VillaGallery({ images, name }: VillaGalleryProps) {
const [isOpen, setIsOpen] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
const openLightbox = (index: number) => {
setCurrentIndex(index);
setIsOpen(true);
document.body.style.overflow = 'hidden';
};
const closeLightbox = () => {
setIsOpen(false);
document.body.style.overflow = 'auto';
};
const nextImage = () => {
setCurrentIndex((prev) => (prev + 1) % images.length);
};
const prevImage = () => {
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
};
return (
<>
<div className="grid grid-cols-1 md:grid-cols-4 grid-rows-2 h-[400px] md:h-[600px] gap-4 rounded-3xl overflow-hidden shadow-2xl">
{/* Main large image */}
<div
className="md:col-span-2 md:row-span-2 relative group overflow-hidden cursor-zoom-in"
onClick={() => openLightbox(0)}
>
<Image
src={images[0]}
fill
className="object-cover transition-transform duration-700 group-hover:scale-105"
alt={`${name} 1`}
priority
/>
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<Maximize2 className="text-white w-8 h-8" />
</div>
</div>
{/* Second image */}
<div
className="md:col-span-1 relative group overflow-hidden cursor-zoom-in"
onClick={() => openLightbox(1)}
>
{images[1] ? (
<Image src={images[1]} fill className="object-cover transition-transform duration-700 group-hover:scale-105" alt={`${name} 2`} />
) : (
<div className="w-full h-full bg-aegean-dark/5" />
)}
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<Maximize2 className="text-white w-6 h-6" />
</div>
</div>
{/* Third image */}
<div
className="md:col-span-1 relative group overflow-hidden cursor-zoom-in"
onClick={() => openLightbox(2)}
>
{images[2] ? (
<Image src={images[2]} fill className="object-cover transition-transform duration-700 group-hover:scale-105" alt={`${name} 3`} />
) : (
<div className="w-full h-full bg-aegean-dark/5" />
)}
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<Maximize2 className="text-white w-6 h-6" />
</div>
</div>
{/* Fourth image with overlay if more exists */}
<div
className="md:col-span-2 relative group overflow-hidden cursor-zoom-in"
onClick={() => openLightbox(3)}
>
{images[3] ? (
<Image src={images[3]} fill className="object-cover transition-transform duration-700 group-hover:scale-105" alt={`${name} 4`} />
) : (
<div className="w-full h-full bg-aegean-dark/5" />
)}
{images.length > 4 && (
<div className="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white backdrop-blur-[2px] group-hover:bg-black/40 transition-all">
<span className="text-3xl font-serif">+{images.length - 4}</span>
<span className="text-[10px] font-bold uppercase tracking-widest mt-2">Daha Fazla Fotoğraf</span>
</div>
)}
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
{! (images.length > 4) && <Maximize2 className="text-white w-8 h-8" />}
</div>
</div>
</div>
{/* Lightbox Overlay */}
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100] bg-black/95 flex items-center justify-center p-4 md:p-8"
>
<button
onClick={closeLightbox}
className="absolute top-8 right-8 text-white/70 hover:text-white z-50 transition-colors"
>
<X className="w-8 h-8" />
</button>
<button
onClick={prevImage}
className="absolute left-4 md:left-8 text-white/70 hover:text-white z-50 p-2 rounded-full hover:bg-white/10 transition-all"
>
<ChevronLeft className="w-10 h-10" />
</button>
<button
onClick={nextImage}
className="absolute right-4 md:right-8 text-white/70 hover:text-white z-50 p-2 rounded-full hover:bg-white/10 transition-all"
>
<ChevronRight className="w-10 h-10" />
</button>
<motion.div
key={currentIndex}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="relative w-full h-full max-w-6xl max-h-[80vh]"
>
{images[currentIndex] ? (
<Image
src={images[currentIndex]}
fill
className="object-contain"
alt={`${name} Full`}
quality={100}
/>
) : (
<div className="flex items-center justify-center h-full text-white/50">Görsel Yüklenemedi</div>
)}
</motion.div>
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 text-white/60 text-sm font-medium tracking-widest uppercase">
{currentIndex + 1} / {images.length} {name}
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}

286
data/villas.ts Normal file
View File

@@ -0,0 +1,286 @@
export interface Villa {
id: string;
name: string;
slug: string;
shortDescription: {
tr: string;
en: string;
};
description: {
tr: string;
en: string;
};
images: string[];
bedrooms: number;
bathrooms: number;
capacity: number;
priceLow: number;
priceHigh: number;
features: string[];
amenities: {
tr: string[];
en: string[];
};
distanceToSea: string;
location: string;
}
export const villas: Villa[] = [
{
id: "meyra",
name: "Villa Meyra",
slug: "villa-meyra",
shortDescription: {
tr: "Dereköyde doğayla iç içe, geleneksel taş yapı ve huzurlu bir yaşam.",
en: "A traditional stone building in Dereköy, intertwined with nature and peace."
},
description: {
tr: "Gümüşlük yolu üzerinde Dereköyde yer alan villalarımız, tam olarak Ortakent, Turgutreis ve Gümüşlük üçgeninin ortasında yer almaktadır. Zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer alan, müstakil ve her villanın bahçesinde havuzu bulunan, geleneksel tarzda inşa ve dekore edilmiş bu iki katlı taş villalarımız, özellikle şehrin gürültüsünden uzak ama Bodrumun önde gelen çekici tatil beldelerine kısa bir sürede ulaşmak isteyen misafirler için doğru bir seçimdir.",
en: "Located in Dereköy on the road to Gümüşlük, our villas are perfectly centered between Ortakent, Turgutreis, and Gümüşlük. Situated on a large estate covered with olive trees, bougainvillea, and flowers, these two-story stone villas are built and decorated in a traditional style. Each villa has its own private garden and pool, making them an ideal choice for guests who want to stay away from the city's noise but still be close to Bodrum's leading attractive holiday resorts."
},
images: [
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166923/salvilla/villa-meyra/leila-1.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166925/salvilla/villa-meyra/leila-10.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166928/salvilla/villa-meyra/leila-11.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166930/salvilla/villa-meyra/leila-12.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166933/salvilla/villa-meyra/leila-14.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166936/salvilla/villa-meyra/leila-16.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166939/salvilla/villa-meyra/leila-18.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166942/salvilla/villa-meyra/leila-19.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166945/salvilla/villa-meyra/leila-2.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166947/salvilla/villa-meyra/leila-20.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166949/salvilla/villa-meyra/leila-21.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166952/salvilla/villa-meyra/leila-22.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166955/salvilla/villa-meyra/leila-24.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166957/salvilla/villa-meyra/leila-26.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166960/salvilla/villa-meyra/leila-28.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166962/salvilla/villa-meyra/leila-29.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166966/salvilla/villa-meyra/leila-4.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166969/salvilla/villa-meyra/leila-5.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166972/salvilla/villa-meyra/leila-6.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166973/salvilla/villa-meyra/leila-7.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166975/salvilla/villa-meyra/leila-8.png",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166977/salvilla/villa-meyra/leila-9.png"
],
bedrooms: 3,
bathrooms: 2,
capacity: 7,
priceLow: 1100,
priceHigh: 2500,
features: ["pool", "fireplace", "wifi", "ac", "alarm", "bbq"],
amenities: {
tr: ["Özel Havuz", "Taş Barbekü", "Şömine", "Güvenlik Alarmı", "Oyun Konsolu", "Bulaşık Makinası"],
en: ["Private Pool", "Stone BBQ", "Fireplace", "Security Alarm", "Game Console", "Dishwasher"]
},
distanceToSea: "4km",
location: "Dereköy, Bodrum"
},
{
id: "melda",
name: "Villa Melda",
slug: "villa-melda",
shortDescription: {
tr: "Zeytin ağaçları arasında, 4 kişilik butik taş villa deneyimi.",
en: "Boutique stone villa experience for 4 guests among olive trees."
},
description: {
tr: "Dereköyde zeytin ağaçları ve begonvillerle çevrili, geleneksel taş mimarisiyle inşa edilmiş Villa Melda, huzur arayan çekirdek aileler için idealdir. Tam donanımlı mutfağı, özel havuzu ve korunaklı bahçesiyle Bodrum'un karmaşasından uzak ama cazibe merkezlerine bir o kadar yakın bir konaklama sunar.",
en: "Built with traditional stone architecture surrounded by olive trees and bougainvillea in Dereköy, Villa Melda is ideal for small families seeking peace. With its fully equipped kitchen, private pool, and sheltered garden, it offers accommodation far from the chaos of Bodrum but close to its attractions."
},
images: [
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166899/salvilla/villa-melda/melda-36-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166900/salvilla/villa-melda/melda-37-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166901/salvilla/villa-melda/melda-38-800x550_1.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166902/salvilla/villa-melda/melda-38-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166903/salvilla/villa-melda/melda-39-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166904/salvilla/villa-melda/melda-40-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166905/salvilla/villa-melda/melda-41-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166906/salvilla/villa-melda/melda-42-800x550_1.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166908/salvilla/villa-melda/melda-42-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166909/salvilla/villa-melda/melda-43-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166910/salvilla/villa-melda/melda-44-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166911/salvilla/villa-melda/melda-45-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166911/salvilla/villa-melda/melda-49-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166912/salvilla/villa-melda/melda-52-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166914/salvilla/villa-melda/melda-53-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166915/salvilla/villa-melda/melda-55-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166915/salvilla/villa-melda/melda-57-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166916/salvilla/villa-melda/melda-58-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166917/salvilla/villa-melda/melda-60-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166918/salvilla/villa-melda/melda-62-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166919/salvilla/villa-melda/melda-63-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166919/salvilla/villa-melda/melda-65-scaled-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166920/salvilla/villa-melda/melda-67-scaled-800x550.jpg"
],
bedrooms: 2,
bathrooms: 2,
capacity: 4,
priceLow: 1100,
priceHigh: 2500,
features: ["pool", "fireplace", "wifi", "ac", "alarm"],
amenities: {
tr: ["Özel Havuz", "Taş Barbekü", "Şömine", "Güvenlik Alarmı", "Geniş Bahçe"],
en: ["Private Pool", "Stone BBQ", "Fireplace", "Security Alarm", "Large Garden"]
},
distanceToSea: "4km",
location: "Dereköy, Bodrum"
},
{
id: "su",
name: "Villa Su",
slug: "villa-su",
shortDescription: {
tr: "Geniş bahçeli ve havuzlu, 6 kişilik konforlu tatil evi.",
en: "A comfortable vacation home for 6 with a large garden and pool."
},
description: {
tr: "Aynı arazi içindeki 4 taş villadan biri olan Villa Su, geniş aileler ve arkadaş grupları için tasarlanmıştır. Doğa manzaralı havuz başı, güneşlenme alanları ve keyifli akşam yemekleri için gölgelikli terasıyla misafirlerine unutulmaz bir Ege tatili vaat eder.",
en: "One of the 4 stone villas on the same estate, Villa Su is designed for large families and groups of friends. With its nature-view poolside, sunbathing areas, and shaded terrace for pleasant dinners, it promises guests an unforgettable Aegean vacation."
},
images: [
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166978/salvilla/villa-su/SU-1-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166980/salvilla/villa-su/SU-11-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166981/salvilla/villa-su/SU-12-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166981/salvilla/villa-su/SU-13-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166982/salvilla/villa-su/SU-14-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166983/salvilla/villa-su/SU-16-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166984/salvilla/villa-su/SU-17-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166985/salvilla/villa-su/SU-18-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166985/salvilla/villa-su/SU-2-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166986/salvilla/villa-su/SU-20-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166987/salvilla/villa-su/SU-22-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166988/salvilla/villa-su/SU-24-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166988/salvilla/villa-su/SU-25-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166989/salvilla/villa-su/SU-26-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166990/salvilla/villa-su/SU-27-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166991/salvilla/villa-su/SU-28-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166992/salvilla/villa-su/SU-29-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166993/salvilla/villa-su/SU-3-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166994/salvilla/villa-su/SU-30-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166994/salvilla/villa-su/SU-31-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166995/salvilla/villa-su/SU-4-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166996/salvilla/villa-su/SU-5-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166997/salvilla/villa-su/SU-7-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166998/salvilla/villa-su/SU-8-800x550.jpg"
],
bedrooms: 3,
bathrooms: 2,
capacity: 6,
priceLow: 1100,
priceHigh: 2500,
features: ["pool", "fireplace", "wifi", "ac", "alarm"],
amenities: {
tr: ["Özel Havuz", "Taş Barbekü", "Şömine", "LCD TV", "Bulaşık Makinası"],
en: ["Private Pool", "Stone BBQ", "Fireplace", "LCD TV", "Dishwasher"]
},
distanceToSea: "4km",
location: "Dereköy, Bodrum"
},
{
id: "tuncay",
name: "Villa Tuncay",
slug: "villa-tuncay",
shortDescription: {
tr: "Doğa ile iç içe, huzur dolu ve geleneksel taş mimari.",
en: "Traditional stone architecture intertwined with nature and peace."
},
description: {
tr: "Salmakis bünyesindeki 4'lü villa kompleksinin bir parçası olan Villa Tuncay, geleneksel tarzda inşa edilmiş ve dekore edilmiştir. Villa görevlisinin aynı arazide ikamet etmesi sayesinde 24 saat yardım alma imkanı sunar.",
en: "Part of the 4-villa complex within Salmakis, Villa Tuncay is built and decorated in a traditional style. It offers the possibility of 24-hour assistance thanks to the villa assistant residing on the same plot."
},
images: [
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166999/salvilla/villa-tuncay/TUNCAY-1-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776166999/salvilla/villa-tuncay/TUNCAY-10-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167001/salvilla/villa-tuncay/TUNCAY-11-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167001/salvilla/villa-tuncay/TUNCAY-12-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167002/salvilla/villa-tuncay/TUNCAY-14-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167003/salvilla/villa-tuncay/TUNCAY-16-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167004/salvilla/villa-tuncay/TUNCAY-17-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167005/salvilla/villa-tuncay/TUNCAY-18-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167006/salvilla/villa-tuncay/TUNCAY-19-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167007/salvilla/villa-tuncay/TUNCAY-2-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167008/salvilla/villa-tuncay/TUNCAY-20-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167008/salvilla/villa-tuncay/TUNCAY-22-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167009/salvilla/villa-tuncay/TUNCAY-23-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167010/salvilla/villa-tuncay/TUNCAY-25-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167011/salvilla/villa-tuncay/TUNCAY-27-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167012/salvilla/villa-tuncay/TUNCAY-28-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167013/salvilla/villa-tuncay/TUNCAY-3-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167014/salvilla/villa-tuncay/TUNCAY-4-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167015/salvilla/villa-tuncay/TUNCAY-5-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167016/salvilla/villa-tuncay/TUNCAY-7-800x550.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167016/salvilla/villa-tuncay/TUNCAY-8-800x550.jpg"
],
bedrooms: 3,
bathrooms: 2,
capacity: 6,
priceLow: 1100,
priceHigh: 2500,
features: ["pool", "fireplace", "wifi", "ac", "alarm"],
amenities: {
tr: ["Özel Havuz", "Taş Barbekü", "Şömine", "İnternet", "Kasa"],
en: ["Private Pool", "Stone BBQ", "Fireplace", "Internet", "Safe Box"]
},
distanceToSea: "4km",
location: "Dereköy, Bodrum"
},
{
id: "yalikavak",
name: "Villa Yalıkavak",
slug: "villa-yalikavak",
shortDescription: {
tr: "Yalıkavakta modern konfor ve geleneksel taş yapının buluşması.",
en: "The meeting of modern comfort and traditional stone architecture in Yalıkavak."
},
description: {
tr: "Yalıkavak'ın eşsiz manzarasına hakim bir noktada yer alan bu taş villamız, modern yaşamın tüm gereksinimlerini geleneksel dokuyla birleştirir. Geniş ebeveyn yatak odası, denize yakınlığı ve lüks donanımıyla fark yaratan bir tatil deneyimi sunar.",
en: "Located at a point overlooking the unique view of Yalıkavak, this stone villa combines all the requirements of modern life with traditional texture. It offers a distinct vacation experience with its large master bedroom, proximity to the sea, and luxury equipment."
},
images: [
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167017/salvilla/villa-yalikavak/10.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167018/salvilla/villa-yalikavak/11.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167019/salvilla/villa-yalikavak/12.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167019/salvilla/villa-yalikavak/13.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167020/salvilla/villa-yalikavak/14.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167021/salvilla/villa-yalikavak/15.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167021/salvilla/villa-yalikavak/16.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167022/salvilla/villa-yalikavak/17.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167023/salvilla/villa-yalikavak/18.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167024/salvilla/villa-yalikavak/19.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167024/salvilla/villa-yalikavak/2.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167025/salvilla/villa-yalikavak/20.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167026/salvilla/villa-yalikavak/21.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167026/salvilla/villa-yalikavak/22.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167027/salvilla/villa-yalikavak/23.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167028/salvilla/villa-yalikavak/24.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167028/salvilla/villa-yalikavak/25.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167029/salvilla/villa-yalikavak/26.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167030/salvilla/villa-yalikavak/27.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167031/salvilla/villa-yalikavak/28.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167031/salvilla/villa-yalikavak/29.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167033/salvilla/villa-yalikavak/3.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167033/salvilla/villa-yalikavak/30.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167035/salvilla/villa-yalikavak/31.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167036/salvilla/villa-yalikavak/4.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167036/salvilla/villa-yalikavak/5.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167037/salvilla/villa-yalikavak/6.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167038/salvilla/villa-yalikavak/7.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167039/salvilla/villa-yalikavak/8.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167040/salvilla/villa-yalikavak/9.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167041/salvilla/villa-yalikavak/master-bedroom.jpg",
"https://res.cloudinary.com/du7xohbct/image/upload/v1776167042/salvilla/villa-yalikavak/YALIKAVAK-VILLA-1.jpg"
],
bedrooms: 3,
bathrooms: 2,
capacity: 6,
priceLow: 1100,
priceHigh: 2500,
features: ["pool", "sea-view", "wifi", "ac", "alarm"],
amenities: {
tr: ["Özel Havuz", "Taş Barbekü", "Şömine", "Ekmek Kızartma Makinası", "Kasa"],
en: ["Private Pool", "Stone BBQ", "Fireplace", "Toaster", "Safe Box"]
},
distanceToSea: "2km",
location: "Yalıkavak, Bodrum"
}
];

89
docs/prd.md Normal file
View File

@@ -0,0 +1,89 @@
BÖLÜM 1: Ürün Gereksinim Dokümanı (PRD)
1. Proje Özeti
Amaç: Salmakis bünyesindeki villaların lüks ve huzurlu doğasını yansıtan, yüksek dönüşüm oranlı (conversion) bir kiralama vitrini oluşturmak.
Teknoloji Yığını: Next.js (App Router), Tailwind CSS, Framer Motion (Animasyonlar), TypeScript, Next-Intl (Çoklu Dil).
Veri Yönetimi: data/villas.ts gibi dosyalarda tutulan yapılandırılmış veri objeleri.
2. Sayfa Yapısı ve Akış
Home Page (Landing):
Hero: "Huzuru Keşfedin" temalı tam ekran video/slider.
Quick Search: Villa tipi veya kişi sayısına göre hızlı kaydırma (scroll) navigasyonu.
Featured Villas: En iyi 3 villanın büyük kartlar halinde sunumu.
Villas List (Filo/Koleksiyon):
Tüm villaların şık bir grid (ızgara) yapısında listelenmesi.
Özellik ikonları (Havuz, Oda Sayısı, Deniz Mesafesi).
Villa Detail (Single Page):
Gallery: Instagram stili veya tam ekran "Bento Grid" galeri.
Specs: TypeScript dosyasından çekilen teknik veriler (Klima, WiFi, Mutfak ekipmanı vb.).
Pricing: Sezonluk fiyat tablosu (TS objesinden dinamik okunur).
Contact & Booking Request: SQL olmadığı için talepler doğrudan WhatsApp API'sine veya basit bir mailto: linkine yönlendirilir.
3. Fonksiyonel Gereksinimler
Çok Dillilik (i18n): TR, EN ve belki DE dilleri için URL yapısı (örn: /en/villas/villa-flora).
Hız: LCP (Görsel yüklenme hızı) < 1.5s (Next.js Image + Cloudinary).
Type Safety: Villa verileri için kesin interface tanımları (ID, Name, Price, Images, Specs).
BÖLÜM 2: UI Tasarım Stratejisi
Mevcut sitenin "kutulu" ve boğucu yapısı yerine, "Aegean Minimalism" (Ege Minimalizmi) akımını uygulayacağız.
Renkler: Kemik beyazı, kum beji, çok açık gri ve "Salmakis Mavisi" detaylar.
Fontlar: Başlıklarda Cormorant Garamond (Serif - Lüks hissi), metinlerde Inter (Sans-serif - Okunabilirlik).
BÖLÜM 3: UI Tasarımı İçin AI Prompt (Modern Villa)
Bu prompt, AI'nın sana "Modern Villa Kiralama" konseptini çizmesi için özel hazırlandı:
UI/UX Design for "Salmakis Villas" premium vacation rental website. Next-generation luxury travel aesthetic.
Layout: Modern, minimalist, and airy. Use a lot of whitespace.
Hero Section: High-end photography of a stone villa with a private infinity pool overlooking the Bodrum coast at "Golden Hour".
Villa Cards: Large vertical cards with soft rounded corners. Each card shows the villa name in elegant serif font, followed by minimal icons for (Bedrooms, Pool, Sea View).
Navigation: A transparent glassmorphism navbar with links: "Villas", "The Legend", "Contact". A sleek language switcher "EN | TR".
Typography: A mix of sophisticated Serif for titles and clean Sans-serif for body text.
Color Palette: Warm bone white, sand beige, and Mediterranean turquoise accents.
Interactions: Subtle hover effects on villa images, minimalist "Inquire Now" button with a thin border.
Style: Inspired by Airbnb Luxe and high-end boutique hotel sites. 8k resolution, photorealistic, cinematic lighting, trending on Dribbble/Behance. --ar 16:9 --v 6.0
BÖLÜM 4: Yazılımcı İçin Teknik Not (Data Structure)
SQL kullanmayacağın için veriyi şu şekilde kurgulayabiliriz (villas.ts):
TypeScript
export interface Villa {
id: string;
name: string;
slug: string;
images: string[]; // Cloudinary URL'leri
bedrooms: number;
priceLow: number;
priceHigh: number;
amenities: string[];
}
export const villas: Villa[] = [
{
id: "1",
name: "Villa Salmakis Private",
slug: "villa-salmakis-private",
images: ["cloud-url-1", "cloud-url-2"],
bedrooms: 4,
priceLow: 1200,
priceHigh: 2500,
amenities: ["Private Pool", "Sea View", "Smart Home"],
},
// Diğer villalar...
];

23
docs/villa-melda.md Normal file
View File

@@ -0,0 +1,23 @@
Villa Melda:
Gümüşlük yolu üzerinde Dereköyde yer alan villamız, zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer almaktadır. Villa Melda, 2 katlı, bahçesinde havuzu bulunan, 1salon+2 oda olmak üzere 4 kişilik, geleneksel tarzda inşa edilmiş taş yapılı bir binadır. Lokasyon olarak gürültüden uzak, doğayla iç içe ancak Bodrumun önde gelen çekici tatil beldelerine ulaşımı kolay bir yerde yer almaktadır.
Havuza bakan terasında yer alan yemek masası ve oturma alanı misafirlerimize keyifli vakit geçirme imkanı sağlamaktadır.
Villamızda güvenlik alarm sistemi ve kasa mevcuttur.
Genel Yerleşim: 2 katlı, 1 salon+mutfak, 2 yatak odası(1 double+1 twin), 2 adet teras/balkon
Mutfak: Bulaşık Makinası, Çamaşır Makinası, Buzdolabı/Derin Dondurucu, Ocak, Fırın, Ekmek Kızartma Makinası, Çay&Kahve Makinası, Mikrodalga Fırın, Mutfak Gereçleri, Yemek Masası, Teras/Balkon
Salon: Uydu Bağlantılı TV, LCD TV, DVD/CD Player, İnternet, Ipod uyumlu Hoparlör, Oyun Konsolu, Şömine
Dış Mekan: Taş Barbekü, Şezlong/Şemsiye, Bahçe Mobilyaları, Havuzbaşı Duş, Teras/Balkon
Fiyata Dahil olan Hizmetler:
Elektrik Kullanımı
Su Kullanımı
Mutfak Tüpü/Gaz Kullanımı
Havuz Bakımı
Bahçe Bakımı
Giriş Temizliği
İnternet

23
docs/villa-meyra.md Normal file
View File

@@ -0,0 +1,23 @@
Villa Meyra:
Gümüşlük yolu üzerinde Dereköyde yer alan villamız, zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer almaktadır. Villa Meyra, 2 katlı, bahçesinde havuzu bulunan, 1double +1twin +1 triple olmak üzere 7 kişilik, geleneksel tarzda inşa edilmiş taş yapılı bir binadır. Lokasyon olarak gürültüden uzak, doğayla iç içe ancak Bodrumun önde gelen çekici tatil beldelerine ulaşımı kolay bir yerde yer almaktadır.
Havuza bakan terasında yer alan yemek masası ve oturma alanı misafirlerimize keyifli vakit geçirme imkanı sağlamaktadır.
Villamızda güvenlik alarm sistemi ve kasa mevcuttur.
Genel Yerleşim: 2 katlı, 1 salon+mutfak, 3 yatak odası(2 twin+1 double), 2 adet teras/balkon
Mutfak: Bulaşık Makinası, Çamaşır Makinası, Buzdolabı/Derin Dondurucu, Ocak, Fırın, Ekmek Kızartma Makinası, Çay&Kahve Makinası, Mikrodalga Fırın, Mutfak Gereçleri, Yemek Masası, Teras/Balkon
Salon: Uydu Bağlantılı TV, LCD TV, DVD/CD Player, İnternet, Ipod uyumlu Hoparlör, Oyun Konsolu, Şömine
Dış Mekan: Taş Barbekü, Şezlong/Şemsiye, Bahçe Mobilyaları, Havuzbaşı Duş, Teras/Balkon
Fiyata Dahil olan Hizmetler:
Elektrik Kullanımı
Su Kullanımı
Mutfak Tüpü/Gaz Kullanımı
Havuz Bakımı
Bahçe Bakımı
Giriş Temizliği
İnternet

23
docs/villa-su.md Normal file
View File

@@ -0,0 +1,23 @@
Villa Su:
Gümüşlük yolu üzerinde Dereköyde yer alan villamız, zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer almaktadır. Villa Su, 2 katlı, bahçesinde havuzu bulunan, 1salon+3 oda olmak üzere 6 kişilik, geleneksel tarzda inşa edilmiş taş yapılı bir binadır. Lokasyon olarak gürültüden uzak, doğayla iç içe ancak Bodrumun önde gelen çekici tatil beldelerine ulaşımı kolay bir yerde yer almaktadır.
Havuza bakan terasında yer alan yemek masası ve oturma alanı misafirlerimize keyifli vakit geçirme imkanı sağlamaktadır.
Villamızda güvenlik alarm sistemi ve kasa mevcuttur.
Genel Yerleşim: 2 katlı, 1 salon+mutfak, 2 yatak odası(2 double+1 twin), 2 adet teras/balkon
Mutfak: Bulaşık Makinası, Çamaşır Makinası, Buzdolabı/Derin Dondurucu, Ocak, Fırın, Ekmek Kızartma Makinası, Çay&Kahve Makinası, Mikrodalga Fırın, Mutfak Gereçleri, Yemek Masası, Teras/Balkon
Salon: Uydu Bağlantılı TV, LCD TV, DVD/CD Player, İnternet, Ipod uyumlu Hoparlör, Oyun Konsolu, Şömine
Dış Mekan: Taş Barbekü, Şezlong/Şemsiye, Bahçe Mobilyaları, Havuzbaşı Duş, Teras/Balkon
Fiyata Dahil olan Hizmetler:
Elektrik Kullanımı
Su Kullanımı
Mutfak Tüpü/Gaz Kullanımı
Havuz Bakımı
Bahçe Bakımı
Giriş Temizliği
İnternet

23
docs/villa-tuncay.md Normal file
View File

@@ -0,0 +1,23 @@
Villa Tuncay:
Gümüşlük yolu üzerinde Dereköyde yer alan villamız, zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer almaktadır. Villa Tuncay, 2 katlı, bahçesinde havuzu bulunan, 1salon+3 oda olmak üzere 6 kişilik, geleneksel tarzda inşa edilmiş taş yapılı bir binadır. Lokasyon olarak gürültüden uzak, doğayla iç içe ancak Bodrumun önde gelen çekici tatil beldelerine ulaşımı kolay bir yerde yer almaktadır.
Havuza bakan terasında yer alan yemek masası ve oturma alanı misafirlerimize keyifli vakit geçirme imkanı sağlamaktadır.
Villamızda güvenlik alarm sistemi ve kasa mevcuttur.
Genel Yerleşim: 2 katlı, 1 salon+mutfak, 3 yatak odası (2 twin+1 double), 2 adet teras/balkon
Mutfak: Bulaşık Makinası, Çamaşır Makinası, Buzdolabı/Derin Dondurucu, Ocak, Fırın, Ekmek Kızartma Makinası, Çay&Kahve Makinası, Mikrodalga Fırın, Mutfak Gereçleri, Yemek Masası, Teras/Balkon
Salon: Uydu Bağlantılı TV, LCD TV, DVD/CD Player, İnternet, Ipod uyumlu Hoparlör, Oyun Konsolu, Şömine
Dış Mekan: Taş Barbekü, Şezlong/Şemsiye, Bahçe Mobilyaları, Havuzbaşı Duş, Teras/Balkon
Fiyata Dahil olan Hizmetler;
Elektrik Kullanımı
Su Kullanımı
Mutfak Tüpü/Gaz Kullanımı
Havuz Bakımı
Bahçe Bakımı
Giriş Temizliği
İnternet

23
docs/villa-yalikavak.md Normal file
View File

@@ -0,0 +1,23 @@
Villa Yalıkavak:
Yalıkavakta yer alan villamız, zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer almaktadır. Villa Yalıkavak, 2 katlı, bahçesinde havuzu bulunan, 1salon+3 oda olmak üzere 6 kişilik, geleneksel tarzda inşa edilmiş taş yapılı bir binadır. Lokasyon olarak gürültüden uzak, doğayla iç içe ancak Bodrumun önde gelen çekici tatil beldelerine ulaşımı kolay bir yerde yer almaktadır.
Havuza bakan terasında yer alan yemek masası ve oturma alanı misafirlerimize keyifli vakit geçirme imkanı sağlamaktadır.
Villamızda güvenlik alarm sistemi ve kasa mevcuttur.
Genel Yerleşim: 2 katlı, 1 salon+mutfak, 3 yatak odası(1 double+2 twin), 2 adet teras/balkon
Mutfak: Bulaşık Makinası, Çamaşır Makinası, Buzdolabı/Derin Dondurucu, Ocak, Fırın, Ekmek Kızartma Makinası, Çay&Kahve Makinası, Mikrodalga Fırın, Mutfak Gereçleri, Yemek Masası, Teras/Balkon
Salon: Uydu Bağlantılı TV, LCD TV, DVD/CD Player, İnternet, Ipod uyumlu Hoparlör, Oyun Konsolu, Şömine
Dış Mekan: Taş Barbekü, Şezlong/Şemsiye, Bahçe Mobilyaları, Havuzbaşı Duş, Teras/Balkon
Fiyata Dahil olan Hizmetler:
Elektrik Kullanımı
Su Kullanımı
Mutfak Tüpü/Gaz Kullanımı
Havuz Bakımı
Bahçe Bakımı
Giriş Temizliği
İnternet

17
i18n/request.ts Normal file
View File

@@ -0,0 +1,17 @@
import {getRequestConfig} from 'next-intl/server';
import {routing} from './routing';
export default getRequestConfig(async ({requestLocale}) => {
// This typically corresponds to the `[locale]` segment
let locale = await requestLocale;
// Ensure that a valid locale is used
if (!locale || !routing.locales.includes(locale as "en" | "tr")) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
};
});

15
i18n/routing.ts Normal file
View File

@@ -0,0 +1,15 @@
import {defineRouting} from 'next-intl/routing';
import {createNavigation} from 'next-intl/navigation';
export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'tr'],
// Used when no locale matches
defaultLocale: 'tr'
});
// Lightweight wrappers around Next.js' navigation APIs
// that will consider the routing configuration
export const {Link, redirect, usePathname, useRouter, getPathname} =
createNavigation(routing);

35
messages/en.json Normal file
View File

@@ -0,0 +1,35 @@
{
"Index": {
"title": "Discover Serenity",
"description": "Explore the luxury and peaceful nature of Salmakis villas.",
"explore": "Explore Villas"
},
"Navbar": {
"villas": "Villas",
"about": "About Us",
"contact": "Contact"
},
"About": {
"title": "Dereköy Villas",
"paragraph1": "Located in Dereköy on the road to Gümüşlük, our villas are perfectly centered between Ortakent, Turgutreis, and Gümüşlük.",
"paragraph2": "Situated on a large estate covered with olive trees, bougainvillea, and flowers, these two-story stone villas are built and decorated in a traditional style. Each villa has its own private garden and pool, making them an ideal choice for guests who want to stay away from the city's noise but still be close to Bodrum's leading attractive holiday resorts.",
"paragraph3": "Within the same estate, our 4 villas built in the same style (Villa Leila, Villa Tuncay, Villa Janan, Villa Su) offer families and large groups of friends the opportunity to vacation at the same time and location. You can enjoy pleasant dinners on the terrace overlooking the pool, or spend your day sunbathing by the pool area with nature views.",
"paragraph4": "While our villas are equipped with security systems, a villa assistant also resides on the same estate and is ready to help 24 hours a day if needed.",
"location_title": "Perfect Location",
"tradition_title": "Traditional Architecture",
"service_title": "24/7 Service"
},
"Contact": {
"title": "Contact Us",
"description": "We would be happy to get in touch with you. Please reach out for your questions and reservation requests.",
"address": "Address",
"phone": "Phone",
"mobile": "Mobile",
"fax": "Fax",
"email": "Email",
"form_name": "Full Name",
"form_email": "Your Email",
"form_message": "Your Message",
"form_submit": "Send"
}
}

35
messages/tr.json Normal file
View File

@@ -0,0 +1,35 @@
{
"Index": {
"title": "Huzuru Keşfedin",
"description": "Salmakis bünyesindeki villaların lüks ve huzurlu doğasını keşfedin.",
"explore": "Villaları İncele"
},
"Navbar": {
"villas": "Villalar",
"about": "Hakkımızda",
"contact": "İletişim"
},
"About": {
"title": "Dereköy Villaları",
"paragraph1": "Gümüşlük yolu üzerinde Dereköyde yer alan villalarımız, tam olarak Ortakent, Turgutreis ve Gümüşlük üçgeninin ortasında yer almaktadır.",
"paragraph2": "Zeytin ağaçları, begonviller ve çiçeklerle kaplı büyük bir arazide yer alan, müstakil ve her villanın bahçesinde havuzu bulunan, geleneksel tarzda inşa ve dekore edilmiş bu iki katlı taş villalarımız, özellikle şehrin gürültüsünden uzak ama Bodrumun önde gelen çekici tatil beldelerine kısa bir sürede ulaşmak isteyen misafirler için doğru bir seçimdir.",
"paragraph3": "Aynı arazi içinde, aynı tarzda inşa edilmiş 4 adet villalarımız (Villa Leila, Villa Tuncay, Villa Janan, Villa Su), büyük aile ve arkadaş grupları için aynı zamanda ve aynı lokasyonda tatil yapma imkanı sunmaktadır. Doğa manzaralı havuz ve güneşlenme alanı, gününüzü havuz başında rahatça güneşlenerek geçirebileceğiniz, havuza bakan terasta oturma alanı bulunan villalarımızda keyifli akşam yemeklerinin tadını çıkarabilirsiniz.",
"paragraph4": "Villalarımız güvenlik alarmı donanımına sahip olmakla birlikte, villa görevlisi de aynı arazide ikamet etmektedir ve ihtiyacınız olması halinde 24 saat yardımınıza hazırdır.",
"location_title": "Mükemmel Konum",
"tradition_title": "Geleneksel Mimari",
"service_title": "24 Saat Hizmet"
},
"Contact": {
"title": "İletişim",
"description": "Sizinle iletişime geçmekten mutluluk duyarız. Sorularınız ve rezervasyon talepleriniz için bize ulaşabilirsiniz.",
"address": "Adres",
"phone": "Telefon",
"mobile": "Mobil",
"fax": "Faks",
"email": "E-posta",
"form_name": "Ad Soyad",
"form_email": "E-posta Adresiniz",
"form_message": "Mesajınız",
"form_submit": "Gönder"
}
}

View File

@@ -1,7 +1,22 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {
/* config options here */
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: 'res.cloudinary.com',
},
],
},
};
export default nextConfig;
export default withNextIntl(nextConfig);

842
package-lock.json generated
View File

@@ -8,9 +8,16 @@
"name": "salvilla",
"version": "0.1.0",
"dependencies": {
"cloudinary": "^2.9.0",
"clsx": "^2.1.1",
"dotenv": "^17.4.2",
"framer-motion": "^12.38.0",
"lucide-react": "^1.8.0",
"next": "16.2.3",
"next-intl": "^4.9.1",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -453,6 +460,36 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz",
"integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==",
"license": "MIT"
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.4.tgz",
"integrity": "sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==",
"license": "MIT",
"dependencies": {
"@formatjs/icu-skeleton-parser": "2.1.4"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.4.tgz",
"integrity": "sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==",
"license": "MIT"
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.3.tgz",
"integrity": "sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==",
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "3.1.2"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1286,6 +1323,331 @@
"node": ">=12.4.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1293,6 +1655,228 @@
"dev": true,
"license": "MIT"
},
"node_modules/@schummar/icu-type-parser": {
"version": "1.21.5",
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
"license": "MIT"
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz",
"integrity": "sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz",
"integrity": "sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz",
"integrity": "sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz",
"integrity": "sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz",
"integrity": "sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-ppc64-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz",
"integrity": "sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-s390x-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz",
"integrity": "sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz",
"integrity": "sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz",
"integrity": "sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz",
"integrity": "sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz",
"integrity": "sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz",
"integrity": "sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1302,6 +1886,15 @@
"tslib": "^2.8.0"
}
},
"node_modules/@swc/types": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
@@ -2701,6 +3294,27 @@
"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/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2883,7 +3497,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -2902,6 +3515,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",
@@ -3692,6 +4317,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",
@@ -3997,6 +4649,21 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/icu-minify": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.1.tgz",
"integrity": "sha512-6NkfF9GHHFouqnz+wuiLjCWQiyxoEyJ5liUv4Jxxo/8wyhV7MY0L0iTEGDAVEa4aAD58WqTxFMa20S5nyMjwNw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"license": "MIT",
"dependencies": {
"@formatjs/icu-messageformat-parser": "^3.4.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4049,6 +4716,16 @@
"node": ">= 0.4"
}
},
"node_modules/intl-messageformat": {
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.1.tgz",
"integrity": "sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@formatjs/fast-memoize": "3.1.2",
"@formatjs/icu-messageformat-parser": "3.5.4"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4211,7 +4888,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -4257,7 +4933,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -4922,6 +5597,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",
@@ -4952,6 +5633,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",
@@ -5019,6 +5709,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",
@@ -5067,6 +5772,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/next": {
"version": "16.2.3",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz",
@@ -5120,6 +5834,83 @@
}
}
},
"node_modules/next-intl": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.1.tgz",
"integrity": "sha512-N7ga0CjtYcdxNvaKNIi6eJ2mmatlHK5hp8rt0YO2Omoc1m0gean242/Ukdj6+gJNiReBVcYIjK0HZeNx7CV1ug==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"license": "MIT",
"dependencies": {
"@formatjs/intl-localematcher": "^0.8.1",
"@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2",
"icu-minify": "^4.9.1",
"negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "^4.9.1",
"po-parser": "^2.1.1",
"use-intl": "^4.9.1"
},
"peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/next-intl-swc-plugin-extractor": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.1.tgz",
"integrity": "sha512-8whJJ6oxJz8JqkHarggmmuEDyXgC7nEnaPhZD91CJwEWW4xp0AST3Mw17YxvHyP2vAF3taWfFbs1maD+WWtz3w==",
"license": "MIT"
},
"node_modules/next-intl/node_modules/@swc/core": {
"version": "1.15.24",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.24.tgz",
"integrity": "sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.26"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.24",
"@swc/core-darwin-x64": "1.15.24",
"@swc/core-linux-arm-gnueabihf": "1.15.24",
"@swc/core-linux-arm64-gnu": "1.15.24",
"@swc/core-linux-arm64-musl": "1.15.24",
"@swc/core-linux-ppc64-gnu": "1.15.24",
"@swc/core-linux-s390x-gnu": "1.15.24",
"@swc/core-linux-x64-gnu": "1.15.24",
"@swc/core-linux-x64-musl": "1.15.24",
"@swc/core-win32-arm64-msvc": "1.15.24",
"@swc/core-win32-ia32-msvc": "1.15.24",
"@swc/core-win32-x64-msvc": "1.15.24"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -5148,6 +5939,12 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -5424,6 +6221,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/po-parser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
"license": "MIT"
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -6159,6 +6962,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tailwind-merge": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
"integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
@@ -6517,6 +7330,27 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-intl": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.1.tgz",
"integrity": "sha512-iGVV/xFYlhe3btafRlL8RPLD2Jsuet4yqn9DR6LWWbMhULsJnXgLonDkzDmsAIBIwFtk02oJuX/Ox2vwHKF+UQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "^3.1.0",
"@schummar/icu-type-parser": "1.21.5",
"icu-minify": "^4.9.1",
"intl-messageformat": "^11.1.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -9,9 +9,16 @@
"lint": "eslint"
},
"dependencies": {
"cloudinary": "^2.9.0",
"clsx": "^2.1.1",
"dotenv": "^17.4.2",
"framer-motion": "^12.38.0",
"lucide-react": "^1.8.0",
"next": "16.2.3",
"next-intl": "^4.9.1",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",

14
proxy.ts Normal file
View File

@@ -0,0 +1,14 @@
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
import type { NextRequest } from 'next/server';
const intlProxy = createMiddleware(routing);
export function proxy(request: NextRequest) {
return intlProxy(request);
}
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(tr|en)/:path*']
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Some files were not shown because too many files have changed in this diff Show More