Update gallery to bento grid, add real reviews, update phone numbers and locations

This commit is contained in:
Ayris Dev
2026-07-15 13:51:22 +03:00
parent d4bcbb0cc3
commit 7a874ab11d
43 changed files with 1234 additions and 790 deletions
+3 -3
View File
@@ -13,10 +13,10 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: str
const { lang } = await params;
return buildMeta(lang, "/aktiviteler", {
title: "Aktiviteler | Kite Beach Akyaka",
description: "Kitesurf, yoga, SUP ve bisiklet kiralama. Akyaka'nın doğasında IKO sertifikalı eğitmenlerle unutulmaz deneyimler.",
description: "Kitesurf, yoga, SUP ve bisiklet kiralama. Akyaka'nın doğasında profesyonel eğitmenlerle unutulmaz deneyimler.",
}, {
title: "Activities | Kite Beach Akyaka",
description: "Kitesurf, yoga, SUP and bicycle rental. Unforgettable experiences in Akyaka with IKO certified instructors.",
description: "Kitesurf, yoga, SUP and bicycle rental. Unforgettable experiences in Akyaka with professional instructors.",
});
}
@@ -55,7 +55,7 @@ export default async function AktivitelerPage({ params }: { params: Promise<{ la
{/* Activities */}
<section className="py-20 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{activities.length > 0 ? activities.map((act, i) => (
<ScrollReveal key={act.id} delay={i * 80}>
<div className="card-lift group bg-white rounded-3xl overflow-hidden border border-sand flex flex-col h-full">
+6 -6
View File
@@ -23,13 +23,13 @@ const getAltText = (alt: string, isEn: boolean) => {
};
const gallery: { src: string; alt: string; category: "Odalar" | "Aktiviteler" | "Restoran" | "Doğa" }[] = [
// { src: "https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=2940&auto=format&fit=crop", alt: "Standart Oda", category: "Odalar" },
{ src: "https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=2940&auto=format&fit=crop", alt: "Standart Oda", category: "Odalar" },
{ src: "https://images.unsplash.com/photo-1526367790999-0150786686a2?q=80&w=2940&auto=format&fit=crop", alt: "Kitesurf", category: "Aktiviteler" },
{ src: "https://images.unsplash.com/photo-1544551763-46a013bb70d5?q=80&w=2940&auto=format&fit=crop", alt: "Nehir Doğa", category: "Doğa" },
// { src: "https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2874&auto=format&fit=crop", alt: "Nehir Manzaralı Oda",category: "Odalar" },
{ src: "https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2874&auto=format&fit=crop", alt: "Nehir Manzaralı Oda",category: "Odalar" },
{ src: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?q=80&w=2940&auto=format&fit=crop", alt: "Restoran", category: "Restoran" },
{ src: "https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?q=80&w=2940&auto=format&fit=crop", alt: "Yoga", category: "Aktiviteler" },
// { src: "https://images.unsplash.com/photo-1499955085172-a104c9463ece?q=80&w=2940&auto=format&fit=crop", alt: "Bungalow", category: "Odalar" },
{ src: "https://images.unsplash.com/photo-1499955085172-a104c9463ece?q=80&w=2940&auto=format&fit=crop", alt: "Bungalow", category: "Odalar" },
{ src: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=2940&auto=format&fit=crop", alt: "SUP", category: "Aktiviteler" },
{ src: "https://images.unsplash.com/photo-1551504734-5ee1c4a1479b?q=80&w=2940&auto=format&fit=crop", alt: "Kahvaltı", category: "Restoran" },
{ src: "https://images.unsplash.com/photo-1511994298241-608e28f14fde?q=80&w=2940&auto=format&fit=crop", alt: "Bisiklet", category: "Aktiviteler" },
@@ -42,15 +42,15 @@ export default function GaleriPage({ params }: { params: Promise<{ lang: string
const isEn = lang === "en";
const filters = isEn
? ["All", "Activities", "Restaurant", "Nature"]
: ["Tümü", "Aktiviteler", "Restoran", "Doğa"];
? ["All", "Rooms", "Activities", "Restaurant", "Nature"]
: ["Tümü", "Odalar", "Aktiviteler", "Restoran", "Doğa"];
const [active, setActive] = useState<string>(isEn ? "All" : "Tümü");
const [lightbox, setLightbox] = useState<string | null>(null);
const getInternalCategory = (filterName: string) => {
if (filterName === "All" || filterName === "Tümü") return "Tümü";
// if (filterName === "Rooms") return "Odalar";
if (filterName === "Rooms") return "Odalar";
if (filterName === "Activities") return "Aktiviteler";
if (filterName === "Restaurant") return "Restoran";
if (filterName === "Nature") return "Doğa";
+6 -6
View File
@@ -31,7 +31,7 @@ const valuesTr = [
{
icon: Award,
title: "Premium Deneyim",
desc: "IKO sertifikalı kitesurf okulumuzdan restoranımıza kadar her noktada en yüksek kalite standartlarını sunuyoruz.",
desc: "Kitesurf okulumuzdan restoranımıza kadar her noktada en yüksek kalite standartlarını sunuyoruz.",
},
];
@@ -49,21 +49,21 @@ const valuesEn = [
{
icon: Award,
title: "Premium Experience",
desc: "From our IKO certified kitesurf school to our restaurant, we offer the highest quality standards at every point.",
desc: "From our kitesurf school to our restaurant, we offer the highest quality standards at every point.",
},
];
const statsTr = [
{ value: "10+", label: "Yıllık Deneyim" },
{ value: "500+", label: "Mutlu Misafir" },
{ value: "3", label: "IKO Sertifikalı Eğitmen" },
{ value: "3", label: "Profesyonel Eğitmen" },
{ value: "4", label: "Benzersiz Aktivite" },
];
const statsEn = [
{ value: "10+", label: "Years Experience" },
{ value: "500+", label: "Happy Guests" },
{ value: "3", label: "IKO Certified Instructor" },
{ value: "3", label: "Professional Instructor" },
{ value: "4", label: "Unique Activity" },
];
@@ -123,9 +123,9 @@ export default async function HakkimizdaPage({ params }: { params: Promise<{ lan
</p>
<p>
{isEn ? (
"With our nature-friendly architecture, our kitchen supporting local producers, and our training center in IKO standards, we aim to be the face of sustainable tourism in Akyaka."
"With our nature-friendly architecture, our kitchen supporting local producers, and our professional training center, we aim to be the face of sustainable tourism in Akyaka."
) : (
"Doğaya saygılı mimarimiz, lokal üreticileri destekleyen mutfağımız ve IKO standartlarındaki eğitim merkezimizle, Akyaka'nın sürdürülebilir turizm yüzü olmayı hedefliyoruz."
"Doğaya saygılı mimarimiz, lokal üreticileri destekleyen mutfağımız ve profesyonel eğitim merkezimizle, Akyaka'nın sürdürülebilir turizm yüzü olmayı hedefliyoruz."
)}
</p>
</div>
+6 -6
View File
@@ -30,8 +30,8 @@ export default async function IletisimPage({ params }: { params: Promise<{ lang:
{
icon: Phone,
label: "Phone / WhatsApp",
value: "+90 533 081 6181",
href: "tel:+905330816181",
value: "+90 544 504 03 10",
href: "tel:+905445040310",
},
{
icon: Mail,
@@ -55,8 +55,8 @@ export default async function IletisimPage({ params }: { params: Promise<{ lang:
{
icon: Phone,
label: "Telefon / WhatsApp",
value: "+90 533 081 6181",
href: "tel:+905330816181",
value: "+90 544 504 03 10",
href: "tel:+905445040310",
},
{
icon: Mail,
@@ -211,13 +211,13 @@ export default async function IletisimPage({ params }: { params: Promise<{ lang:
{/* Map */}
<div className="rounded-3xl overflow-hidden border border-sand flex-grow min-h-[220px] relative">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d12759.395175968393!2d28.3245!3d37.0564!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14bf76cd1f1f1d1f%3A0x1d1f1f1f1f1f1f1f!2sAkyaka%2C%20Ula%2FMuğla!5e0!3m2!1str!2str!4v1650000000000!5m2!1str!2str"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1570.4884121408588!2d28.328470723755615!3d37.03786201891436!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14bf9f4829fe7243%3A0xe3747440b7bf7a8f!2sKite%20Beach%20Akyaka!5e0!3m2!1str!2str!4v1784112071594!5m2!1str!2str"
width="100%"
height="100%"
style={{ border: 0 }}
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
referrerPolicy="strict-origin-when-cross-origin"
className="absolute inset-0"
/>
</div>
+1 -1
View File
@@ -74,7 +74,7 @@ function buildJsonLd(lang: string) {
description: isEn
? "Boutique hotel and kitesurf center on the banks of Azmak River in Akyaka, Muğla."
: "Akyaka, Muğla'da Azmak Nehri kıyısında butik otel ve kitesurf merkezi.",
telephone: "+905330816181",
telephone: "+905445040310",
email: "info@kitebeachakyaka.com.tr",
address: {
"@type": "PostalAddress",
+59 -163
View File
@@ -1,111 +1,25 @@
import Image from "next/image";
import Link from "next/link";
import { Check, Wifi, Wind, Coffee, Eye, Trees } from "lucide-react";
import { Check, Info } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
import BentoGallery from "@/components/BentoGallery";
const roomsTr = [
{
img: "https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=2940&auto=format&fit=crop",
tag: "Standart",
name: "Standart Oda",
desc: "Konforlu ve modern tasarımlı standart odalarımızda rahatlığın tadını çıkarın. Çift kişilik yatak ve şık dekorasyon ile dinlendirici bir atmosfer.",
sqm: "20 m²",
price: "₺4.500",
features: [
{ icon: Wifi, label: "Ücretsiz Wi-Fi" },
{ icon: Wind, label: "Klima" },
{ icon: Coffee, label: "Minibar" },
{ icon: Eye, label: "Bahçe Manzarası" },
],
reversed: false,
},
{
img: "https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2874&auto=format&fit=crop",
tag: "Popüler",
name: "Nehir Manzaralı Oda",
desc: "Güne Azmak nehrinin eşsiz manzarası eşliğinde uyanacağınız ferah odalar. Balkonunuzda sabah kahvenizi yudumlarken doğanın sesini dinleyin.",
sqm: "25 m²",
price: "₺5.500",
features: [
{ icon: Eye, label: "Nehir Manzarası" },
{ icon: Trees, label: "Özel Balkon" },
{ icon: Coffee, label: "Nespresso Makinesi" },
{ icon: Wifi, label: "Ücretsiz Wi-Fi" },
],
reversed: true,
},
{
img: "https://images.unsplash.com/photo-1499955085172-a104c9463ece?q=80&w=2940&auto=format&fit=crop",
tag: "Premium",
name: "Ahşap Bungalow",
desc: "Doğayla tamamen iç içe, bağımsız girişli ve teraslı ahşap tasarımlı yaşam alanı. Gizlilik ve huzur arayan çiftler için ideal.",
sqm: "35 m²",
price: "₺7.000",
features: [
{ icon: Trees, label: "Özel Teras" },
{ icon: Eye, label: "Doğa Manzarası" },
{ icon: Coffee, label: "Premium İkramlar" },
{ icon: Wind, label: "Klima" },
],
reversed: false,
},
];
const roomsEn = [
{
img: "https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=2940&auto=format&fit=crop",
tag: "Standard",
name: "Standard Room",
desc: "Enjoy comfort in our modern standard rooms. Relaxing atmosphere with a double bed and stylish decoration.",
sqm: "20 m²",
price: "₺4.500",
features: [
{ icon: Wifi, label: "Free Wi-Fi" },
{ icon: Wind, label: "Air Conditioning" },
{ icon: Coffee, label: "Minibar" },
{ icon: Eye, label: "Garden View" },
],
reversed: false,
},
{
img: "https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2874&auto=format&fit=crop",
tag: "Popular",
name: "River View Room",
desc: "Spacious rooms where you wake up to the unique view of the Azmak river. Listen to the sound of nature while sipping morning coffee on your private balcony.",
sqm: "25 m²",
price: "₺5.500",
features: [
{ icon: Eye, label: "River View" },
{ icon: Trees, label: "Private Balcony" },
{ icon: Coffee, label: "Nespresso Machine" },
{ icon: Wifi, label: "Free Wi-Fi" },
],
reversed: true,
},
{
img: "https://images.unsplash.com/photo-1499955085172-a104c9463ece?q=80&w=2940&auto=format&fit=crop",
tag: "Premium",
name: "Wooden Bungalow",
desc: "Wooden living area with private entrance and terrace, fully integrated with nature. Ideal for couples seeking privacy and serenity.",
sqm: "35 m²",
price: "₺7.000",
features: [
{ icon: Trees, label: "Private Terrace" },
{ icon: Eye, label: "Nature View" },
{ icon: Coffee, label: "Premium Amenities" },
{ icon: Wind, label: "Air Conditioning" },
],
reversed: false,
},
const galleryImages = [
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.15.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.18.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.19.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.20%20(1).jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.20.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.21.jpeg",
];
export default async function OdalarPage({ params }: { params: Promise<{ lang: string }> }) {
const { lang } = await params;
const isEn = lang === "en";
const displayRooms = isEn ? roomsEn : roomsTr;
const displayAmenities = isEn
? ["Organic Breakfast Spread", "Free Parking", "Beach & River Access", "Sunbed & Towel", "Wi-Fi", "24/7 Reception"]
: ["Organik Serpme Kahvaltı", "Ücretsiz Otopark", "Plaj & Nehir Erişimi", "Şezlong & Havlu", "Wi-Fi", "24/7 Resepsiyon"];
? ["Organic Breakfast Spread", "Free Parking", "Beach & River Access", "Sunbed & Towel", "Wi-Fi", "24/7 Reception", "Air Conditioning", "Minibar"]
: ["Organik Serpme Kahvaltı", "Ücretsiz Otopark", "Plaj & Nehir Erişimi", "Şezlong & Havlu", "Wi-Fi", "24/7 Resepsiyon", "Klima", "Minibar"];
return (
<div className="min-h-screen bg-background">
@@ -135,83 +49,65 @@ export default async function OdalarPage({ params }: { params: Promise<{ lang: s
</div>
</section>
{/* Room Listings */}
<section className="py-20 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="space-y-16">
{displayRooms.map((room, i) => (
<ScrollReveal key={i}>
<div
className="grid grid-cols-1 md:grid-cols-2 gap-0 bg-white rounded-3xl overflow-hidden shadow-sm border border-sand"
>
{/* Image */}
<div className={`relative h-80 md:h-auto ${room.reversed ? "md:order-2" : ""}`}>
<Image
src={room.img}
alt={room.name}
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover"
/>
{/* Tag */}
<span className="absolute top-5 left-5 bg-dark/80 backdrop-blur-sm text-white text-[10px] font-bold px-3 py-1.5 rounded-full tracking-widest uppercase">
{room.tag}
</span>
</div>
{/* Intro & Price */}
<section className="py-20 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-12">
<h2 className="font-serif text-4xl font-bold text-dark mb-6">
{isEn ? "Comfort & Serenity" : "Konfor & Huzur"}
</h2>
<p className="text-muted text-[15px] leading-relaxed mb-10 max-w-2xl mx-auto">
{isEn
? "All our rooms are carefully designed to offer you the ultimate relaxation, harmonizing with the natural beauty surrounding Akyaka. Enjoy modern amenities with a touch of Aegean warmth."
: "Tüm odalarımız, Akyaka'yı çevreleyen doğal güzelliklerle uyum içinde, size en üst düzeyde rahatlama sunmak için özenle tasarlandı. Ege sıcaklığının dokunuşuyla modern olanakların keyfini çıkarın."}
</p>
<div className="inline-flex items-center justify-center gap-4 bg-primary/10 border-2 border-primary/20 text-primary px-8 py-5 rounded-3xl shadow-sm">
<Info size={24} className="text-accent" />
<div className="text-left">
<span className="block text-xs uppercase tracking-widest text-primary/70 mb-1">
{isEn ? "Standard Rate" : "Standart Fiyat"}
</span>
<span className="block font-bold text-xl md:text-2xl tracking-wide">
{isEn ? "8000 TL / Night (Breakfast Included)" : "8000 TL / Gecelik (Kahvaltı Dahil)"}
</span>
</div>
</div>
</ScrollReveal>
</section>
{/* Content */}
<div className={`p-10 md:p-14 flex flex-col justify-center ${room.reversed ? "md:order-1" : ""}`}>
<span className="text-muted text-xs tracking-[0.2em] uppercase mb-3 block">{room.sqm}</span>
<h2 className="font-serif text-3xl font-bold text-dark mb-4">{room.name}</h2>
<p className="text-gray-500 mb-8 leading-relaxed text-[15px]">{room.desc}</p>
<div className="grid grid-cols-2 gap-3 mb-10">
{room.features.map(({ icon: Icon, label }, j) => (
<div key={j} className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-full bg-primary/8 flex items-center justify-center flex-shrink-0">
<Icon size={13} className="text-primary" />
</div>
<span className="text-[13px] text-foreground/70">{label}</span>
</div>
))}
</div>
<div className="flex justify-between items-center pt-8 border-t border-sand">
<div>
<span className="text-muted text-xs block mb-1 uppercase tracking-wider">
{isEn ? "Per Night" : "Gecelik"}
</span>
<span className="font-serif text-3xl font-bold text-dark">{room.price}</span>
</div>
<Link
href={`/${lang}/iletisim`}
className="btn-shimmer bg-accent text-white px-7 py-3.5 rounded-full text-[11px] font-bold tracking-[0.12em] uppercase hover:bg-accent/90 transition-colors shadow-lg"
>
{isEn ? "Contact Us" : "Bize Ulaşın"}
</Link>
</div>
</div>
</div>
</ScrollReveal>
))}
{/* Bento Grid Gallery */}
<section className="pb-24 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal>
<BentoGallery images={galleryImages} />
</ScrollReveal>
<div className="text-center mt-16">
<Link
href={`/${lang}/iletisim`}
className="btn-shimmer bg-accent text-white px-10 py-4 rounded-full text-sm font-bold tracking-[0.15em] uppercase hover:bg-accent/90 transition-colors shadow-xl"
>
{isEn ? "Contact For Reservation" : "Rezervasyon İçin İletişime Geçin"}
</Link>
</div>
</section>
{/* Info strip */}
<section className="py-16 bg-primary">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h3 className="font-serif text-3xl text-white font-bold mb-4">
{isEn ? "What's included in all rooms" : "Tüm odalarda dahil olanlar"}
{/* Info strip (Disabled)
<section className="py-20 bg-primary">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h3 className="font-serif text-3xl text-white font-bold mb-6">
{isEn ? "Included in your stay" : "Konaklamanıza dahil olanlar"}
</h3>
<div className="flex flex-wrap justify-center gap-4 mt-8">
<div className="flex flex-wrap justify-center gap-5 mt-8">
{displayAmenities.map((item) => (
<span key={item} className="inline-flex items-center gap-2 bg-white/10 text-white text-[13px] px-4 py-2 rounded-full border border-white/15">
<Check size={13} className="text-accent" />
<span key={item} className="inline-flex items-center gap-2.5 bg-white/10 text-white text-[14px] px-5 py-3 rounded-full border border-white/15 backdrop-blur-sm">
<Check size={16} className="text-accent" />
{item}
</span>
))}
</div>
</div>
</section>
*/}
</div>
);
}
+47 -581
View File
@@ -1,102 +1,64 @@
import Image from "next/image";
import Link from "next/link";
import {
ArrowRight, Star, Wind, Anchor, Utensils, BedDouble, Sun,
Waves, Bike, Coffee, Music, MapPin, Camera, Heart, Compass,
Fish, Mountain, Dumbbell, Leaf, Shield, Umbrella, Flame,
type LucideIcon,
} from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
import { getDictionary, Locale } from "./dictionaries";
import prisma from "@/lib/prisma";
import { buildMeta } from "@/lib/metadata";
import type { Metadata } from "next";
import HeroSection from "@/components/home/HeroSection";
import FeaturesStrip from "@/components/home/FeaturesStrip";
import AboutSection from "@/components/home/AboutSection";
import ActivitiesGrid from "@/components/home/ActivitiesGrid";
import EventsPreview from "@/components/home/EventsPreview";
import InstagramFeed from "@/components/home/InstagramFeed";
import TestimonialsSection from "@/components/home/TestimonialsSection";
import CtaBand from "@/components/home/CtaBand";
import RoomsSection from "@/components/home/RoomsSection";
export const revalidate = 3600;
const ICON_MAP: Record<string, LucideIcon> = {
Wind, Anchor, Utensils, BedDouble, Sun, Star, Waves, Bike,
Coffee, Music, MapPin, Camera, Heart, Compass, Fish, Mountain,
Dumbbell, Leaf, Shield, Umbrella, Flame,
};
import HeroVideo from "@/components/HeroVideo";
const defaultFeatures = [
{ icon: "Wind", label: "Kitesurf Okulu", sub: "IKO Sertifikalı" },
{ icon: "Anchor", label: "Yoga Platformu", sub: "Nehir Kenarı" },
{ icon: "Wind", label: "Kitesurf", sub: "Eğitimleri" },
{ icon: "Anchor", label: "Yoga Wellness", sub: "Platform" },
{ icon: "Utensils", label: "Restoran & Bar", sub: "Ege Mutfağı" },
// { icon: "BedDouble", label: "Butik Odalar", sub: "3 Tip Konaklama" },
{ icon: "Sun", label: "Gün Batımı Terası", sub: "Gökova Manzarası" },
];
const rooms = [
{
img: "https://images.unsplash.com/photo-1618773928121-c32242e63f39?q=80&w=2940&auto=format&fit=crop",
badge: "Standart",
badgeStyle: "bg-white/90 text-dark",
name: "Standart Oda",
desc: "Konforlu ve modern tasarımlı odamızda huzurlu bir konaklama.",
price: "₺4.500",
cta: "primary" as const,
},
{
img: "https://images.unsplash.com/photo-1566665797739-1674de7a421a?q=80&w=2874&auto=format&fit=crop",
badge: "Popüler",
badgeStyle: "bg-accent text-white",
name: "Nehir Manzaralı Oda",
desc: "Azmak nehrinin eşsiz manzarası eşliğinde güne uyanın.",
price: "₺5.500",
cta: "accent" as const,
},
{
img: "https://images.unsplash.com/photo-1499955085172-a104c9463ece?q=80&w=2940&auto=format&fit=crop",
badge: "Premium",
badgeStyle: "bg-white/90 text-dark",
name: "Ahşap Bungalow",
desc: "Doğayla tamamen iç içe, teraslı ve bağımsız girişli.",
price: "₺7.000",
cta: "primary" as const,
},
{ icon: "BedDouble", label: "Butik Odalar", sub: "3 Tip Konaklama" },
{ icon: "Sun", label: "Sunset DJ Party", sub: "Etkinlikler" },
];
const testimonials = [
{
text: "Akyaka'da pek çok yerde kaldım ama Kite Beach bambaşka bir deneyimdi. Nehrin sesi, kitesurf dersleri ve muhteşem kahvaltı — her şey mükemmeldi.",
name: "Ayşe K.",
from: "İstanbul",
text: "Çok atmosferik bir yer. Harika ortam, uçurtma ve rüzgar. Kahve fena değil ama biraz pahalı.",
name: "Elena Gerasimova",
from: "Google Yorumları",
},
{
text: "Yoga seansları ahşap platformda, sabah erken saatte, nehir manzarası eşliğinde. Kelimelerle anlatmak zor — mutlaka yaşanmalı.",
name: "Marco B.",
from: "İtalya",
text: "Ortama, plaja ve kitesurf yapanları izlemeye bayıldım. Takılacak bir sürü harika yer, iyi yemek ve iyi müzik var. Kitesurf'e uğramadan Akyaka'ya gelmiş sayılmazsınız.",
name: "Dhyani Love",
from: "Google Yorumları",
},
{
text: "Eğitmenler son derece ilgili. Kitesurf derslerimden sonra bağımlısı oldum. Önümüzdeki yaz mutlaka döneceğim.",
name: "Mehmet Ö.",
from: "Ankara",
text: "Harika bir kitesurf plajı. Öğleden sonraları istikrarlı termal rüzgarlar ve yumuşak kum. Çoğu sörfçü (Eylül-Ekim) dalış kıyafeti giyiyor ama ben şortla bile rahattım. Sadece plaja girmek için ödeme yapmanıza gerek yok, yalnızca sörf yapacaksanız ödüyorsunuz.",
name: "Shebastian Reyes",
from: "Google Yorumları",
},
];
const testimonialsEn = [
{
text: "I've stayed in many places in Akyaka, but Kite Beach was a completely different experience. The sound of the river, kitesurf lessons, and the amazing breakfast — everything was perfect.",
name: "Ayse K.",
from: "Istanbul",
text: "Very atmospheric place. Cool man, kite and wind. Coffee not bad, but expensive.",
name: "Elena Gerasimova",
from: "Google Reviews",
},
{
text: "Yoga sessions on the wooden platform, early in the morning with a river view. Hard to put into words — a must-try.",
name: "Marco B.",
from: "Italy",
text: "Loved the vibes love the beach and watching kite surfers. Lots of cool places to hang out, good food and good music. You cannot come to Akyaka without coming to kite surfing.",
name: "Dhyani Love",
from: "Google Reviews",
},
{
text: "The instructors are very caring. After my kitesurf lessons, I became addicted. I will definitely return next summer.",
name: "Mehmet O.",
from: "Ankara",
text: "Great kite beach. Steady thermal afternoon winds and soft sand. Most kiters wear a wetsuit (Sep - Oct) but I was fine with just board shorts. You don't have to pay just to go to the beach, only if you plan to kite.",
name: "Shebastian Reyes",
from: "Google Reviews",
},
];
import { getDictionary, Locale } from "./dictionaries";
import prisma from "@/lib/prisma";
import { buildMeta } from "@/lib/metadata";
import type { Metadata } from "next";
export async function generateMetadata(
{ params }: { params: Promise<{ lang: string }> }
): Promise<Metadata> {
@@ -185,519 +147,23 @@ export default async function Home({ params }: { params: Promise<{ lang: string
statsGuestLabel: lang === "en" ? "Happy Guests" : "Mutlu Misafir",
statsYearCount: "10+",
statsYearLabel: lang === "en" ? "Years Experience" : "Yıl Deneyim",
badgeIkoTitle: "IKO",
badgeIkoLabel: lang === "en" ? "Certified" : "Sertifikalı"
badgeIkoTitle: "Kitesurf",
badgeIkoLabel: lang === "en" ? "School" : "Okulu"
};
const displayFeatures = dbFeatures.length > 0 ? dbFeatures : defaultFeatures;
return (
<div className="flex flex-col min-h-screen">
{/* ──────────────────────────────────────────
HERO
────────────────────────────────────────── */}
<section className="relative h-screen w-full flex items-center justify-center overflow-hidden">
<HeroVideo />
{/* Floating season badge
<div className="absolute top-32 right-6 md:right-16 z-20 animate-float hidden sm:block">
<div className="bg-white/10 backdrop-blur-md border border-white/20 rounded-2xl px-5 py-4 text-white">
<span className="block text-accent font-bold text-[10px] tracking-[0.25em] uppercase mb-1">Sezon</span>
<span className="font-serif text-xl font-medium">Nisan — Ekim</span>
</div>
</div>
*/}
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
<div className="animate-fade-up delay-100">
<span className="inline-block text-white/60 text-xs tracking-[0.35em] uppercase mb-8 font-light">
{heroData.location}
</span>
</div>
<div className="flex items-center justify-center gap-4 md:gap-6 mb-6 animate-fade-up delay-200">
<span
className="text-xs md:text-sm tracking-[0.4em] font-bold uppercase whitespace-nowrap text-white/90"
style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
>
{heroData.welcome}
</span>
<h1 className="font-sans font-black text-white leading-none tracking-tighter text-[clamp(4rem,10vw,10rem)] uppercase">
{heroData.title}
</h1>
</div>
<p className="animate-fade-up delay-500 text-white/75 text-base md:text-xl mb-10 font-light italic tracking-wide">
{heroData.subtitle}
</p>
<div className="animate-fade-up delay-600 flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
href={`/${lang}/aktiviteler`}
className="bg-white/10 backdrop-blur-sm border border-white/30 text-white px-10 py-4 rounded-full text-xs font-bold tracking-[0.15em] uppercase hover:bg-white/20 transition-colors w-full sm:w-auto text-center"
>
{heroData.exploreActivities}
</Link>
</div>
</div>
{/* Scroll indicator */}
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center gap-3">
<span className="text-white/40 text-[10px] tracking-[0.3em] uppercase">{heroData.scroll}</span>
<div className="w-px h-12 bg-gradient-to-b from-white/40 to-transparent animate-scroll-bounce" />
</div>
</section>
{/* ──────────────────────────────────────────
FEATURES STRIP
────────────────────────────────────────── */}
<section className="bg-dark text-white py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 md:grid-cols-5 divide-x divide-white/8">
{displayFeatures.map(({ icon, label, sub }, i) => {
const IconComponent = ICON_MAP[icon as string] || Star;
return (
<div
key={i}
className={`flex flex-col items-center justify-center gap-2 py-6 px-4 group ${i >= 2 ? "hidden md:flex" : ""}`}
>
<IconComponent className="w-5 h-5 text-accent group-hover:scale-110 transition-transform duration-300" />
<span className="text-[13px] font-semibold text-center">{label}</span>
<span className="text-[11px] text-white/40 text-center">{sub}</span>
</div>
);
})}
</div>
</div>
</section>
{/* ──────────────────────────────────────────
ABOUT
────────────────────────────────────────── */}
<section className="py-28 bg-background overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 items-center">
<ScrollReveal className="relative">
<div className="relative h-[540px] lg:h-[660px] rounded-3xl overflow-hidden shadow-2xl">
<Image
src={aboutData.image}
alt={aboutData.badge}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
</div>
{/* Stats card */}
<div className="absolute -bottom-5 -right-4 md:-right-8 bg-white rounded-2xl p-6 shadow-xl border border-sand">
<div className="grid grid-cols-2 gap-6 divide-x divide-sand">
<div className="text-center">
<span className="block font-serif text-3xl font-bold text-primary">{aboutData.statsGuestCount}</span>
<span className="block text-[10px] text-muted uppercase tracking-wider mt-1">{aboutData.statsGuestLabel}</span>
</div>
<div className="text-center pl-6">
<span className="block font-serif text-3xl font-bold text-primary">{aboutData.statsYearCount}</span>
<span className="block text-[10px] text-muted uppercase tracking-wider mt-1">{aboutData.statsYearLabel}</span>
</div>
</div>
</div>
{/* IKO badge */}
<div className="absolute -top-4 -left-4 bg-accent text-white rounded-2xl p-4 shadow-lg">
<Star className="w-4 h-4 mb-1" fill="white" />
<span className="block text-xs font-bold leading-none">{aboutData.badgeIkoTitle}</span>
<span className="block text-[10px] opacity-80 mt-0.5">{aboutData.badgeIkoLabel}</span>
</div>
</ScrollReveal>
<ScrollReveal delay={200}>
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-5">
{aboutData.badge}
</span>
<h2 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold text-dark leading-[1.05] mb-8">
{aboutData.titleLine1}<br />{aboutData.titleLine2}<br />
<span className="text-primary italic">{aboutData.titleHighlight}</span>
</h2>
<p className="text-gray-600 leading-relaxed mb-5">
{aboutData.description1}
</p>
<p className="text-gray-600 leading-relaxed mb-10">
{aboutData.description2}
</p>
<div className="section-line mb-8" />
<Link
href={`/${lang}/hakkimizda`}
className="inline-flex items-center gap-2 text-dark font-bold text-sm tracking-wide group"
>
<span className="border-b-2 border-accent pb-0.5">
{lang === "en" ? "Read Our Story" : "Hikayemizi Okuyun"}
</span>
<ArrowRight className="w-4 h-4 group-hover:translate-x-1.5 transition-transform" />
</Link>
</ScrollReveal>
</div>
</div>
</section>
{/* ──────────────────────────────────────────
ROOMS
────────────────────────────────────────── */}
{/*
<section className="py-28 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-16">
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
Konaklama
</span>
<h2 className="font-serif text-4xl md:text-5xl font-bold text-dark mb-6">Kite Beach Akyaka Otel</h2>
<p className="text-muted max-w-3xl mx-auto text-sm leading-relaxed">
Akyakanın en güzel köşesinde, nehrin dinginliği ile denizin sonsuz maviliğinin kucaklaştığı o özel noktadayız. Kapınızı açtığınız anda sizi karşılayan nehir kıyısının sessizliği, güne başlamak için en huzurlu davetiniz olacak. Doğanın kalbinde, nehir manzarasına karşı yaptığınız bir yoga seansıyla ruhunuzu dengelerken; hemen ardından Akyakanın meşhur rüzgarlarında Kite Surf ile adrenalini zirveye taşıyabilirsiniz. Günün yorgunluğunu ise, restoranımızın mutfağından çıkan lezzetlerle, Egenin kendine has o büyüleyici gün batımını izleyerek geride bırakın. Burada zaman, sadece sizin istediğiniz hızda akıyor.
</p>
</ScrollReveal>
<ScrollReveal stagger className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
{rooms.map((room, i) => (
<div
key={i}
className={`card-lift group rounded-2xl overflow-hidden bg-sand border border-sand ${i === 1 ? "md:-mt-4 md:shadow-xl" : ""
}`}
>
<div className="relative h-64 overflow-hidden">
<Image
src={room.img}
alt={room.name}
fill
sizes="(max-width: 768px) 100vw, 33vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
<span className={`absolute top-4 left-4 text-[10px] font-bold px-3 py-1.5 rounded-full tracking-wider uppercase ${room.badgeStyle} backdrop-blur-sm`}>
{room.badge}
</span>
</div>
<div className="p-7">
<h3 className="font-serif text-xl font-bold text-dark mb-2">{room.name}</h3>
<p className="text-muted mb-6 text-[13px] leading-relaxed">{room.desc}</p>
<div className="flex justify-between items-center pt-5 border-t border-sand">
<div>
<span className="text-2xl font-bold text-dark">{room.price}</span>
<span className="text-muted text-xs ml-1">/gece</span>
</div>
<Link
href="/odalar"
className={`text-[11px] px-5 py-2.5 rounded-full font-bold tracking-wider uppercase transition-colors ${room.cta === "accent"
? "bg-accent text-white hover:bg-accent/90"
: "bg-primary text-white hover:bg-primary/90"
}`}
>
İncele
</Link>
</div>
</div>
</div>
))}
</ScrollReveal>
<div className="text-center mt-12">
<Link
href="/odalar"
className="inline-flex items-center gap-2 border-2 border-dark text-dark text-xs px-8 py-3.5 rounded-full font-bold uppercase tracking-widest hover:bg-dark hover:text-white transition-colors"
>
Tüm Odaları Gör
<ArrowRight className="w-3.5 h-3.5" />
</Link>
</div>
</div>
</section>
*/}
{/* ──────────────────────────────────────────
ACTIVITIES — BENTO GRID
────────────────────────────────────────── */}
<section className="py-28 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="flex flex-col md:flex-row md:items-end md:justify-between gap-6 mb-10">
<div>
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{lang === "en" ? "What Will You Do?" : "Ne Yapacaksınız?"}
</span>
<h2 className="font-serif text-5xl font-bold text-dark">
{lang === "en" ? "Activities" : "Aktiviteler"}
</h2>
</div>
<Link
href={`/${lang}/aktiviteler`}
className="inline-flex items-center gap-2 text-sm font-semibold text-primary hover:text-accent transition-colors"
>
{lang === "en" ? "See All" : "Tümünü Gör"} <ArrowRight className="w-4 h-4" />
</Link>
</ScrollReveal>
{/* Bento rows */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{activities.length > 0 ? (
activities.map((act, i) => (
<ScrollReveal
key={act.id}
className={i === 0 || i === 3 ? "md:col-span-2" : ""}
delay={i * 50}
>
<Link href={`/${lang}/aktiviteler`} className={`group relative block rounded-3xl overflow-hidden ${activities.length === 1 ? "aspect-[4/3] md:aspect-video" : i < 2 ? "h-[400px]" : "h-[280px]"}`}>
<Image
src={act.image}
alt={act.title}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className={`absolute inset-0 bg-gradient-to-t ${i === 0 ? "from-dark/90 via-dark/20" : "from-dark/80"} to-transparent`} />
<div className={`absolute bottom-0 ${i === 0 || i === 3 ? "p-8" : "p-7"}`}>
{act.badge && (
<span className="block text-accent text-[10px] tracking-[0.25em] uppercase font-bold mb-2">{act.badge}</span>
)}
<h3 className={`font-serif text-white font-bold mb-1 ${i === 0 ? "text-4xl mb-2" : i === 1 || i === 3 ? "text-3xl" : "text-2xl"}`}>{act.title}</h3>
{i === 0 && (
<p className="text-white/60 text-sm mb-4 max-w-xs opacity-0 translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-400">
{act.description}
</p>
)}
{i === 0 && (
<span className="inline-flex items-center gap-2 text-white text-sm font-semibold hover:text-accent transition-colors">
<ArrowRight className="w-3.5 h-3.5" />
</span>
)}
</div>
</Link>
</ScrollReveal>
))
) : (
<div className="md:col-span-3 py-10 text-center text-gray-500">
{lang === "en" ? "No activities added yet." : "Henüz aktivite eklenmedi."}
</div>
)}
</div>
</div>
</section>
{/* ──────────────────────────────────────────
EVENTS (PREVIEW)
────────────────────────────────────────── */}
<section className="py-28 bg-dark text-white relative overflow-hidden">
{/* Background glow */}
<div className="absolute top-0 right-0 w-96 h-96 bg-accent/20 rounded-full blur-[120px] pointer-events-none" />
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-primary/20 rounded-full blur-[150px] pointer-events-none" />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<ScrollReveal className="flex flex-col md:flex-row md:items-end md:justify-between gap-6 mb-16">
<div>
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{lang === "en" ? "Calendar" : "Takvim"}
</span>
<h2 className="font-serif text-5xl font-bold">
{lang === "en" ? "Events" : "Etkinlikler"}
</h2>
</div>
<Link
href={`/${lang}/etkinlikler`}
className="inline-flex items-center gap-2 text-sm font-semibold text-white/70 hover:text-white transition-colors border border-white/20 hover:border-white px-6 py-2.5 rounded-full"
>
{lang === "en" ? "All Events" : "Tüm Etkinlikler"} <ArrowRight className="w-4 h-4" />
</Link>
</ScrollReveal>
<ScrollReveal delay={100}>
{events.length > 0 ? (
events.map((ev) => (
<div key={ev.id} className="group relative flex flex-col md:flex-row gap-8 bg-white/5 border border-white/10 rounded-3xl p-6 md:p-10 hover:bg-white/10 transition-colors backdrop-blur-sm mb-6">
<div className="relative w-full md:w-1/3 h-[250px] md:h-auto rounded-2xl overflow-hidden shrink-0">
<Image
src={ev.image}
alt={ev.title}
fill
sizes="(max-width: 768px) 100vw, 33vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
<div className="flex flex-col justify-center w-full md:w-2/3">
{ev.badge && (
<div className="inline-block bg-accent/20 text-accent text-[10px] font-bold px-3 py-1.5 rounded-full tracking-widest uppercase mb-4 w-max">
{ev.badge}
</div>
)}
<h3 className="font-serif text-3xl md:text-4xl font-bold mb-4">{ev.title}</h3>
<p className="text-white/60 leading-relaxed mb-6 max-w-xl">
{ev.description}
</p>
<div className="flex flex-wrap items-center gap-6 mb-8 text-sm text-white/80">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent" /> {ev.date}
</span>
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent" /> {ev.time}
</span>
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent" /> {ev.location}
</span>
</div>
<Link
href={`/${lang}/etkinlikler`}
className="inline-flex items-center gap-2 text-xs font-bold tracking-widest uppercase text-accent hover:text-white transition-colors w-max"
>
{lang === "en" ? "View Details" : "Detayları İncele"} <ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="py-10 text-center text-white/50">
{lang === "en" ? "No events added yet." : "Henüz etkinlik eklenmedi."}
</div>
)}
</ScrollReveal>
</div>
</section>
{/* ──────────────────────────────────────────
INSTAGRAM FEED
────────────────────────────────────────── */}
{instagramPosts.length > 0 && (
<section className="py-28 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-16">
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
@kitebeachakyaka
</span>
<h2 className="font-serif text-5xl font-bold text-dark">Instagram</h2>
</ScrollReveal>
<ScrollReveal stagger className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
{instagramPosts.map((post: any) => (
<a
key={post.id}
href={`https://instagram.com/p/${post.code}`}
target="_blank"
rel="noreferrer"
className="group block relative rounded-2xl overflow-hidden aspect-[4/5] border border-sand"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={post.image_versions2?.candidates[0]?.url}
alt={post.caption?.text?.substring(0, 100) || "Instagram post"}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className="absolute inset-0 bg-dark/60 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col items-center justify-center text-white p-6 text-center">
<Camera className="w-8 h-8 mb-3" />
<p className="text-xs line-clamp-3">
{post.caption?.text}
</p>
</div>
</a>
))}
</ScrollReveal>
<div className="text-center mt-12">
<a
href="https://instagram.com/kitebeachakyaka"
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 border-2 border-dark text-dark text-xs px-8 py-3.5 rounded-full font-bold uppercase tracking-widest hover:bg-dark hover:text-white transition-colors"
>
{lang === "en" ? "Follow Us" : "Bizi Takip Edin"}
<ArrowRight className="w-3.5 h-3.5" />
</a>
</div>
</div>
</section>
)}
{/* ──────────────────────────────────────────
TESTIMONIALS
────────────────────────────────────────── */}
<section className="py-28 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-16">
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{lang === "en" ? "Guest Reviews" : "Misafir Yorumları"}
</span>
<h2 className="font-serif text-5xl font-bold text-dark">
{lang === "en" ? "Experiences" : "Deneyimler"}
</h2>
</ScrollReveal>
<ScrollReveal stagger className="grid grid-cols-1 md:grid-cols-3 gap-6">
{displayTestimonials.map((t, i) => (
<div key={i} className="relative bg-sand rounded-2xl p-8 border border-sand overflow-hidden">
{/* Large decorative quote */}
<span className="absolute top-3 right-5 font-serif text-[8rem] leading-none text-primary/8 select-none">
"
</span>
{/* Stars */}
<div className="flex gap-1 mb-5 relative z-10">
{Array.from({ length: 5 }).map((_, j) => (
<Star key={j} className="w-3.5 h-3.5 text-accent" fill="#E87040" />
))}
</div>
<p className="text-foreground/75 text-[13px] leading-relaxed mb-6 relative z-10 italic">
"{t.text}"
</p>
<div className="flex items-center gap-3 relative z-10">
<div className="w-9 h-9 bg-primary/10 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-primary font-bold text-sm">{t.name[0]}</span>
</div>
<div>
<span className="block font-semibold text-dark text-sm">{t.name}</span>
<span className="text-muted text-xs">{t.from}</span>
</div>
</div>
</div>
))}
</ScrollReveal>
</div>
</section>
{/* ──────────────────────────────────────────
CTA BAND
────────────────────────────────────────── */}
<section className="relative py-28 overflow-hidden">
<div
className="absolute inset-0 animate-gradient-pan"
style={{
background:
"linear-gradient(135deg, #0D3D50 0%, #1A6B8A 35%, #0E1A24 60%, #1A4A60 100%)",
}}
/>
<div className="absolute inset-0 opacity-[0.04]">
<Image
src="https://images.unsplash.com/photo-1498623116890-37e912163d5d?q=80&w=2940&auto=format&fit=crop"
alt=""
fill
sizes="100vw"
className="object-cover"
/>
</div>
<div className="relative z-10 max-w-3xl mx-auto px-4 text-center">
<ScrollReveal>
<span className="inline-block text-accent text-xs tracking-[0.3em] uppercase font-semibold mb-6">
{lang === "en" ? "Get Started" : "Hemen Başlayın"}
</span>
<h2 className="font-serif text-5xl md:text-6xl font-bold text-white leading-tight mb-6">
{lang === "en" ? (
<>Are you ready to plan<br />your dream vacation?</>
) : (
<>Hayalindeki tatili<br />planlamaya hazır mısın?</>
)}
</h2>
<p className="text-white/50 mb-10 text-base leading-relaxed">
{lang === "en"
? "The wind is calling you. Book your spot now and become a part of this unique experience."
: "Rüzgar seni çağırıyor. Hemen yerini ayırt ve bu eşsiz deneyimin bir parçası ol."}
</p>
</ScrollReveal>
</div>
</section>
<HeroSection heroData={heroData} lang={lang} />
<FeaturesStrip features={displayFeatures} />
<AboutSection aboutData={aboutData} lang={lang} />
<RoomsSection lang={lang} />
<ActivitiesGrid activities={activities} lang={lang} />
<EventsPreview events={events} lang={lang} />
<InstagramFeed instagramPosts={instagramPosts} lang={lang} />
<TestimonialsSection testimonials={displayTestimonials} lang={lang} />
<CtaBand lang={lang} />
</div>
);
}
+1 -1
View File
@@ -12,7 +12,7 @@ export function GET() {
- **Location**: Akyaka Mahallesi, Akçapınar Mevkii, Akyaka, Muğla, Turkey
- **Coordinates**: 37.0543° N, 28.3286° E
- **Phone / WhatsApp**: +90 533 081 6181
- **Phone / WhatsApp**: +90 544 504 03 10
- **Email**: info@kitebeachakyaka.com.tr
- **Reception hours**: 08:00 22:00 daily
- **Type**: Boutique hotel, kitesurf school, beach club, restaurant & bar
+1 -1
View File
@@ -4,7 +4,7 @@ const baseUrl = 'https://kitebeachakyaka.com.tr';
const langs = ['tr', 'en'];
const routes = [
{ path: '', changeFrequency: 'daily' as const, priority: 1.0 },
// { path: '/odalar', changeFrequency: 'weekly' as const, priority: 0.9 },
{ path: '/odalar', changeFrequency: 'weekly' as const, priority: 0.9 },
{ path: '/aktiviteler', changeFrequency: 'weekly' as const, priority: 0.9 },
{ path: '/etkinlikler', changeFrequency: 'weekly' as const, priority: 0.9 },
{ path: '/restoran', changeFrequency: 'weekly' as const, priority: 0.8 },
+42
View File
@@ -0,0 +1,42 @@
import 'dotenv/config';
import prisma from './lib/prisma';
import bcrypt from 'bcryptjs';
async function main() {
const users = await prisma.user.findMany();
console.log("Users in DB:", users.length);
for (const u of users) {
console.log(`- ID: ${u.id}, Username: ${u.username}`);
}
if (users.length > 0) {
// Force reset the password of the first user to what's in .env just in case
const username = process.env.ADMIN_USERNAME || "admin";
const password = process.env.ADMIN_PASSWORD || "secretpassword";
console.log(`Updating password for user ${username} to ${password}...`);
const hashedPassword = await bcrypt.hash(password, 10);
// Check if the exact user exists
const exactUser = await prisma.user.findUnique({ where: { username } });
if (exactUser) {
await prisma.user.update({
where: { username },
data: { password: hashedPassword }
});
console.log(`Password reset successfully for ${username}.`);
} else {
console.log(`No exact user found with username ${username}. Updating first user.`);
await prisma.user.update({
where: { id: users[0].id },
data: { username: username, password: hashedPassword }
});
console.log(`Updated first user to be ${username} with new password.`);
}
} else {
console.log("No users exist in DB. The auth code will create one automatically.");
}
}
main()
.catch(e => console.error(e))
.finally(() => prisma.$disconnect());
+144
View File
@@ -0,0 +1,144 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import { X } from "lucide-react";
interface BentoGalleryProps {
images: string[];
}
export default function BentoGallery({ images }: BentoGalleryProps) {
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
const openLightbox = (index: number) => {
setLightboxIndex(index);
};
const closeLightbox = () => {
setLightboxIndex(null);
};
return (
<>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 auto-rows-[200px] md:auto-rows-[250px]">
{/* Big Feature Image */}
{images.length > 0 && (
<div
className="relative rounded-3xl overflow-hidden group col-span-2 row-span-2 cursor-pointer h-full"
onClick={() => openLightbox(0)}
>
<Image
src={images[0]}
alt="Gallery Image 1"
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
)}
{/* Top Right */}
{images.length > 1 && (
<div
className="relative rounded-3xl overflow-hidden group col-span-2 row-span-1 cursor-pointer h-full"
onClick={() => openLightbox(1)}
>
<Image
src={images[1]}
alt="Gallery Image 2"
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
)}
{/* Middle Right 1 */}
{images.length > 2 && (
<div
className="relative rounded-3xl overflow-hidden group col-span-1 row-span-1 cursor-pointer h-full"
onClick={() => openLightbox(2)}
>
<Image
src={images[2]}
alt="Gallery Image 3"
fill
sizes="(max-width: 768px) 50vw, 25vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
)}
{/* Middle Right 2 */}
{images.length > 3 && (
<div
className="relative rounded-3xl overflow-hidden group col-span-1 row-span-1 cursor-pointer h-full"
onClick={() => openLightbox(3)}
>
<Image
src={images[3]}
alt="Gallery Image 4"
fill
sizes="(max-width: 768px) 50vw, 25vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
)}
{/* Bottom Row 1 */}
{images.length > 4 && (
<div
className="relative rounded-3xl overflow-hidden group col-span-2 md:col-span-3 row-span-1 cursor-pointer h-full"
onClick={() => openLightbox(4)}
>
<Image
src={images[4]}
alt="Gallery Image 5"
fill
sizes="(max-width: 768px) 100vw, 75vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
)}
{/* Bottom Row 2 */}
{images.length > 5 && (
<div
className="relative rounded-3xl overflow-hidden group col-span-2 md:col-span-1 row-span-1 cursor-pointer h-full"
onClick={() => openLightbox(5)}
>
<Image
src={images[5]}
alt="Gallery Image 6"
fill
sizes="(max-width: 768px) 100vw, 25vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
)}
</div>
{/* Lightbox */}
{lightboxIndex !== null && (
<div
className="fixed inset-0 z-[100] bg-dark/95 backdrop-blur-sm flex items-center justify-center p-4 cursor-zoom-out opacity-0 animate-fade-in"
style={{ animationFillMode: "forwards" }}
onClick={closeLightbox}
>
<div className="relative max-w-6xl w-full h-[85vh] aspect-auto" onClick={(e) => e.stopPropagation()}>
<Image
src={images[lightboxIndex]}
alt="Lightbox Image"
fill
sizes="100vw"
className="object-contain rounded-xl shadow-2xl"
/>
</div>
<button
className="absolute top-6 right-6 text-white/60 hover:text-white transition-colors bg-white/10 hover:bg-white/20 p-2 rounded-full"
onClick={closeLightbox}
>
<X size={28} />
</button>
</div>
)}
</>
);
}
+4 -4
View File
@@ -33,7 +33,7 @@ export default function Footer({ lang = "tr" }: { lang?: string }) {
const isEn = lang === "en";
const quickLinks = isEn ? [
{ name: "Home", href: "/en" },
// { name: "Rooms", href: "/en/odalar" },
{ name: "Rooms", href: "/en/odalar" },
{ name: "Activities", href: "/en/aktiviteler"},
{ name: "Restaurant", href: "/en/restoran" },
{ name: "Events", href: "/en/etkinlikler"},
@@ -41,7 +41,7 @@ export default function Footer({ lang = "tr" }: { lang?: string }) {
{ name: "About Us", href: "/en/hakkimizda" },
] : [
{ name: "Ana Sayfa", href: "/tr" },
// { name: "Odalar", href: "/tr/odalar" },
{ name: "Odalar", href: "/tr/odalar" },
{ name: "Aktiviteler", href: "/tr/aktiviteler"},
{ name: "Restoran", href: "/tr/restoran" },
{ name: "Etkinlikler", href: "/tr/etkinlikler"},
@@ -134,10 +134,10 @@ export default function Footer({ lang = "tr" }: { lang?: string }) {
<Phone size={13} className="text-accent" />
</div>
<a
href="tel:+905330816181"
href="tel:+905445040310"
className="text-white/50 hover:text-white transition-colors text-[13px]"
>
+90 533 081 6181
+90 544 504 03 10
</a>
</li>
<li className="flex items-center gap-3">
+2 -2
View File
@@ -8,7 +8,7 @@ import { motion, AnimatePresence } from "framer-motion";
import { Menu, X, Globe } from "lucide-react";
const navLinksEn = [
// { name: "Rooms", href: "/en/odalar" },
{ name: "Rooms", href: "/en/odalar" },
{ name: "Activities", href: "/en/aktiviteler" },
{ name: "Restaurant", href: "/en/restoran" },
{ name: "Events", href: "/en/etkinlikler" },
@@ -17,7 +17,7 @@ const navLinksEn = [
];
const navLinksTr = [
// { name: "Odalar", href: "/tr/odalar" },
{ name: "Odalar", href: "/tr/odalar" },
{ name: "Aktiviteler", href: "/tr/aktiviteler" },
{ name: "Restoran", href: "/tr/restoran" },
{ name: "Etkinlikler", href: "/tr/etkinlikler" },
+1 -1
View File
@@ -6,7 +6,7 @@ import { motion } from "framer-motion";
export default function WhatsAppButton() {
return (
<motion.a
href="https://wa.me/905330816181"
href="https://wa.me/905445040310"
target="_blank"
rel="noopener noreferrer"
className="fixed bottom-6 right-6 z-50 flex items-center justify-center w-14 h-14 bg-green-500 text-white rounded-full shadow-lg hover:shadow-2xl hover:bg-green-600 transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
+90
View File
@@ -0,0 +1,90 @@
import Image from "next/image";
import Link from "next/link";
import { Star, ArrowRight } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
interface AboutSectionProps {
aboutData: {
image: string;
badge: string;
statsGuestCount: string;
statsGuestLabel: string;
statsYearCount: string;
statsYearLabel: string;
badgeIkoTitle: string;
badgeIkoLabel: string;
titleLine1: string;
titleLine2: string;
titleHighlight: string;
description1: string;
description2: string;
};
lang: string;
}
export default function AboutSection({ aboutData, lang }: AboutSectionProps) {
return (
<section className="py-28 bg-background overflow-hidden">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 items-center">
<ScrollReveal className="relative">
<div className="relative h-[540px] lg:h-[660px] rounded-3xl overflow-hidden shadow-2xl">
<Image
src={aboutData.image}
alt={aboutData.badge}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
</div>
{/* Stats card */}
<div className="absolute -bottom-5 -right-4 md:-right-8 bg-white rounded-2xl p-6 shadow-xl border border-sand">
<div className="grid grid-cols-2 gap-6 divide-x divide-sand">
<div className="text-center">
<span className="block font-serif text-3xl font-bold text-primary">{aboutData.statsGuestCount}</span>
<span className="block text-[10px] text-muted uppercase tracking-wider mt-1">{aboutData.statsGuestLabel}</span>
</div>
<div className="text-center pl-6">
<span className="block font-serif text-3xl font-bold text-primary">{aboutData.statsYearCount}</span>
<span className="block text-[10px] text-muted uppercase tracking-wider mt-1">{aboutData.statsYearLabel}</span>
</div>
</div>
</div>
{/* IKO badge */}
<div className="absolute -top-4 -left-4 bg-accent text-white rounded-2xl p-4 shadow-lg">
<Star className="w-4 h-4 mb-1" fill="white" />
<span className="block text-xs font-bold leading-none">{aboutData.badgeIkoTitle}</span>
<span className="block text-[10px] opacity-80 mt-0.5">{aboutData.badgeIkoLabel}</span>
</div>
</ScrollReveal>
<ScrollReveal delay={200}>
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-5">
{aboutData.badge}
</span>
<h2 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold text-dark leading-[1.05] mb-8">
{aboutData.titleLine1}<br />{aboutData.titleLine2}<br />
<span className="text-primary italic">{aboutData.titleHighlight}</span>
</h2>
<p className="text-gray-600 leading-relaxed mb-5">
{aboutData.description1}
</p>
<p className="text-gray-600 leading-relaxed mb-10">
{aboutData.description2}
</p>
<div className="section-line mb-8" />
<Link
href={`/${lang}/hakkimizda`}
className="inline-flex items-center gap-2 text-dark font-bold text-sm tracking-wide group"
>
<span className="border-b-2 border-accent pb-0.5">
{lang === "en" ? "Read Our Story" : "Hikayemizi Okuyun"}
</span>
<ArrowRight className="w-4 h-4 group-hover:translate-x-1.5 transition-transform" />
</Link>
</ScrollReveal>
</div>
</div>
</section>
);
}
+81
View File
@@ -0,0 +1,81 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
interface Activity {
id: number | string;
image: string;
title: string;
description: string;
badge?: string | null;
}
interface ActivitiesGridProps {
activities: Activity[];
lang: string;
}
export default function ActivitiesGrid({ activities, lang }: ActivitiesGridProps) {
return (
<section className="py-28 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="flex flex-col md:flex-row md:items-end md:justify-between gap-6 mb-10">
<div>
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{lang === "en" ? "What Will You Do?" : "Ne Yapacaksınız?"}
</span>
<h2 className="font-serif text-5xl font-bold text-dark">
{lang === "en" ? "Activities" : "Aktiviteler"}
</h2>
</div>
<Link
href={`/${lang}/aktiviteler`}
className="inline-flex items-center gap-2 text-sm font-semibold text-primary hover:text-accent transition-colors"
>
{lang === "en" ? "See All" : "Tümünü Gör"} <ArrowRight className="w-4 h-4" />
</Link>
</ScrollReveal>
{/* Single Row 3 Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{activities.length > 0 ? (
activities.map((act, i) => (
<ScrollReveal
key={act.id}
delay={i * 50}
>
<Link href={`/${lang}/aktiviteler`} className={`group relative block rounded-3xl overflow-hidden h-[400px]`}>
<Image
src={act.image}
alt={act.title}
fill
sizes="(max-width: 768px) 100vw, 33vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className={`absolute inset-0 bg-gradient-to-t from-dark/90 via-dark/30 to-transparent`} />
<div className={`absolute bottom-0 p-8 flex flex-col justify-end h-full`}>
{act.badge && (
<span className="block text-accent text-[10px] tracking-[0.25em] uppercase font-bold mb-3">{act.badge}</span>
)}
<h3 className={`font-serif text-white font-bold mb-3 text-3xl`}>{act.title}</h3>
<p className="text-white/70 text-sm mb-4 max-w-xs line-clamp-3">
{act.description}
</p>
<span className="inline-flex items-center gap-2 text-white text-sm font-semibold hover:text-accent transition-colors mt-auto opacity-0 translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 duration-400">
<ArrowRight className="w-4 h-4" />
</span>
</div>
</Link>
</ScrollReveal>
))
) : (
<div className="md:col-span-3 py-10 text-center text-gray-500">
{lang === "en" ? "No activities added yet." : "Henüz aktivite eklenmedi."}
</div>
)}
</div>
</div>
</section>
);
}
+50
View File
@@ -0,0 +1,50 @@
import Image from "next/image";
import ScrollReveal from "@/components/ScrollReveal";
interface CtaBandProps {
lang: string;
}
export default function CtaBand({ lang }: CtaBandProps) {
return (
<section className="relative py-28 overflow-hidden">
<div
className="absolute inset-0 animate-gradient-pan"
style={{
background:
"linear-gradient(135deg, #0D3D50 0%, #1A6B8A 35%, #0E1A24 60%, #1A4A60 100%)",
}}
/>
<div className="absolute inset-0 opacity-[0.04]">
<Image
src="https://images.unsplash.com/photo-1498623116890-37e912163d5d?q=80&w=2940&auto=format&fit=crop"
alt=""
fill
sizes="100vw"
className="object-cover"
/>
</div>
<div className="relative z-10 max-w-3xl mx-auto px-4 text-center">
<ScrollReveal>
<span className="inline-block text-accent text-xs tracking-[0.3em] uppercase font-semibold mb-6">
{lang === "en" ? "Get Started" : "Hemen Başlayın"}
</span>
<h2 className="font-serif text-5xl md:text-6xl font-bold text-white leading-tight mb-6">
{lang === "en" ? (
<>Are you ready to plan<br />your dream vacation?</>
) : (
<>Hayalindeki tatili<br />planlamaya hazır mısın?</>
)}
</h2>
<p className="text-white/50 mb-10 text-base leading-relaxed">
{lang === "en"
? "The wind is calling you. Book your spot now and become a part of this unique experience."
: "Rüzgar seni çağırıyor. Hemen yerini ayırt ve bu eşsiz deneyimin bir parçası ol."}
</p>
</ScrollReveal>
</div>
</section>
);
}
+99
View File
@@ -0,0 +1,99 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
interface Event {
id: number | string;
image: string;
title: string;
description: string;
badge?: string | null;
date: string;
time: string;
location: string;
}
interface EventsPreviewProps {
events: Event[];
lang: string;
}
export default function EventsPreview({ events, lang }: EventsPreviewProps) {
return (
<section className="py-28 bg-dark text-white relative overflow-hidden">
{/* Background glow */}
<div className="absolute top-0 right-0 w-96 h-96 bg-accent/20 rounded-full blur-[120px] pointer-events-none" />
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-primary/20 rounded-full blur-[150px] pointer-events-none" />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<ScrollReveal className="flex flex-col md:flex-row md:items-end md:justify-between gap-6 mb-16">
<div>
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{lang === "en" ? "Calendar" : "Takvim"}
</span>
<h2 className="font-serif text-5xl font-bold">
{lang === "en" ? "Events" : "Etkinlikler"}
</h2>
</div>
<Link
href={`/${lang}/etkinlikler`}
className="inline-flex items-center gap-2 text-sm font-semibold text-white/70 hover:text-white transition-colors border border-white/20 hover:border-white px-6 py-2.5 rounded-full"
>
{lang === "en" ? "All Events" : "Tüm Etkinlikler"} <ArrowRight className="w-4 h-4" />
</Link>
</ScrollReveal>
<ScrollReveal delay={100}>
{events.length > 0 ? (
events.map((ev) => (
<div key={ev.id} className="group relative flex flex-col md:flex-row gap-8 bg-white/5 border border-white/10 rounded-3xl p-6 md:p-10 hover:bg-white/10 transition-colors backdrop-blur-sm mb-6">
<div className="relative w-full md:w-1/3 h-[250px] md:h-auto rounded-2xl overflow-hidden shrink-0">
<Image
src={ev.image}
alt={ev.title}
fill
sizes="(max-width: 768px) 100vw, 33vw"
className="object-cover group-hover:scale-105 transition-transform duration-700"
/>
</div>
<div className="flex flex-col justify-center w-full md:w-2/3">
{ev.badge && (
<div className="inline-block bg-accent/20 text-accent text-[10px] font-bold px-3 py-1.5 rounded-full tracking-widest uppercase mb-4 w-max">
{ev.badge}
</div>
)}
<h3 className="font-serif text-3xl md:text-4xl font-bold mb-4">{ev.title}</h3>
<p className="text-white/60 leading-relaxed mb-6 max-w-xl">
{ev.description}
</p>
<div className="flex flex-wrap items-center gap-6 mb-8 text-sm text-white/80">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent" /> {ev.date}
</span>
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent" /> {ev.time}
</span>
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-accent" /> {ev.location}
</span>
</div>
<Link
href={`/${lang}/etkinlikler`}
className="inline-flex items-center gap-2 text-xs font-bold tracking-widest uppercase text-accent hover:text-white transition-colors w-max"
>
{lang === "en" ? "View Details" : "Detayları İncele"} <ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
))
) : (
<div className="py-10 text-center text-white/50">
{lang === "en" ? "No events added yet." : "Henüz etkinlik eklenmedi."}
</div>
)}
</ScrollReveal>
</div>
</section>
);
}
+46
View File
@@ -0,0 +1,46 @@
import {
Star, Wind, Anchor, Utensils, BedDouble, Sun,
Waves, Bike, Coffee, Music, MapPin, Camera, Heart, Compass,
Fish, Mountain, Dumbbell, Leaf, Shield, Umbrella, Flame,
type LucideIcon,
} from "lucide-react";
const ICON_MAP: Record<string, LucideIcon> = {
Wind, Anchor, Utensils, BedDouble, Sun, Star, Waves, Bike,
Coffee, Music, MapPin, Camera, Heart, Compass, Fish, Mountain,
Dumbbell, Leaf, Shield, Umbrella, Flame,
};
interface Feature {
icon: string | null;
label: string;
sub: string;
}
interface FeaturesStripProps {
features: Feature[];
}
export default function FeaturesStrip({ features }: FeaturesStripProps) {
return (
<section className="bg-dark text-white py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 md:grid-cols-5 divide-x divide-white/8">
{features.map(({ icon, label, sub }, i) => {
const IconComponent = ICON_MAP[icon as string] || Star;
return (
<div
key={i}
className={`flex flex-col items-center justify-center gap-2 py-6 px-4 group ${i >= 2 ? "hidden md:flex" : ""}`}
>
<IconComponent className="w-5 h-5 text-accent group-hover:scale-110 transition-transform duration-300" />
<span className="text-[13px] font-semibold text-center">{label}</span>
<span className="text-[11px] text-white/40 text-center">{sub}</span>
</div>
);
})}
</div>
</div>
</section>
);
}
+61
View File
@@ -0,0 +1,61 @@
import Link from "next/link";
import HeroVideo from "@/components/HeroVideo";
interface HeroSectionProps {
heroData: {
location: string;
welcome: string;
title: string;
subtitle: string;
exploreActivities: string;
scroll: string;
};
lang: string;
}
export default function HeroSection({ heroData, lang }: HeroSectionProps) {
return (
<section className="relative h-screen w-full flex items-center justify-center overflow-hidden">
<HeroVideo />
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
<div className="animate-fade-up delay-100">
<span className="inline-block text-white/60 text-xs tracking-[0.35em] uppercase mb-8 font-light">
{heroData.location}
</span>
</div>
<div className="flex items-center justify-center gap-4 md:gap-6 mb-6 animate-fade-up delay-200">
<span
className="text-xs md:text-sm tracking-[0.4em] font-bold uppercase whitespace-nowrap text-white/90"
style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
>
{heroData.welcome}
</span>
<h1 className="font-sans font-black text-white leading-none tracking-tighter text-[clamp(4rem,10vw,10rem)] uppercase">
{heroData.title}
</h1>
</div>
<p className="animate-fade-up delay-500 text-white/75 text-base md:text-xl mb-10 font-light italic tracking-wide">
{heroData.subtitle}
</p>
<div className="animate-fade-up delay-600 flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
href={`/${lang}/aktiviteler`}
className="bg-white/10 backdrop-blur-sm border border-white/30 text-white px-10 py-4 rounded-full text-xs font-bold tracking-[0.15em] uppercase hover:bg-white/20 transition-colors w-full sm:w-auto text-center"
>
{heroData.exploreActivities}
</Link>
</div>
</div>
{/* Scroll indicator */}
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center gap-3">
<span className="text-white/40 text-[10px] tracking-[0.3em] uppercase">{heroData.scroll}</span>
<div className="w-px h-12 bg-gradient-to-b from-white/40 to-transparent animate-scroll-bounce" />
</div>
</section>
);
}
+63
View File
@@ -0,0 +1,63 @@
import { ArrowRight, Camera } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
interface InstagramFeedProps {
instagramPosts: any[];
lang: string;
}
export default function InstagramFeed({ instagramPosts, lang }: InstagramFeedProps) {
if (!instagramPosts || instagramPosts.length === 0) {
return null;
}
return (
<section className="py-28 bg-background">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-16">
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
@kitebeachakyaka
</span>
<h2 className="font-serif text-5xl font-bold text-dark">Instagram</h2>
</ScrollReveal>
<ScrollReveal stagger className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-6">
{instagramPosts.map((post: any) => (
<a
key={post.id}
href={`https://instagram.com/p/${post.code}`}
target="_blank"
rel="noreferrer"
className="group block relative rounded-2xl overflow-hidden aspect-[4/5] border border-sand"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={post.image_versions2?.candidates[0]?.url}
alt={post.caption?.text?.substring(0, 100) || "Instagram post"}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className="absolute inset-0 bg-dark/60 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex flex-col items-center justify-center text-white p-6 text-center">
<Camera className="w-8 h-8 mb-3" />
<p className="text-xs line-clamp-3">
{post.caption?.text}
</p>
</div>
</a>
))}
</ScrollReveal>
<div className="text-center mt-12">
<a
href="https://instagram.com/kitebeachakyaka"
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 border-2 border-dark text-dark text-xs px-8 py-3.5 rounded-full font-bold uppercase tracking-widest hover:bg-dark hover:text-white transition-colors"
>
{lang === "en" ? "Follow Us" : "Bizi Takip Edin"}
<ArrowRight className="w-3.5 h-3.5" />
</a>
</div>
</div>
</section>
);
}
+57
View File
@@ -0,0 +1,57 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRight, Info } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
import BentoGallery from "@/components/BentoGallery";
export default function RoomsSection({ lang }: { lang: string }) {
const isEn = lang === "en";
const galleryImages = [
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.15.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.18.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.19.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.20%20(1).jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.20.jpeg",
"https://media.ayris.tech/t/kitebeachakyaka/rooms/WhatsApp%20Image%202026-07-13%20at%2017.02.21.jpeg",
];
return (
<section className="py-28 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-16">
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{isEn ? "Accommodation" : "Konaklama"}
</span>
<h2 className="font-serif text-4xl md:text-5xl font-bold text-dark mb-6">Kite Beach Akyaka Otel</h2>
<p className="text-muted max-w-3xl mx-auto text-sm leading-relaxed mb-8">
{isEn
? "We are at that special point in Akyaka's most beautiful corner, where the tranquility of the river embraces the endless blue of the sea. The silence of the riverbank that greets you as soon as you open your door will be your most peaceful invitation to start the day. You can balance your soul with a yoga session against the river view in the heart of nature, and then push the adrenaline to the top with Kite Surfing in Akyaka's famous winds. Leave the tiredness of the day behind by tasting the delicacies from our restaurant's kitchen and watching the enchanting sunset of the Aegean. Here, time flows only at the pace you desire."
: "Akyakanın en güzel köşesinde, nehrin dinginliği ile denizin sonsuz maviliğinin kucaklaştığı o özel noktadayız. Kapınızı açtığınız anda sizi karşılayan nehir kıyısının sessizliği, güne başlamak için en huzurlu davetiniz olacak. Doğanın kalbinde, nehir manzarasına karşı yaptığınız bir yoga seansıyla ruhunuzu dengelerken; hemen ardından Akyakanın meşhur rüzgarlarında Kite Surf ile adrenalini zirveye taşıyabilirsiniz. Günün yorgunluğunu ise, restoranımızın mutfağından çıkan lezzetlerle, Egenin kendine has o büyüleyici gün batımını izleyerek geride bırakın. Burada zaman, sadece sizin istediğiniz hızda akıyor."}
</p>
<div className="inline-flex items-center gap-3 bg-primary/10 text-primary border border-primary/20 px-6 py-3 rounded-2xl shadow-sm">
<Info size={18} />
<span className="font-bold text-[15px] tracking-wide">
{isEn ? "Price: 8000 TL / Night (Breakfast Included)" : "Fiyat Bilgisi: 8000 TL / Gecelik (Kahvaltı Dahil)"}
</span>
</div>
</ScrollReveal>
<ScrollReveal>
<BentoGallery images={galleryImages} />
</ScrollReveal>
<div className="text-center mt-12">
<Link
href={isEn ? "/en/odalar" : "/tr/odalar"}
className="inline-flex items-center gap-2 border-2 border-dark text-dark text-xs px-8 py-3.5 rounded-full font-bold uppercase tracking-widest hover:bg-dark hover:text-white transition-colors"
>
{isEn ? "View Details" : "Detayları Gör"}
<ArrowRight className="w-3.5 h-3.5" />
</Link>
</div>
</div>
</section>
);
}
+59
View File
@@ -0,0 +1,59 @@
import { Star } from "lucide-react";
import ScrollReveal from "@/components/ScrollReveal";
interface Testimonial {
text: string;
name: string;
from: string;
}
interface TestimonialsSectionProps {
testimonials: Testimonial[];
lang: string;
}
export default function TestimonialsSection({ testimonials, lang }: TestimonialsSectionProps) {
return (
<section className="py-28 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<ScrollReveal className="text-center mb-16">
<span className="inline-block text-accent text-xs tracking-[0.25em] uppercase font-semibold mb-4">
{lang === "en" ? "Guest Reviews" : "Misafir Yorumları"}
</span>
<h2 className="font-serif text-5xl font-bold text-dark">
{lang === "en" ? "Experiences" : "Deneyimler"}
</h2>
</ScrollReveal>
<ScrollReveal stagger className="grid grid-cols-1 md:grid-cols-3 gap-6">
{testimonials.map((t, i) => (
<div key={i} className="relative bg-sand rounded-2xl p-8 border border-sand overflow-hidden">
{/* Large decorative quote */}
<span className="absolute top-3 right-5 font-serif text-[8rem] leading-none text-primary/8 select-none">
"
</span>
{/* Stars */}
<div className="flex gap-1 mb-5 relative z-10">
{Array.from({ length: 5 }).map((_, j) => (
<Star key={j} className="w-3.5 h-3.5 text-accent" fill="#E87040" />
))}
</div>
<p className="text-foreground/75 text-[13px] leading-relaxed mb-6 relative z-10 italic">
"{t.text}"
</p>
<div className="flex items-center gap-3 relative z-10">
<div className="w-9 h-9 bg-primary/10 rounded-full flex items-center justify-center flex-shrink-0">
<span className="text-primary font-bold text-sm">{t.name[0]}</span>
</div>
<div>
<span className="block font-semibold text-dark text-sm">{t.name}</span>
<span className="text-muted text-xs">{t.from}</span>
</div>
</div>
</div>
))}
</ScrollReveal>
</div>
</section>
);
}
+39
View File
@@ -0,0 +1,39 @@
'use client'
export default function openinaryLoader({ src, width, quality }: { src: string, width: number, quality?: number }) {
// Handle already absolute openinary URLs
let path = src;
if (src.startsWith('https://media.ayris.tech/t/')) {
// format: https://media.ayris.tech/t/w_800,h_800/moyqr/img.jpg
const parts = src.split('/');
path = parts.slice(5).join('/');
} else if (src.startsWith('https://media.ayris.tech/upload/')) {
// format: https://media.ayris.tech/upload/moyqr/img.jpg
const parts = src.split('/');
path = parts.slice(4).join('/');
} else if (src.includes('res.cloudinary.com')) {
// Correctly apply width & quality for unmigrated Cloudinary URLs
// e.g. https://res.cloudinary.com/domain/image/upload/v1234/path.jpg
// becomes: https://res.cloudinary.com/domain/image/upload/w_800,f_webp,q_75/v1234/path.jpg
const parts = src.split('/upload/');
if (parts.length === 2) {
return `${parts[0]}/upload/w_${width},f_webp,q_${quality || 75}/${parts[1]}`;
}
return src;
} else if (src.startsWith('http')) {
// For other external URLs, we append the width as a query parameter to satisfy Next.js.
const url = new URL(src);
url.searchParams.set('w', width.toString());
if (quality) {
url.searchParams.set('q', quality.toString());
}
return url.toString();
}
// Clean up any leading slash
if (path.startsWith('/')) {
path = path.substring(1);
}
return `https://media.ayris.tech/t/w_${width},f_webp,q_${quality || 75}/${path}`
}
+5
View File
@@ -0,0 +1,5 @@
const BASE = process.env.NEXT_PUBLIC_OPENINARY_URL;
export function optimizedImage(path: string, params: string) {
return `${BASE}/t/${params}/${path}`;
}
+19
View File
@@ -0,0 +1,19 @@
export async function uploadToOpeninary(file: File, folder: string) {
const formData = new FormData();
formData.append("files", file);
formData.append("folder", folder);
const res = await fetch(`${process.env.OPENINARY_API_URL}/api/upload`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENINARY_API_KEY}` },
body: formData,
});
if (!res.ok) {
const errorText = await res.text();
console.error("Openinary upload error:", errorText);
throw new Error("Upload başarısız: " + errorText);
}
const data = await res.json();
return data.files[0]; // { path, url, size, ... }
}
+6
View File
@@ -7,6 +7,12 @@ const nextConfig: NextConfig = {
formats: ["image/avif", "image/webp"],
minimumCacheTTL: 2592000,
remotePatterns: [
{
protocol: 'https',
hostname: 'media.ayris.tech',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'images.unsplash.com',
+14 -15
View File
@@ -17,7 +17,7 @@ model SiteSettings {
}
model HeroSection {
lang String @id // "tr" or "en"
lang String @id
location String
welcome String
title String
@@ -30,12 +30,12 @@ model HeroSection {
model Activity {
id Int @id @default(autoincrement())
lang String // "tr" or "en"
lang String
title String
description String
price String
image String
badge String? // e.g. "IKO Sertifikalı"
badge String?
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -43,31 +43,31 @@ model Activity {
model Event {
id Int @id @default(autoincrement())
lang String // "tr" or "en"
lang String
title String
description String
date String
time String
location String
image String
badge String? // e.g. "Yaklaşan Etkinlik"
badge String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Feature {
id Int @id @default(autoincrement())
lang String // "tr" or "en"
icon String
label String
sub String
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id Int @id @default(autoincrement())
lang String
icon String
label String
sub String
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AboutSection {
lang String @id // "tr" or "en"
lang String @id
badge String
titleLine1 String
titleLine2 String
@@ -91,4 +91,3 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+16
View File
@@ -0,0 +1,16 @@
import 'dotenv/config';
import prisma from './lib/prisma';
async function main() {
const imageUrl = "https://media.ayris.tech/t/kitebeachakyaka/kitesurf.png";
await prisma.aboutSection.updateMany({
data: {
image: imageUrl
}
});
console.log("About section image updated to the kitesurf one successfully!");
}
main().catch(console.error).finally(() => prisma.$disconnect());
+24
View File
@@ -0,0 +1,24 @@
import 'dotenv/config';
import prisma from './lib/prisma';
async function main() {
await prisma.aboutSection.updateMany({
where: { lang: 'tr' },
data: {
badgeIkoTitle: 'Kitesurf',
badgeIkoLabel: 'Okulu'
}
});
await prisma.aboutSection.updateMany({
where: { lang: 'en' },
data: {
badgeIkoTitle: 'Kitesurf',
badgeIkoLabel: 'School'
}
});
console.log('AboutSection DB updated successfully');
}
main().catch(console.error).finally(() => prisma.$disconnect());
+69
View File
@@ -0,0 +1,69 @@
import 'dotenv/config';
import prisma from './lib/prisma';
async function main() {
await prisma.activity.deleteMany({});
await prisma.activity.createMany({
data: [
{
lang: 'tr',
title: 'Kitesurf',
description: 'Akyaka\'nın muhteşem rüzgarında profesyonel eğitmenlerle kitesurf deneyimi yaşayın.',
price: 'Fiyat bilgisi için ulaşın',
image: 'https://images.unsplash.com/photo-1526367790999-0150786686a2?q=80&w=2940&auto=format&fit=crop',
badge: 'Popüler',
order: 1
},
{
lang: 'tr',
title: 'Yoga Wellness',
description: 'Nehir kenarında güne taze bir başlangıç veya gün batımında ruhunuzu dinlendiren yoga seansları.',
price: 'Fiyat bilgisi için ulaşın',
image: 'https://images.unsplash.com/photo-1545205597-3d9d02c29597?q=80&w=2940&auto=format&fit=crop',
badge: 'Rahatlatıcı',
order: 2
},
{
lang: 'tr',
title: 'Sunset DJ Party',
description: 'Gün batımını Ege\'nin muhteşem manzarası ve harika müzikler eşliğinde kutlayın.',
price: 'Ücretsiz Katılım',
image: 'https://images.unsplash.com/photo-1514525253161-7a46d19cd819?q=80&w=2874&auto=format&fit=crop',
badge: 'Eğlence',
order: 3
},
{
lang: 'en',
title: 'Kitesurf',
description: 'Experience kitesurfing with professional instructors in Akyaka\'s amazing wind.',
price: 'Contact for price',
image: 'https://images.unsplash.com/photo-1526367790999-0150786686a2?q=80&w=2940&auto=format&fit=crop',
badge: 'Popular',
order: 1
},
{
lang: 'en',
title: 'Yoga Wellness',
description: 'A fresh start to the day by the river or relaxing yoga sessions at sunset.',
price: 'Contact for price',
image: 'https://images.unsplash.com/photo-1545205597-3d9d02c29597?q=80&w=2940&auto=format&fit=crop',
badge: 'Relaxing',
order: 2
},
{
lang: 'en',
title: 'Sunset DJ Party',
description: 'Celebrate the sunset with the amazing view of the Aegean and great music.',
price: 'Free Entry',
image: 'https://images.unsplash.com/photo-1514525253161-7a46d19cd819?q=80&w=2874&auto=format&fit=crop',
badge: 'Entertainment',
order: 3
}
]
});
console.log('Activities updated successfully');
}
main().catch(console.error).finally(() => prisma.$disconnect());
+19
View File
@@ -0,0 +1,19 @@
import 'dotenv/config';
import prisma from './lib/prisma';
async function main() {
const imageUrl = "https://media.ayris.tech/t/kitebeachakyaka/kitesurf.png";
await prisma.activity.updateMany({
where: {
title: "Kitesurf"
},
data: {
image: imageUrl
}
});
console.log("Kitesurf activity image updated to the local one successfully!");
}
main().catch(console.error).finally(() => prisma.$disconnect());
+41
View File
@@ -0,0 +1,41 @@
import fs from 'fs';
import path from 'path';
import 'dotenv/config';
async function uploadFile(filePath, folder) {
const fileName = path.basename(filePath);
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: 'image/png' });
const formData = new FormData();
formData.append('files', blob, fileName);
formData.append('folder', folder);
const res = await fetch(`${process.env.OPENINARY_API_URL}/api/upload`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENINARY_API_KEY}` },
body: formData,
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Upload failed for ${fileName}: ${text}`);
}
const data = await res.json();
return data.files[0].url;
}
async function main() {
const filePath = './public/kitesurf.png';
console.log(`Uploading ${filePath}...`);
try {
const url = await uploadFile(filePath, 'kitebeachakyaka');
console.log(`Uploaded! URL: ${url}`);
} catch (e) {
console.error(e);
}
}
main().catch(console.error);
+53
View File
@@ -0,0 +1,53 @@
import fs from 'fs';
import path from 'path';
import 'dotenv/config';
async function uploadFile(filePath, folder) {
const fileName = path.basename(filePath);
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer], { type: 'image/jpeg' });
const formData = new FormData();
// We can pass a file name to the formData in Node by passing an object with name or using File (if available)
// But FormData append takes (name, value, filename)
formData.append('files', blob, fileName);
formData.append('folder', folder);
const res = await fetch(`${process.env.OPENINARY_API_URL}/api/upload`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENINARY_API_KEY}` },
body: formData,
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Upload failed for ${fileName}: ${text}`);
}
const data = await res.json();
return data.files[0].url;
}
async function main() {
const dirPath = './public/rooms';
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.jpeg') || f.endsWith('.jpg'));
const uploadedUrls = [];
for (const file of files) {
console.log(`Uploading ${file}...`);
try {
const url = await uploadFile(path.join(dirPath, file), 'kitebeachakyaka/rooms');
console.log(`Uploaded! URL: ${url}`);
uploadedUrls.push(url);
} catch (e) {
console.error(e);
}
}
console.log('\n--- UPLOADED URLS ---');
console.log(JSON.stringify(uploadedUrls, null, 2));
}
main().catch(console.error);