first commit
This commit is contained in:
@@ -1,36 +1,49 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# Moy Group
|
||||
|
||||
## Getting Started
|
||||
Modern, şık ve çift dilli (TR/EN) kurumsal web sitesi projesi. Moy Group çatısı altında yer alan Moy Beach, Kite Beach Hotel ve Volkswagen Kite (Surfing) mekanlarının tanıtımı, hizmet detayları, galeri ve iletişim/rezervasyon bilgilerini içerir.
|
||||
|
||||
First, run the development server:
|
||||
## Teknolojiler
|
||||
|
||||
- **[Next.js 15+ (App Router)](https://nextjs.org/)**: React tabanlı modern web framework.
|
||||
- **[Tailwind CSS](https://tailwindcss.com/)**: Özelleştirilmiş tasarım sistemi (`tailwind.config.ts` üzerinden yapılandırılmış Moy Group marka renkleri).
|
||||
- **[Framer Motion](https://www.framer.com/motion/)**: Akıcı sayfa geçişleri ve mikro animasyonlar.
|
||||
- **[Lucide Icons](https://lucide.dev/)**: Minimal ve modern ikon seti.
|
||||
- **i18n (Uluslararasılaştırma)**: Klasör bazlı (`app/[lang]`) özel dil desteği mimarisi. Sözlük (dictionary) yaklaşımı ile Türkçe ve İngilizce tam sayfa desteği.
|
||||
|
||||
## Proje Yapısı
|
||||
|
||||
- `app/[lang]/`: Tüm sayfalar, TR ve EN desteği için bu dinamik rota altında yer alır.
|
||||
- `app/components/`: Yeniden kullanılabilir UI bileşenleri (Navbar, Footer, SectionHeader vb.).
|
||||
- `dictionaries/`: Dil dosyalarının bulunduğu klasör (`tr.json`, `en.json` ve `index.ts`).
|
||||
- `public/`: Görseller, videolar ve statik kaynaklar.
|
||||
- `proxy.ts`: Next.js middleware / proxy yönlendirmeleri (Kullanıcının diline göre otomatik sayfa seçimi).
|
||||
|
||||
## Kurulum ve Çalıştırma
|
||||
|
||||
Projeyi lokalinizde çalıştırmak için aşağıdaki adımları izleyin:
|
||||
|
||||
1. **Bağımlılıkları Yükleyin**
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
npm install
|
||||
# veya
|
||||
yarn install
|
||||
# veya
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
2. **Geliştirme Sunucusunu Başlatın**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
3. Tarayıcınızda [http://localhost:3000](http://localhost:3000) adresine gidin.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
## Marka Renkleri
|
||||
- **Moy Gold**: `#c9a96e`
|
||||
- **Moy Dark**: `#1a1f24`
|
||||
- **Moy Light**: `#f8f9fa`
|
||||
- **Moy Blue**: `#2a3b4c`
|
||||
- **Moy Green**: `#2c4c3b`
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
## Geliştirici
|
||||
created by [ayris.tech](https://ayris.tech)
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { Utensils, Umbrella, Home, Wind, Hotel, Star, MapPin, ArrowRight } from "lucide-react";
|
||||
import { Button } from "@/app/components/ui/button";
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Marquee } from "@/app/components/ui/marquee";
|
||||
import { AnimatedNumber } from "@/app/components/ui/animated-number";
|
||||
import { TextReveal } from "@/app/components/ui/text-reveal";
|
||||
import { ShimmerButton } from "@/app/components/ui/shimmer-button";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
|
||||
const HERO_BG =
|
||||
"https://images.unsplash.com/photo-1499793983690-e29da59ef1c2?auto=format&fit=crop&q=80&w=2070";
|
||||
|
||||
const MARQUEE_ITEMS = [
|
||||
"Akyaka",
|
||||
"Akçapınar",
|
||||
"Datça",
|
||||
"Beach Bar",
|
||||
"Kite Surf",
|
||||
"Restaurant",
|
||||
"Bungalov",
|
||||
"Hotel",
|
||||
"Ege Denizi",
|
||||
];
|
||||
|
||||
const STATS = [
|
||||
{ value: 15, suffix: "+", label: "Yıl Deneyim" },
|
||||
{ value: 3, suffix: "", label: "Lokasyon" },
|
||||
{ value: 5000, suffix: "+", label: "Mutlu Misafir" },
|
||||
{ value: 5, suffix: "", label: "Hizmet Kategorisi" },
|
||||
];
|
||||
|
||||
const SERVICES = [
|
||||
{
|
||||
id: "restaurant",
|
||||
title: "Restaurant",
|
||||
icon: Utensils,
|
||||
desc: "Ege'nin taze lezzetleri, yerel malzemeler ve şef imzalı tatlar.",
|
||||
bg: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "beach",
|
||||
title: "Beach Bar",
|
||||
icon: Umbrella,
|
||||
desc: "Altın kumlar, masmavi deniz ve özenle seçilmiş içecekler.",
|
||||
bg: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "bungalow",
|
||||
title: "Bungalov",
|
||||
icon: Home,
|
||||
desc: "Doğa ile iç içe, huzurlu ve modern konaklama deneyimi.",
|
||||
bg: "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "kitesurf",
|
||||
title: "Kite Surf",
|
||||
icon: Wind,
|
||||
desc: "Rüzgarın enerjisini hissedin; başlangıç ve ileri seviye dersler.",
|
||||
bg: "https://images.unsplash.com/photo-1526685514088-290076a0d426?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "hotel",
|
||||
title: "Hotel",
|
||||
icon: Hotel,
|
||||
desc: "Premium konfor ve Ege'nin eşsiz manzarasıyla özel tatil.",
|
||||
bg: "https://images.unsplash.com/photo-1566073771259-6a8506099945?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
const TESTIMONIALS = [
|
||||
{
|
||||
name: "Ayşe Y.",
|
||||
origin: "İstanbul",
|
||||
comment:
|
||||
"Ege'de şimdiye kadar geçirdiğim en güzel tatildi. Hizmet kalitesi muazzamdı, her detay özenle düşünülmüş.",
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
name: "John S.",
|
||||
origin: "London",
|
||||
comment:
|
||||
"The kite surfing instructors were incredibly professional. The location is breathtaking — I'll be back next summer.",
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
name: "Mehmet K.",
|
||||
origin: "Ankara",
|
||||
comment:
|
||||
"Bungalovlar son derece huzurlu. Doğanın içindeyken şehrin konforundan hiç taviz vermiyorsunuz.",
|
||||
rating: 5,
|
||||
},
|
||||
];
|
||||
|
||||
const fadeUp: Variants = {
|
||||
hidden: { opacity: 0, y: 30 },
|
||||
visible: (i = 0) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.65, delay: i * 0.12, ease: [0.16, 1, 0.3, 1] as [number, number, number, number] },
|
||||
}),
|
||||
};
|
||||
|
||||
export default function HomeClient({ lang, dict }: { lang: string; dict: any }) {
|
||||
const [activeMap, setActiveMap] = useState(0);
|
||||
|
||||
const VENUES: { id: string; title: string; tag: string; desc: string; img: string; color: string }[] = dict.venuesList;
|
||||
const LOCATIONS_MAP: { place: string; addr: string; embed: string }[] = dict.locationsMap;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
|
||||
{/* ── Hero ── */}
|
||||
<section className="relative h-screen flex items-center justify-center overflow-hidden moy-grain">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat scale-105"
|
||||
style={{ backgroundImage: `url(${HERO_BG})` }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/30 to-black/60" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
||||
{/* Eyebrow */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-[10px] uppercase tracking-[0.3em] text-[var(--color-moy-gold)] mb-8 font-medium"
|
||||
>
|
||||
{dict.hero.eyebrow}
|
||||
</motion.p>
|
||||
|
||||
{/* Main heading */}
|
||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-serif text-white mb-8 leading-[0.95] tracking-tight">
|
||||
<TextReveal text={dict.hero.title} delay={0.2} />
|
||||
</h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.9 }}
|
||||
className="text-base md:text-lg text-gray-300/90 mb-12 font-light max-w-xl mx-auto leading-relaxed"
|
||||
>
|
||||
{dict.hero.subtitle}
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 1.1 }}
|
||||
className="flex flex-col sm:flex-row gap-4 justify-center"
|
||||
>
|
||||
<Link href={`/${lang}/iletisim`}>
|
||||
<Button variant="primary" size="lg" shimmer className="group">
|
||||
{dict.hero.btn1}
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="outline-light" size="lg" className="group" as="button">
|
||||
<Link href={`/${lang}/#mekanlar`} className="flex items-center gap-2">
|
||||
{dict.hero.btn2}
|
||||
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.6, duration: 0.6 }}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2"
|
||||
>
|
||||
<span className="text-[9px] uppercase tracking-[0.25em] text-white/50">{dict.hero.scroll}</span>
|
||||
<motion.div
|
||||
animate={{ y: [0, 8, 0] }}
|
||||
transition={{ duration: 1.6, repeat: Infinity, ease: "easeInOut" }}
|
||||
className="w-[1px] h-8 bg-gradient-to-b from-white/50 to-transparent"
|
||||
/>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* ── Marquee Strip ── */}
|
||||
<div className="bg-[var(--color-moy-gold)] py-4 overflow-hidden">
|
||||
<Marquee
|
||||
items={MARQUEE_ITEMS}
|
||||
separator="·"
|
||||
speed="normal"
|
||||
itemClassName="text-[var(--color-moy-dark)] font-semibold"
|
||||
separatorClassName="text-[var(--color-moy-dark)] opacity-40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── Stats ── */}
|
||||
<section className="bg-[var(--color-moy-dark)] py-20">
|
||||
<div className="max-w-5xl mx-auto px-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-12 md:gap-6">
|
||||
{STATS.map((s, i) => (
|
||||
<motion.div
|
||||
key={s.label}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={fadeUp}
|
||||
className="text-center"
|
||||
>
|
||||
<p className="text-5xl md:text-6xl font-serif text-[var(--color-moy-gold)] mb-2 leading-none">
|
||||
<AnimatedNumber value={s.value} suffix={s.suffix} duration={1600} />
|
||||
</p>
|
||||
<p className="text-[10px] uppercase tracking-[0.2em] text-gray-500 mt-3">
|
||||
{s.label}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Services (Geçici olarak gizlendi) ── */}
|
||||
{/*
|
||||
<section id="hizmetler" className="py-28 bg-[var(--color-moy-light)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<SectionHeader
|
||||
eyebrow="Ne Sunuyoruz"
|
||||
title="Hizmetlerimiz"
|
||||
subtitle="Her biri kendi hikâyesini taşıyan beş kategori — hepsi Ege'nin özüyle harmanlanmış."
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-5">
|
||||
{SERVICES.map((service, i) => (
|
||||
<motion.div
|
||||
key={service.id}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={fadeUp}
|
||||
whileHover={{ y: -10 }}
|
||||
className="group relative h-80 overflow-hidden cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110"
|
||||
style={{ backgroundImage: `url(${service.bg})` }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[var(--color-moy-dark)] via-[var(--color-moy-dark)]/50 to-transparent" />
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-[var(--color-moy-gold)] scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left" />
|
||||
|
||||
<div className="absolute inset-0 p-6 flex flex-col justify-end items-center text-center">
|
||||
<div className="w-14 h-14 border border-[var(--color-moy-gold)]/60 text-[var(--color-moy-gold)] flex items-center justify-center mb-4 group-hover:bg-[var(--color-moy-gold)] group-hover:text-[var(--color-moy-dark)] group-hover:border-[var(--color-moy-gold)] transition-all duration-300">
|
||||
<service.icon size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-serif text-white mb-2">{service.title}</h3>
|
||||
<p className="text-xs text-gray-300/80 opacity-0 translate-y-3 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300 leading-relaxed">
|
||||
{service.desc}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
|
||||
{/* ── Venues ── */}
|
||||
<section id="mekanlar" className="py-28 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<SectionHeader
|
||||
eyebrow={dict.venues.eyebrow}
|
||||
title={dict.venues.title}
|
||||
subtitle={dict.venues.subtitle}
|
||||
/>
|
||||
|
||||
<div className="space-y-20">
|
||||
{VENUES.map((venue, i) => (
|
||||
<motion.div
|
||||
key={venue.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-60px" }}
|
||||
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={`flex flex-col ${i % 2 === 1 ? "md:flex-row-reverse" : "md:flex-row"} gap-10 items-center`}
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="w-full md:w-[55%] group relative overflow-hidden shadow-[var(--shadow-xl)]">
|
||||
<img
|
||||
src={venue.img}
|
||||
alt={venue.title}
|
||||
className="w-full h-[420px] object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
<div className={`absolute inset-0 bg-gradient-to-t ${venue.color} to-transparent opacity-50`} />
|
||||
<Badge variant="light" className="absolute bottom-6 left-6 shadow-md">
|
||||
{venue.tag}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Text */}
|
||||
<div className="w-full md:w-[45%] px-2 md:px-6">
|
||||
<p className="text-[10px] uppercase tracking-[0.22em] text-[var(--color-moy-gold-dark)] mb-4">
|
||||
{venue.tag}
|
||||
</p>
|
||||
<h3 className="text-3xl md:text-4xl font-serif text-[var(--color-moy-dark)] mb-6 leading-tight">
|
||||
{venue.title}
|
||||
</h3>
|
||||
<span className="moy-gold-line mb-6 block" />
|
||||
<p className="text-gray-500 mb-8 text-base leading-relaxed">{venue.desc}</p>
|
||||
<Link
|
||||
href={`/${lang}/mekanlar/${venue.id}`}
|
||||
className="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.18em] font-semibold text-[var(--color-moy-dark)] border-b border-[var(--color-moy-gold)] pb-1 hover:text-[var(--color-moy-gold-dark)] transition-colors group"
|
||||
>
|
||||
{dict.cta.exploreBtn}
|
||||
<ArrowRight size={12} className="group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Testimonials (Geçici olarak gizlendi) ── */}
|
||||
{/*
|
||||
<section className="py-28 bg-[var(--color-moy-blue)] relative overflow-hidden">
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[600px] rounded-full bg-[var(--color-moy-gold)]/5 blur-3xl pointer-events-none" />
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 relative">
|
||||
<SectionHeader
|
||||
eyebrow="Misafir Deneyimleri"
|
||||
title="Misafirlerimiz Ne Diyor?"
|
||||
light
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{TESTIMONIALS.map((t, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={fadeUp}
|
||||
className="relative bg-white/6 backdrop-blur-sm border border-white/10 p-8 hover:border-[var(--color-moy-gold)]/30 transition-colors duration-300"
|
||||
>
|
||||
<span className="absolute top-6 right-8 text-6xl font-serif text-[var(--color-moy-gold)]/15 leading-none select-none">
|
||||
"
|
||||
</span>
|
||||
|
||||
<div className="flex gap-1 mb-6">
|
||||
{[...Array(t.rating)].map((_, j) => (
|
||||
<Star key={j} size={14} className="fill-[var(--color-moy-gold)] text-[var(--color-moy-gold)]" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-300/85 text-sm leading-relaxed mb-8 italic">
|
||||
"{t.comment}"
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-[1px] bg-[var(--color-moy-gold)]/50" />
|
||||
<div>
|
||||
<p className="font-serif text-white text-base">{t.name}</p>
|
||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mt-0.5">{t.origin}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
|
||||
{/* ── CTA Banner ── */}
|
||||
<section className="relative py-32 overflow-hidden bg-[var(--color-moy-dark)]">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center opacity-20"
|
||||
style={{
|
||||
backgroundImage: `url(https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=60&w=1600)`,
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[var(--color-moy-dark)] via-transparent to-[var(--color-moy-dark)]" />
|
||||
|
||||
<div className="relative max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-6">
|
||||
{dict.cta.eyebrow}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-6xl font-serif text-white mb-6 leading-tight">
|
||||
{dict.cta.title1}<br />
|
||||
<span className="italic text-[var(--color-moy-gold)]">{dict.cta.title2}</span>
|
||||
</h2>
|
||||
<p className="text-gray-400 text-base mb-12 max-w-md mx-auto leading-relaxed">
|
||||
{dict.cta.subtitle}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href={`/${lang}/iletisim`}>
|
||||
<ShimmerButton
|
||||
shimmerColor="rgba(201,169,110,0.6)"
|
||||
background="var(--color-moy-gold)"
|
||||
className="text-[var(--color-moy-dark)] font-bold"
|
||||
>
|
||||
{dict.cta.btn1}
|
||||
</ShimmerButton>
|
||||
</Link>
|
||||
<Link href={`/${lang}/iletisim`}>
|
||||
<Button variant="outline-light" size="lg">
|
||||
{dict.cta.btn2}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Map / Location ── */}
|
||||
<section className="py-28 bg-[var(--color-moy-light)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col lg:flex-row gap-16 items-start">
|
||||
<div className="w-full lg:w-1/3">
|
||||
<SectionHeader
|
||||
eyebrow={dict.venues.eyebrow}
|
||||
title={dict.venues.title}
|
||||
align="left"
|
||||
/>
|
||||
<p className="text-gray-500 mb-10 -mt-8 leading-relaxed">
|
||||
{dict.venues.subtitle}
|
||||
</p>
|
||||
<ul className="space-y-4">
|
||||
{LOCATIONS_MAP.map((loc, idx) => (
|
||||
<li
|
||||
key={loc.place}
|
||||
className={`flex items-start gap-3 p-4 rounded-xl cursor-pointer transition-all duration-300 ${activeMap === idx ? 'bg-white shadow-[var(--shadow-sm)] translate-x-2' : 'hover:bg-white/50'}`}
|
||||
onClick={() => setActiveMap(idx)}
|
||||
>
|
||||
<MapPin className={`${activeMap === idx ? 'text-[var(--color-moy-gold)]' : 'text-gray-400'} mt-0.5 shrink-0 transition-colors`} size={18} />
|
||||
<div>
|
||||
<strong className={`block text-sm font-medium mb-0.5 transition-colors ${activeMap === idx ? 'text-[var(--color-moy-dark)]' : 'text-gray-600'}`}>
|
||||
{loc.place}
|
||||
</strong>
|
||||
<span className="text-xs text-gray-500">{loc.addr}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-2/3 h-[420px] bg-white overflow-hidden shadow-[var(--shadow-lg)] relative rounded-2xl">
|
||||
<iframe
|
||||
src={LOCATIONS_MAP[activeMap].embed}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen={false}
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
className="absolute inset-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X, ZoomIn } from "lucide-react";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
|
||||
export default function GaleriClient({ lang, dict }: { lang: string; dict: any }) {
|
||||
const t = dict.gallery;
|
||||
|
||||
const CATEGORIES = [t.all, t.venues, "Beach", "Bungalov", "Kite Surf", "Hotel"] as const;
|
||||
type Category = string;
|
||||
|
||||
const IMAGES = [
|
||||
{ src: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?auto=format&fit=crop&q=80&w=800", cat: t.venues, title: "Akşam Servisi" },
|
||||
{ src: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=800", cat: "Beach", title: "Moy Beach Alaçatı" },
|
||||
{ src: "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&q=80&w=800", cat: "Bungalov", title: "Doğada Konaklama" },
|
||||
{ src: "https://images.unsplash.com/photo-1526685514088-290076a0d426?auto=format&fit=crop&q=80&w=800", cat: "Kite Surf", title: "Gökova Körfezi" },
|
||||
{ src: "https://images.unsplash.com/photo-1566073771259-6a8506099945?auto=format&fit=crop&q=80&w=800", cat: "Hotel", title: "Kite Beach Hotel" },
|
||||
{ src: "https://images.unsplash.com/photo-1519046904884-53103b34b206?auto=format&fit=crop&q=80&w=800", cat: "Beach", title: "Gün Batımı" },
|
||||
{ src: "https://images.unsplash.com/photo-1571003123894-1f0594d2b5d9?auto=format&fit=crop&q=80&w=800", cat: "Hotel", title: "Oda Manzarası" },
|
||||
{ src: "https://images.unsplash.com/photo-1614214227038-f9d9cf98b1ec?auto=format&fit=crop&q=80&w=800", cat: "Kite Surf", title: "Sabah Seansı" },
|
||||
{ src: "https://images.unsplash.com/photo-1490197415175-074fd86b1fcc?auto=format&fit=crop&q=80&w=800", cat: t.venues, title: "Taze Deniz Ürünleri" },
|
||||
];
|
||||
|
||||
const [active, setActive] = useState<Category>(t.all);
|
||||
const [lightbox, setLightbox] = useState<{src:string, cat:string, title:string} | null>(null);
|
||||
|
||||
const filtered = active === t.all ? IMAGES : IMAGES.filter((img) => img.cat === active);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-moy-dark)]">
|
||||
{/* Hero */}
|
||||
<div className="pt-40 pb-16 text-center px-6">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">{t.eyebrow}</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6">{t.title}</h1>
|
||||
<p className="text-gray-400 text-base max-w-md mx-auto leading-relaxed">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Filter pills */}
|
||||
<div className="flex flex-wrap justify-center gap-3 px-6 mb-14">
|
||||
{CATEGORIES.map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setActive(cat)}
|
||||
className={[
|
||||
"px-5 py-2 text-[10px] uppercase tracking-[0.16em] font-medium transition-all duration-200",
|
||||
active === cat
|
||||
? "bg-[var(--color-moy-gold)] text-[var(--color-moy-dark)]"
|
||||
: "border border-white/15 text-gray-400 hover:border-[var(--color-moy-gold)]/50 hover:text-[var(--color-moy-gold)]",
|
||||
].join(" ")}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 pb-28">
|
||||
<motion.div
|
||||
layout
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{filtered.map((img) => (
|
||||
<motion.div
|
||||
key={img.src}
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.94 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.94 }}
|
||||
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="group relative aspect-square cursor-pointer overflow-hidden"
|
||||
onClick={() => setLightbox(img)}
|
||||
>
|
||||
<img
|
||||
src={img.src}
|
||||
alt={img.title}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[var(--color-moy-dark)]/0 group-hover:bg-[var(--color-moy-dark)]/50 transition-all duration-300 flex flex-col items-center justify-center gap-3">
|
||||
<ZoomIn
|
||||
size={28}
|
||||
className="text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300 translate-y-2 group-hover:translate-y-0"
|
||||
/>
|
||||
<span className="text-white text-sm font-serif opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
{img.title}
|
||||
</span>
|
||||
</div>
|
||||
<Badge
|
||||
variant="dark"
|
||||
className="absolute top-3 left-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
>
|
||||
{img.cat}
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Lightbox */}
|
||||
<AnimatePresence>
|
||||
{lightbox && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[300] bg-black/90 flex items-center justify-center p-6"
|
||||
onClick={() => setLightbox(null)}
|
||||
>
|
||||
<button
|
||||
className="absolute top-6 right-6 text-white/60 hover:text-white transition-colors"
|
||||
onClick={() => setLightbox(null)}
|
||||
>
|
||||
<X size={28} />
|
||||
</button>
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="max-w-4xl w-full"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<img
|
||||
src={lightbox.src}
|
||||
alt={lightbox.title}
|
||||
className="w-full max-h-[80vh] object-contain"
|
||||
/>
|
||||
<p className="text-center text-white font-serif text-xl mt-4">{lightbox.title}</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
import GaleriClient from "./GaleriClient";
|
||||
|
||||
export const metadata = {
|
||||
title: "Galeri | Moy Group",
|
||||
};
|
||||
|
||||
export default async function GaleriPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
|
||||
return <GaleriClient lang={locale} dict={dict} />;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { MapPin, Phone, Mail } from "lucide-react";
|
||||
import { Input, Textarea } from "@/app/components/ui/input";
|
||||
import { Button } from "@/app/components/ui/button";
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
|
||||
const LOCATIONS = [
|
||||
{
|
||||
name: "Moy Beach",
|
||||
tag: "Beach & Bar",
|
||||
address: "Akyaka, Atatürk 1. Cd No:86, 48640 Ula/Muğla",
|
||||
phones: ["+90 534 465 62 35"],
|
||||
email: "beach@moygroup.com.tr",
|
||||
variant: "gold" as const,
|
||||
},
|
||||
{
|
||||
name: "Kite Beach Hotel",
|
||||
tag: "Hotel",
|
||||
address: "Akçapınar Mah. Akçapınar Sok. Kiteboard Sahili, Muğla",
|
||||
phones: ["+90 533 081 61 81"],
|
||||
email: "hotel@moygroup.com.tr",
|
||||
variant: "blue" as const,
|
||||
},
|
||||
{
|
||||
name: "Volkswagen Kite (Surfing)",
|
||||
tag: "Kite Surf",
|
||||
address: "Akçapınar kite sahili, Ula, Turkey 48640",
|
||||
phones: ["+90 537 882 31 37", "+90 505 204 98 56"],
|
||||
email: "kite@moygroup.com.tr",
|
||||
variant: "green" as const,
|
||||
},
|
||||
];
|
||||
|
||||
export default async function IletisimPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
const t = dict.contact;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-moy-light)]">
|
||||
{/* Hero */}
|
||||
<div className="bg-[var(--color-moy-dark)] pt-40 pb-20">
|
||||
<div className="max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">
|
||||
{t.eyebrow}
|
||||
</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6 leading-tight">
|
||||
{t.title}
|
||||
</h1>
|
||||
<p className="text-gray-400 text-base leading-relaxed max-w-lg mx-auto">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Locations */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-10 pb-24">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mb-24">
|
||||
{LOCATIONS.map((loc) => (
|
||||
<div
|
||||
key={loc.name}
|
||||
className="bg-white border border-gray-100 p-8 shadow-[var(--shadow-sm)] hover:shadow-[var(--shadow-md)] transition-shadow duration-300"
|
||||
>
|
||||
<Badge variant={loc.variant} className="mb-5">{loc.tag}</Badge>
|
||||
<h3 className="text-xl font-serif text-[var(--color-moy-dark)] mb-5">{loc.name}</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={15} className="text-[var(--color-moy-gold)] mt-0.5 shrink-0" />
|
||||
<span className="text-sm text-gray-500">{loc.address}</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Phone size={15} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col gap-1">
|
||||
{loc.phones.map((phone, i) => (
|
||||
<a key={i} href={`tel:${phone.replace(/\s/g, "")}`} className="text-sm text-gray-500 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
{phone}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Mail size={15} className="text-[var(--color-moy-gold)] shrink-0" />
|
||||
<a href={`mailto:${loc.email}`} className="text-sm text-gray-500 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
{loc.email}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contact Form + Info */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-12">
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-3 bg-white border border-gray-100 p-10 shadow-[var(--shadow-sm)]">
|
||||
<SectionHeader
|
||||
eyebrow={t.formEyebrow}
|
||||
title={t.formTitle}
|
||||
align="left"
|
||||
className="mb-10"
|
||||
/>
|
||||
<form className="space-y-5">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<Input label={t.name} placeholder={t.namePlaceholder} id="name" />
|
||||
<Input label={t.email} type="email" placeholder={t.emailPlaceholder} id="email" />
|
||||
</div>
|
||||
<Input label={t.phone} type="tel" placeholder={t.phonePlaceholder} id="phone" />
|
||||
<div>
|
||||
<label className="text-[10px] font-medium uppercase tracking-[0.16em] text-gray-500 block mb-1.5">
|
||||
{t.subject}
|
||||
</label>
|
||||
<select className="w-full px-4 py-3.5 bg-white border border-gray-200 text-sm text-[var(--color-moy-dark)] focus:outline-none focus:ring-2 focus:ring-[var(--color-moy-gold)]/40 focus:border-[var(--color-moy-gold)] transition-all duration-200">
|
||||
{t.subjectOptions.map((opt: string, i: number) => (
|
||||
<option key={i} value={i === 0 ? "" : opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<Textarea label={t.message} placeholder={t.messagePlaceholder} id="message" rows={5} />
|
||||
<Button variant="primary" size="lg" shimmer className="w-full group mt-2">
|
||||
{t.send}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Sidebar info */}
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<div className="bg-[var(--color-moy-dark)] p-8">
|
||||
<h3 className="font-serif text-xl text-white mb-6">{t.generalContact}</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-center gap-3">
|
||||
<Phone size={16} className="text-[var(--color-moy-gold)]" />
|
||||
<div>
|
||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mb-0.5">{t.phoneText}</p>
|
||||
<a href="tel:+905344656235" className="text-sm text-gray-300 hover:text-[var(--color-moy-gold)] transition-colors">+90 534 465 62 35</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Mail size={16} className="text-[var(--color-moy-gold)]" />
|
||||
<div>
|
||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mb-0.5">{t.emailText}</p>
|
||||
<a href="mailto:info@moygroup.com.tr" className="text-sm text-gray-300 hover:text-[var(--color-moy-gold)] transition-colors">info@moygroup.com.tr</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 p-8">
|
||||
<h3 className="font-serif text-xl text-[var(--color-moy-dark)] mb-6">{t.hours}</h3>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{[
|
||||
[t.monFri, "09:00 – 23:00"],
|
||||
[t.sat, "10:00 – 24:00"],
|
||||
[t.sun, "10:00 – 23:00"],
|
||||
].map(([day, hours]) => (
|
||||
<li key={day as string} className="flex justify-between text-gray-500">
|
||||
<span>{day as string}</span>
|
||||
<span className="font-medium text-[var(--color-moy-dark)]">{hours as string}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-xs text-[var(--color-moy-gold-dark)] mt-4">{t.hoursNote}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 p-8">
|
||||
<h3 className="font-serif text-xl text-[var(--color-moy-dark)] mb-4">{t.social}</h3>
|
||||
<div className="flex gap-4">
|
||||
<a href="#" aria-label="Instagram" className="w-10 h-10 border border-gray-200 flex items-center justify-center hover:border-[var(--color-moy-gold)] hover:text-[var(--color-moy-gold)] text-gray-400 transition-all duration-200">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="20" x="2" y="2" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/></svg>
|
||||
</a>
|
||||
<a href="#" aria-label="Facebook" className="w-10 h-10 border border-gray-200 flex items-center justify-center hover:border-[var(--color-moy-gold)] hover:text-[var(--color-moy-gold)] text-gray-400 transition-all duration-200">
|
||||
<svg width="18" height="18" 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"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
|
||||
export const metadata = {
|
||||
title: "Kurumsal | Moy Group",
|
||||
};
|
||||
|
||||
export default async function KurumsalPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
const t = dict.corporate;
|
||||
|
||||
return (
|
||||
<div className="pt-32 pb-24 min-h-screen bg-[var(--color-moy-light)]">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{/* HAKKIMIZDA */}
|
||||
<section className="mb-20">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-[var(--color-moy-dark)] mb-6 text-center">{t.title}</h1>
|
||||
<div className="w-24 h-1 bg-[var(--color-moy-gold)] mx-auto mb-10"></div>
|
||||
<div className="bg-white p-8 md:p-12 rounded-2xl shadow-sm border border-gray-100 text-gray-700 leading-relaxed space-y-6">
|
||||
<p>{t.p1}</p>
|
||||
<p>{t.p2}</p>
|
||||
<p>{t.p3}</p>
|
||||
<p>{t.p4}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* VİZYONUMUZ & MİSYONUMUZ */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||
|
||||
<section>
|
||||
<div className="bg-[var(--color-moy-dark)] text-white p-8 md:p-10 rounded-2xl shadow-lg h-full border border-gray-800">
|
||||
<h2 className="text-2xl font-serif text-[var(--color-moy-gold)] mb-6 uppercase tracking-wider">{t.vision}</h2>
|
||||
<p className="text-gray-300 leading-relaxed font-light">
|
||||
{t.visionText}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div className="bg-white p-8 md:p-10 rounded-2xl shadow-sm h-full border border-gray-200">
|
||||
<h2 className="text-2xl font-serif text-[var(--color-moy-gold-dark)] mb-6 uppercase tracking-wider">{t.mission}</h2>
|
||||
<p className="text-gray-600 leading-relaxed">
|
||||
{t.missionText}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Cormorant_Garamond, Montserrat } from "next/font/google";
|
||||
import Navbar from "../components/Navbar";
|
||||
import Footer from "../components/Footer";
|
||||
import "../globals.css";
|
||||
import { Locale, getDictionary } from "../dictionaries";
|
||||
|
||||
const cormorant = Cormorant_Garamond({
|
||||
variable: "--font-cormorant",
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
const montserrat = Montserrat({
|
||||
variable: "--font-montserrat",
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Moy Group | Ege'nin Ruhunu Hisset",
|
||||
description: "Akyaka, Akçapınar ve Datça'da restoran, beach, bungalov ve kitesurf hizmetleriyle doğa ile iç içe premium tatil deneyimi.",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ lang: string }>;
|
||||
}>) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
className={`${cormorant.variable} ${montserrat.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">
|
||||
<Navbar lang={locale} dict={dict.navigation} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer lang={locale} dict={dict.footer} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
|
||||
export const metadata = {
|
||||
title: "Dijital Menü | Moy Group",
|
||||
};
|
||||
|
||||
export default async function MenuPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
const t = dict.menu;
|
||||
|
||||
const MENU_SECTIONS = [
|
||||
{
|
||||
title: t.starters,
|
||||
badge: "Starters",
|
||||
variant: "gold" as const,
|
||||
items: [
|
||||
{ name: "Zeytinyağlı Enginar", desc: "Taze dereotu ve limon", price: "₺ 185" },
|
||||
{ name: "Ahtapot Izgara", desc: "Kapari salatası, fesleğen yağı", price: "₺ 340" },
|
||||
{ name: "Kabak Çiçeği Dolması", desc: "Lor peyniri, nane", price: "₺ 210" },
|
||||
{ name: "Ezme Tabağı", desc: "Ev yapımı ekmek ile", price: "₺ 145" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.mains,
|
||||
badge: "Mains",
|
||||
variant: "blue" as const,
|
||||
items: [
|
||||
{ name: "Levrek Izgara", desc: "Mevsim yeşillikleri, limon yağı", price: "₺ 680" },
|
||||
{ name: "Çipura Fırın", desc: "Ege otları, domates, zeytinyağı", price: "₺ 620" },
|
||||
{ name: "Kuzu Pirzola", desc: "Kekik soslu, ızgara sebze", price: "₺ 750" },
|
||||
{ name: "Mantar Risotto", desc: "Porcini, parmesan, taze kekik", price: "₺ 390" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.desserts,
|
||||
badge: "Desserts",
|
||||
variant: "green" as const,
|
||||
items: [
|
||||
{ name: "Incir Tatlısı", desc: "Kaymak, ceviz, pekmez", price: "₺ 175" },
|
||||
{ name: "Dondurma Tabağı", desc: "Yerel bademli, mevsim meyve", price: "₺ 145" },
|
||||
{ name: "Kemalpaşa", desc: "Sıcak servis, dövme ceviz", price: "₺ 120" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.drinks,
|
||||
badge: "Drinks",
|
||||
variant: "dark" as const,
|
||||
items: [
|
||||
{ name: "Datça Badem Şerbeti", desc: "Ev yapımı, taze", price: "₺ 95" },
|
||||
{ name: "Limonata", desc: "Taze sıkım, nane", price: "₺ 85" },
|
||||
{ name: "Lokal Şarap", desc: "Günlük seçki", price: "₺ 195 / bardak" },
|
||||
{ name: "Soda & Maden Suyu", desc: "", price: "₺ 45" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-moy-light)]">
|
||||
{/* Hero */}
|
||||
<div className="bg-[var(--color-moy-dark)] pt-40 pb-20">
|
||||
<div className="max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">{t.eyebrow}</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6">{t.title}</h1>
|
||||
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 py-24 space-y-16">
|
||||
<SectionHeader
|
||||
eyebrow="Moy Restaurant"
|
||||
title={t.sectionTitle}
|
||||
subtitle={t.sectionSubtitle}
|
||||
/>
|
||||
|
||||
{MENU_SECTIONS.map((section) => (
|
||||
<div key={section.title}>
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<h2 className="text-2xl font-serif text-[var(--color-moy-dark)]">{section.title}</h2>
|
||||
<Badge variant={section.variant}>{section.badge}</Badge>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-gray-200/60">
|
||||
{section.items.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="flex items-baseline justify-between py-5 group hover:px-3 transition-all duration-200"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-serif text-lg text-[var(--color-moy-dark)] group-hover:text-[var(--color-moy-gold-dark)] transition-colors">
|
||||
{item.name}
|
||||
</h3>
|
||||
{item.desc && (
|
||||
<p className="text-xs text-gray-400 mt-1 tracking-wide">{item.desc}</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-sm font-medium text-[var(--color-moy-dark)] ml-8 shrink-0">
|
||||
{item.price}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<p className="text-xs text-center text-gray-400 border-t border-gray-200 pt-8">
|
||||
{t.note}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getDictionary, Locale } from "../dictionaries";
|
||||
import HomeClient from "./HomeClient";
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: Locale }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
|
||||
return <HomeClient lang={lang} dict={dict.home} />;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Plus, Minus } from "lucide-react";
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
|
||||
function FaqItem({ q, a }: { q: string; a: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className="border-b border-gray-100 last:border-0">
|
||||
<button
|
||||
className="w-full flex items-center justify-between py-5 text-left group"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
>
|
||||
<span className="font-serif text-lg text-[var(--color-moy-dark)] group-hover:text-[var(--color-moy-gold-dark)] transition-colors pr-4">
|
||||
{q}
|
||||
</span>
|
||||
<span className="shrink-0 w-7 h-7 border border-gray-200 group-hover:border-[var(--color-moy-gold)] flex items-center justify-center transition-colors">
|
||||
{open
|
||||
? <Minus size={14} className="text-[var(--color-moy-gold)]" />
|
||||
: <Plus size={14} className="text-gray-400 group-hover:text-[var(--color-moy-gold)] transition-colors" />
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<p className="text-gray-500 text-sm leading-relaxed pb-5">{a}</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SSSClient({ lang, dict }: { lang: string; dict: any }) {
|
||||
const t = dict.faq;
|
||||
|
||||
const FAQS = [
|
||||
{
|
||||
category: t.categories.accommodation,
|
||||
variant: "blue" as const,
|
||||
items: [
|
||||
{ q: t.q1, a: t.a1 },
|
||||
{ q: t.q2, a: t.a2 },
|
||||
{ q: t.q3, a: t.a3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t.categories.kitesurf,
|
||||
variant: "green" as const,
|
||||
items: [
|
||||
{ q: t.q4, a: t.a4 },
|
||||
{ q: t.q5, a: t.a5 },
|
||||
{ q: t.q6, a: t.a6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t.categories.reservation,
|
||||
variant: "gold" as const,
|
||||
items: [
|
||||
{ q: t.q7, a: t.a7 },
|
||||
{ q: t.q8, a: t.a8 },
|
||||
{ q: t.q9, a: t.a9 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero */}
|
||||
<div className="bg-[var(--color-moy-dark)] pt-40 pb-20">
|
||||
<div className="max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">{t.eyebrow}</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6">{t.title}</h1>
|
||||
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-24 space-y-14">
|
||||
{FAQS.map((group) => (
|
||||
<div key={group.category}>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Badge variant={group.variant}>{group.category}</Badge>
|
||||
</div>
|
||||
<div className="bg-[var(--color-moy-light)] px-6">
|
||||
{group.items.map((item) => (
|
||||
<FaqItem key={item.q} q={item.q} a={item.a} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
import SSSClient from "./SSSClient";
|
||||
|
||||
export const metadata = {
|
||||
title: "SSS | Moy Group",
|
||||
};
|
||||
|
||||
export default async function SSSPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
|
||||
return <SSSClient lang={locale} dict={dict} />;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import Link from "next/link";
|
||||
import { MapPin, Phone, Mail } from "lucide-react";
|
||||
import { Locale } from "../dictionaries";
|
||||
|
||||
export default function Footer({ lang, dict }: { lang: Locale; dict: any }) {
|
||||
return (
|
||||
<footer className="bg-[var(--color-moy-dark)] text-white pt-16 pb-8 border-t border-gray-800">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-12">
|
||||
{/* Brand */}
|
||||
<div className="space-y-4">
|
||||
<img src="/logo.png" alt="Moy Group Logo" className="h-12 w-auto mb-6" />
|
||||
<p className="text-gray-400 text-sm leading-relaxed">
|
||||
{dict.description}
|
||||
</p>
|
||||
<div className="flex gap-4 pt-4">
|
||||
<a href="#" className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="20" x="2" y="2" rx="5" ry="5" /><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" /><line x1="17.5" x2="17.51" y1="6.5" y2="6.5" /></svg>
|
||||
</a>
|
||||
<a href="#" className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
<svg width="20" height="20" 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" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h3 className="text-lg font-serif mb-6 text-white uppercase tracking-wider">{dict.quickLinks}</h3>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<Link href={`/${lang}/kurumsal`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">{dict.about}</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`/${lang}/galeri`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Galeri</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link href={`/${lang}/iletisim`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">{dict.contactInfo}</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Locations */}
|
||||
<div>
|
||||
<h3 className="text-lg font-serif mb-6 text-white uppercase tracking-wider">{dict.ourVenues}</h3>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<Link href={`/${lang}/#mekanlar`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Moy Beach</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`/${lang}/#mekanlar`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Kite Beach Hotel</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`/${lang}/#mekanlar`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Volkswagen Kite</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div>
|
||||
<h3 className="text-lg font-serif mb-6 text-white uppercase tracking-wider">{dict.contactInfo}</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={18} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white text-sm mb-1">Moy Beach</span>
|
||||
<span className="text-gray-400 text-xs">Atatürk 1. Cd No:86, Akyaka, Ula/Muğla</span>
|
||||
<a href="tel:+905344656235" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs mt-1">+90 534 465 62 35</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={18} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white text-sm mb-1">Kite Beach Hotel</span>
|
||||
<span className="text-gray-400 text-xs">Akçapınar Sok. Kiteboard Sahili, Muğla</span>
|
||||
<a href="tel:+905330816181" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs mt-1">+90 533 081 61 81</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={18} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white text-sm mb-1">Volkswagen Kite</span>
|
||||
<span className="text-gray-400 text-xs">Akçapınar Kite Sahili, Ula/Muğla</span>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<a href="tel:+905378823137" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs">+90 537 882 31 37</a>
|
||||
<a href="tel:+905052049856" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs">+90 505 204 98 56</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom */}
|
||||
<div className="pt-8 border-t border-gray-800 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p className="text-gray-500 text-xs">
|
||||
© {new Date().getFullYear()} Moy Group. {dict.rights}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a href="https://ayris.tech" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-[var(--color-moy-gold)] transition-colors text-xs flex items-center gap-1">
|
||||
created by <span className="font-medium">ayris.tech</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, X, Globe } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Locale } from "../dictionaries";
|
||||
|
||||
export default function Navbar({ lang, dict }: { lang: Locale; dict: any }) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const isHome = pathname === `/${lang}` || pathname === `/${lang}/`;
|
||||
|
||||
const navLinks = [
|
||||
{ name: dict.home, href: `/${lang}` },
|
||||
{ name: dict.corporate, href: `/${lang}/kurumsal` },
|
||||
{ name: dict.venues, href: `/${lang}/#mekanlar` },
|
||||
{ name: dict.gallery, href: `/${lang}/galeri` },
|
||||
{ name: dict.contact, href: `/${lang}/iletisim` },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 50);
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const switchLanguage = (newLang: string) => {
|
||||
if (!pathname) return '/';
|
||||
// Replace the current lang code in the URL with the new one
|
||||
const segments = pathname.split('/');
|
||||
if (segments[1] === 'tr' || segments[1] === 'en') {
|
||||
segments[1] = newLang;
|
||||
} else {
|
||||
segments.splice(1, 0, newLang);
|
||||
}
|
||||
return segments.join('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
|
||||
isScrolled || !isHome
|
||||
? "bg-[var(--color-moy-dark)]/90 backdrop-blur-md py-4 shadow-lg"
|
||||
: "bg-transparent py-6"
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center">
|
||||
{/* Logo */}
|
||||
<Link href={`/${lang}`} className="flex-shrink-0">
|
||||
<img src="/logo.png" alt="Moy Group Logo" className="h-10 w-auto" />
|
||||
</Link>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="text-white hover:text-[var(--color-moy-gold)] transition-colors text-sm uppercase tracking-wider font-medium"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{/* Language Switcher */}
|
||||
<div className="flex items-center gap-2 ml-4 border-l border-gray-600 pl-4">
|
||||
<Globe size={16} className="text-[var(--color-moy-gold)]" />
|
||||
<Link href={switchLanguage('tr')} className={`text-sm font-medium ${lang === 'tr' ? 'text-white' : 'text-gray-400 hover:text-white'}`}>TR</Link>
|
||||
<span className="text-gray-600">|</span>
|
||||
<Link href={switchLanguage('en')} className={`text-sm font-medium ${lang === 'en' ? 'text-white' : 'text-gray-400 hover:text-white'}`}>EN</Link>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="md:hidden text-white"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
{mobileMenuOpen ? <X size={28} /> : <Menu size={28} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Nav */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="absolute top-full left-0 w-full bg-[var(--color-moy-dark)] border-t border-gray-800 shadow-xl md:hidden"
|
||||
>
|
||||
<div className="px-4 pt-2 pb-6 space-y-1">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="block px-3 py-4 text-white border-b border-gray-800 hover:text-[var(--color-moy-gold)] uppercase tracking-wider text-sm"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
<div className="flex items-center gap-4 px-3 py-4 text-white">
|
||||
<Globe size={18} className="text-[var(--color-moy-gold)]" />
|
||||
<Link href={switchLanguage('tr')} onClick={() => setMobileMenuOpen(false)} className={`text-sm ${lang === 'tr' ? 'text-[var(--color-moy-gold)] font-bold' : ''}`}>Türkçe</Link>
|
||||
<Link href={switchLanguage('en')} onClick={() => setMobileMenuOpen(false)} className={`text-sm ${lang === 'en' ? 'text-[var(--color-moy-gold)] font-bold' : ''}`}>English</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useInView } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AnimatedNumberProps {
|
||||
value: number;
|
||||
suffix?: string;
|
||||
prefix?: string;
|
||||
duration?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AnimatedNumber({
|
||||
value,
|
||||
suffix = "",
|
||||
prefix = "",
|
||||
duration = 1800,
|
||||
className,
|
||||
}: AnimatedNumberProps) {
|
||||
const [count, setCount] = useState(0);
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-80px" });
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInView) return;
|
||||
|
||||
let rafId: number;
|
||||
let startTime: number | null = null;
|
||||
|
||||
const step = (ts: number) => {
|
||||
if (!startTime) startTime = ts;
|
||||
const progress = Math.min((ts - startTime) / duration, 1);
|
||||
// Ease-out cubic
|
||||
const eased = 1 - Math.pow(1 - progress, 3);
|
||||
setCount(Math.round(eased * value));
|
||||
if (progress < 1) rafId = requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
rafId = requestAnimationFrame(step);
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
}, [isInView, value, duration]);
|
||||
|
||||
return (
|
||||
<span ref={ref} className={cn(className)}>
|
||||
{prefix}{count}{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Variant = "gold" | "blue" | "green" | "dark" | "light";
|
||||
|
||||
interface BadgeProps {
|
||||
variant?: Variant;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const variants: Record<Variant, string> = {
|
||||
gold: "bg-[var(--color-moy-gold-faint)] text-[var(--color-moy-gold-dark)] border border-[var(--color-moy-gold-subtle)]",
|
||||
blue: "bg-[var(--color-moy-blue)]/10 text-[var(--color-moy-blue)] border border-[var(--color-moy-blue)]/20",
|
||||
green: "bg-[var(--color-moy-green)]/10 text-[var(--color-moy-green)] border border-[var(--color-moy-green)]/20",
|
||||
dark: "bg-[var(--color-moy-dark)] text-white border border-[var(--color-moy-dark-border)]",
|
||||
light: "bg-white text-[var(--color-moy-dark)] border border-gray-200",
|
||||
};
|
||||
|
||||
export function Badge({ variant = "gold", children, className }: BadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center px-3 py-1 text-[10px] font-medium uppercase tracking-[0.18em] rounded-full",
|
||||
variants[variant],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { type ButtonHTMLAttributes, forwardRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Variant = "primary" | "secondary" | "ghost" | "outline-light";
|
||||
type Size = "sm" | "md" | "lg";
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
shimmer?: boolean;
|
||||
as?: "button";
|
||||
}
|
||||
|
||||
const variants: Record<Variant, string> = {
|
||||
primary:
|
||||
"bg-[var(--color-moy-gold)] text-[var(--color-moy-dark)] hover:bg-[var(--color-moy-gold-light)] font-bold shadow-[var(--shadow-gold)] hover:shadow-[var(--shadow-gold-lg)]",
|
||||
secondary:
|
||||
"border border-[var(--color-moy-gold)] text-[var(--color-moy-gold)] hover:bg-[var(--color-moy-gold)] hover:text-[var(--color-moy-dark)] font-medium",
|
||||
ghost:
|
||||
"text-[var(--color-moy-gold)] hover:bg-[var(--color-moy-gold-faint)] font-medium",
|
||||
"outline-light":
|
||||
"border border-white/50 text-white hover:border-white hover:bg-white/10 font-medium",
|
||||
};
|
||||
|
||||
const sizes: Record<Size, string> = {
|
||||
sm: "px-5 py-2 text-[11px] tracking-[0.15em]",
|
||||
md: "px-8 py-3.5 text-[11px] tracking-[0.18em]",
|
||||
lg: "px-10 py-4 text-[11px] tracking-[0.2em]",
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = "primary", size = "md", shimmer = false, children, ...props }, ref) => (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center uppercase overflow-hidden",
|
||||
"transition-all duration-300",
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{shimmer && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 moy-shimmer opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
/>
|
||||
)}
|
||||
<span className="relative">{children}</span>
|
||||
</button>
|
||||
)
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
@@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/* ── Base Card ── */
|
||||
interface CardProps {
|
||||
children: React.ReactNode;
|
||||
hover?: boolean;
|
||||
glass?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Card({ children, hover = true, glass = false, className }: CardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={hover ? { y: -6 } : undefined}
|
||||
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden transition-shadow duration-300",
|
||||
glass
|
||||
? "bg-white/10 backdrop-blur-md border border-white/20"
|
||||
: "bg-white border border-gray-100 shadow-[var(--shadow-sm)] hover:shadow-[var(--shadow-lg)]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Image Card ── */
|
||||
interface ImageCardProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
badge?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ImageCard({ src, alt, title, subtitle, badge, className }: ImageCardProps) {
|
||||
return (
|
||||
<Card className={cn("group cursor-pointer", className)}>
|
||||
<div className="relative h-64 overflow-hidden">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-108"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-400" />
|
||||
{badge && (
|
||||
<span className="absolute top-4 left-4 text-[10px] uppercase tracking-[0.18em] font-semibold px-3 py-1 bg-[var(--color-moy-gold)] text-[var(--color-moy-dark)] rounded-full">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6 border-t-[1.5px] border-transparent group-hover:border-[var(--color-moy-gold)] transition-[border-color] duration-300">
|
||||
<h3 className="text-xl font-serif text-[var(--color-moy-dark)] mb-2 leading-snug">{title}</h3>
|
||||
{subtitle && (
|
||||
<p className="text-sm text-gray-500 leading-relaxed">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export { Button } from "./button";
|
||||
export { Badge } from "./badge";
|
||||
export { SectionHeader } from "./section-header";
|
||||
export { Card, ImageCard } from "./card";
|
||||
export { Input, Textarea } from "./input";
|
||||
export { AnimatedNumber } from "./animated-number";
|
||||
export { ShimmerButton } from "./shimmer-button";
|
||||
export { Marquee } from "./marquee";
|
||||
export { TextReveal } from "./text-reveal";
|
||||
@@ -0,0 +1,68 @@
|
||||
import { forwardRef, type InputHTMLAttributes, type TextareaHTMLAttributes } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/* ── Input ── */
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, label, error, id, ...props }, ref) => (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{label && (
|
||||
<label htmlFor={id} className="text-[10px] font-medium uppercase tracking-[0.16em] text-gray-500">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={cn(
|
||||
"w-full px-4 py-3.5 bg-white border border-gray-200 text-[var(--color-moy-dark)] text-sm",
|
||||
"focus:outline-none focus:ring-2 focus:ring-[var(--color-moy-gold)]/40 focus:border-[var(--color-moy-gold)]",
|
||||
"transition-all duration-200 placeholder:text-gray-300",
|
||||
"rounded-none", // sharp corners = editorial feel
|
||||
error && "border-red-300 focus:ring-red-200/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span className="text-xs text-red-500">{error}</span>}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
/* ── Textarea ── */
|
||||
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, label, error, id, ...props }, ref) => (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{label && (
|
||||
<label htmlFor={id} className="text-[10px] font-medium uppercase tracking-[0.16em] text-gray-500">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<textarea
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={cn(
|
||||
"w-full px-4 py-3.5 bg-white border border-gray-200 text-[var(--color-moy-dark)] text-sm resize-none",
|
||||
"focus:outline-none focus:ring-2 focus:ring-[var(--color-moy-gold)]/40 focus:border-[var(--color-moy-gold)]",
|
||||
"transition-all duration-200 placeholder:text-gray-300",
|
||||
"rounded-none",
|
||||
error && "border-red-300 focus:ring-red-200/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span className="text-xs text-red-500">{error}</span>}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
Textarea.displayName = "Textarea";
|
||||
@@ -0,0 +1,49 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface MarqueeProps {
|
||||
items: string[];
|
||||
separator?: string;
|
||||
speed?: "slow" | "normal" | "fast";
|
||||
reverse?: boolean;
|
||||
className?: string;
|
||||
itemClassName?: string;
|
||||
separatorClassName?: string;
|
||||
}
|
||||
|
||||
const speeds = { slow: "42s", normal: "28s", fast: "16s" };
|
||||
|
||||
export function Marquee({
|
||||
items,
|
||||
separator = "✦",
|
||||
speed = "normal",
|
||||
reverse = false,
|
||||
className,
|
||||
itemClassName,
|
||||
separatorClassName,
|
||||
}: MarqueeProps) {
|
||||
// Double for seamless infinite loop
|
||||
const doubled = [...items, ...items];
|
||||
|
||||
return (
|
||||
<div className={cn("overflow-hidden whitespace-nowrap select-none", className)}>
|
||||
<div
|
||||
className="inline-flex"
|
||||
style={{
|
||||
animation: `marquee ${speeds[speed]} linear infinite${reverse ? " reverse" : ""}`,
|
||||
willChange: "transform",
|
||||
}}
|
||||
>
|
||||
{doubled.map((item, i) => (
|
||||
<span key={i} className="inline-flex items-center">
|
||||
<span className={cn("px-7 text-[11px] font-medium uppercase tracking-[0.18em]", itemClassName)}>
|
||||
{item}
|
||||
</span>
|
||||
<span className={cn("opacity-50 text-sm", separatorClassName)}>
|
||||
{separator}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
eyebrow?: string;
|
||||
align?: "left" | "center" | "right";
|
||||
light?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SectionHeader({
|
||||
title,
|
||||
subtitle,
|
||||
eyebrow,
|
||||
align = "center",
|
||||
light = false,
|
||||
className,
|
||||
}: SectionHeaderProps) {
|
||||
const alignCls = {
|
||||
left: "items-start text-left",
|
||||
center: "items-center text-center",
|
||||
right: "items-end text-right",
|
||||
}[align];
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col mb-16", alignCls, className)}>
|
||||
{eyebrow && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-[10px] font-medium uppercase tracking-[0.22em] mb-5",
|
||||
light ? "text-[var(--color-moy-gold-light)]" : "text-[var(--color-moy-gold-dark)]"
|
||||
)}
|
||||
>
|
||||
{eyebrow}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<h2
|
||||
className={cn(
|
||||
"text-4xl md:text-[3.25rem] font-serif leading-tight mb-5",
|
||||
light ? "text-white" : "text-[var(--color-moy-dark)]"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
"moy-gold-line",
|
||||
align === "center" && "mx-auto",
|
||||
align === "right" && "ml-auto"
|
||||
)}
|
||||
/>
|
||||
|
||||
{subtitle && (
|
||||
<p
|
||||
className={cn(
|
||||
"mt-6 text-base md:text-lg leading-relaxed max-w-2xl",
|
||||
align === "center" && "mx-auto",
|
||||
light ? "text-gray-300/80" : "text-gray-500"
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ShimmerButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
shimmerColor?: string;
|
||||
background?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ShimmerButton = forwardRef<HTMLButtonElement, ShimmerButtonProps>(
|
||||
(
|
||||
{
|
||||
shimmerColor = "rgba(201,169,110,0.55)",
|
||||
background = "var(--color-moy-dark)",
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group relative inline-flex items-center justify-center overflow-hidden",
|
||||
"px-10 py-4 text-[11px] font-medium uppercase tracking-[0.2em] text-white",
|
||||
"transition-all duration-300",
|
||||
className
|
||||
)}
|
||||
style={{ background }}
|
||||
{...props}
|
||||
>
|
||||
{/* Animated shimmer strip */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background: `linear-gradient(105deg, transparent 35%, ${shimmerColor} 50%, transparent 65%)`,
|
||||
backgroundSize: "200% 100%",
|
||||
animation: "shimmer 2.8s linear infinite",
|
||||
}}
|
||||
/>
|
||||
{/* Inset bg to sit above shimmer */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="absolute inset-[1px]"
|
||||
style={{ background }}
|
||||
/>
|
||||
<span className="relative">{children}</span>
|
||||
</button>
|
||||
)
|
||||
);
|
||||
ShimmerButton.displayName = "ShimmerButton";
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface TextRevealProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
delay?: number;
|
||||
once?: boolean;
|
||||
stagger?: number;
|
||||
}
|
||||
|
||||
export function TextReveal({
|
||||
text,
|
||||
className,
|
||||
delay = 0,
|
||||
once = true,
|
||||
stagger = 0.055,
|
||||
}: TextRevealProps) {
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const isInView = useInView(ref, { once, margin: "-60px" });
|
||||
const words = text.split(" ");
|
||||
|
||||
return (
|
||||
<span ref={ref} className={cn("inline", className)}>
|
||||
{words.map((word, i) => (
|
||||
<motion.span
|
||||
key={i}
|
||||
className="inline-block mr-[0.28em] last:mr-0"
|
||||
initial={{ opacity: 0, y: 22, filter: "blur(4px)" }}
|
||||
animate={
|
||||
isInView
|
||||
? { opacity: 1, y: 0, filter: "blur(0px)" }
|
||||
: { opacity: 0, y: 22, filter: "blur(4px)" }
|
||||
}
|
||||
transition={{
|
||||
duration: 0.55,
|
||||
delay: delay + i * stagger,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
{word}
|
||||
</motion.span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'server-only';
|
||||
|
||||
const dictionaries = {
|
||||
en: () => import('../dictionaries/en.json').then((module) => module.default),
|
||||
tr: () => import('../dictionaries/tr.json').then((module) => module.default),
|
||||
};
|
||||
|
||||
export type Locale = keyof typeof dictionaries;
|
||||
export const i18n = {
|
||||
defaultLocale: 'tr',
|
||||
locales: ['tr', 'en'],
|
||||
} as const;
|
||||
|
||||
export const getDictionary = async (locale: Locale) => {
|
||||
return dictionaries[locale]?.() ?? dictionaries.tr();
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
+162
-12
@@ -1,26 +1,176 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
@theme {
|
||||
/* ── Core Palette ── */
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
|
||||
--color-moy-gold: #c9a96e;
|
||||
--color-moy-gold-light: #d6bc8b;
|
||||
--color-moy-gold-dark: #b39152;
|
||||
--color-moy-gold-faint: rgba(201, 169, 110, 0.12);
|
||||
--color-moy-gold-subtle: rgba(201, 169, 110, 0.25);
|
||||
|
||||
--color-moy-blue: #1c3552;
|
||||
--color-moy-blue-light: #2c4d75;
|
||||
--color-moy-blue-dark: #0f1f30;
|
||||
|
||||
--color-moy-green: #3a5342;
|
||||
--color-moy-green-light: #4a6854;
|
||||
|
||||
--color-moy-dark: #121212;
|
||||
--color-moy-dark-lighter: #1e1e1e;
|
||||
--color-moy-dark-border: #2c2c2c;
|
||||
|
||||
--color-moy-light: #f9f6f0;
|
||||
--color-moy-light-warm: #f0ead8;
|
||||
--color-moy-cream: #ede8de;
|
||||
|
||||
/* ── Typography ── */
|
||||
--font-serif: var(--font-cormorant);
|
||||
--font-sans: var(--font-montserrat);
|
||||
|
||||
/* ── Animations ── */
|
||||
--animate-shimmer: shimmer 2.8s linear infinite;
|
||||
--animate-marquee: marquee 28s linear infinite;
|
||||
--animate-marquee-reverse: marquee 28s linear infinite reverse;
|
||||
--animate-float: float 7s ease-in-out infinite;
|
||||
--animate-pulse-gold: pulse-gold 2.4s ease-in-out infinite;
|
||||
--animate-fade-up: fadeUp 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
--animate-scale-in: scaleIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
/* ── Raw CSS tokens (non-Tailwind) ── */
|
||||
:root {
|
||||
--background: #f9f6f0;
|
||||
--foreground: #121212;
|
||||
|
||||
--shadow-xs: 0 1px 2px rgba(0,0,0,0.06);
|
||||
--shadow-sm: 0 2px 8px rgba(0,0,0,0.08);
|
||||
--shadow-md: 0 4px 20px rgba(0,0,0,0.10);
|
||||
--shadow-lg: 0 8px 36px rgba(0,0,0,0.14);
|
||||
--shadow-xl: 0 16px 60px rgba(0,0,0,0.20);
|
||||
--shadow-gold: 0 4px 28px rgba(201,169,110,0.35);
|
||||
--shadow-gold-lg: 0 8px 48px rgba(201,169,110,0.45);
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 18px;
|
||||
--radius-xl: 28px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
--z-below: -1;
|
||||
--z-base: 0;
|
||||
--z-raised: 10;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-modal: 300;
|
||||
--z-toast: 400;
|
||||
|
||||
--ease-expo-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 600ms;
|
||||
--duration-slower: 1000ms;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--background: #121212;
|
||||
--foreground: #f9f6f0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Keyframes ── */
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-14px); }
|
||||
}
|
||||
|
||||
@keyframes pulse-gold {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(201,169,110,0); }
|
||||
50% { box-shadow: 0 0 0 10px rgba(201,169,110,0.15); }
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(28px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from { opacity: 0; transform: scale(0.93); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes revealLine {
|
||||
from { transform: scaleX(0); }
|
||||
to { transform: scaleX(1); }
|
||||
}
|
||||
|
||||
/* ── Base ── */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
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, h5, h6 {
|
||||
font-family: var(--font-serif);
|
||||
}
|
||||
|
||||
/* ── Utility classes ── */
|
||||
.moy-shimmer {
|
||||
background: linear-gradient(
|
||||
105deg,
|
||||
transparent 35%,
|
||||
rgba(201, 169, 110, 0.45) 50%,
|
||||
transparent 65%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: var(--animate-shimmer);
|
||||
}
|
||||
|
||||
.moy-gold-line {
|
||||
display: block;
|
||||
width: 3.5rem;
|
||||
height: 1.5px;
|
||||
background: var(--color-moy-gold);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.moy-gold-line-center {
|
||||
display: block;
|
||||
width: 3.5rem;
|
||||
height: 1.5px;
|
||||
background: var(--color-moy-gold);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Grain texture overlay for hero */
|
||||
.moy-grain::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"corporate": "Corporate",
|
||||
"venues": "Venues",
|
||||
"gallery": "Gallery",
|
||||
"contact": "Contact"
|
||||
},
|
||||
"footer": {
|
||||
"description": "Blending the unique beauties of the Aegean in Akyaka, Akçapınar, and Datça, we offer unforgettable holiday experiences with a nature-respectful and innovative approach.",
|
||||
"quickLinks": "Quick Links",
|
||||
"about": "About Us",
|
||||
"ourVenues": "Our Venues",
|
||||
"menus": "Menus",
|
||||
"contactInfo": "Contact Info",
|
||||
"rights": "All Rights Reserved."
|
||||
},
|
||||
"home": {
|
||||
"hero": {
|
||||
"eyebrow": "IN THE HEART OF THE AEGEAN",
|
||||
"title": "Your Gateway to Unforgettable Moments",
|
||||
"subtitle": "Experience the unique harmony of gastronomy, accommodation, and nature sports with the privilege of Moy Group.",
|
||||
"cta": "Contact Us",
|
||||
"btn1": "Make a Reservation",
|
||||
"btn2": "Explore Venues",
|
||||
"scroll": "Explore"
|
||||
},
|
||||
"stats": {
|
||||
"years": "Years of Experience",
|
||||
"venues": "Exclusive Venues",
|
||||
"guests": "Happy Guests"
|
||||
},
|
||||
"venues": {
|
||||
"eyebrow": "Our Locations",
|
||||
"title": "Explore Our Venues",
|
||||
"subtitle": "It is very easy to reach our venues in the heart of the Aegean. Let us prepare the best route for you.",
|
||||
"viewMap": "View on Map"
|
||||
},
|
||||
"venuesList": [
|
||||
{
|
||||
"id": "moy-beach",
|
||||
"title": "Moy Beach",
|
||||
"tag": "Beach & Bar",
|
||||
"desc": "An exclusive beach and gastronomy experience amidst the unique nature and azure waters of Akyaka. The point where local aromas meet international flavors.",
|
||||
"img": "/moy-beach.jpg",
|
||||
"color": "from-amber-900/60"
|
||||
},
|
||||
{
|
||||
"id": "kite-beach-hotel",
|
||||
"title": "Kite Beach Hotel",
|
||||
"tag": "Hotel",
|
||||
"desc": "A boutique hotel right on the clear waters of Akyaka, offering the perfect balance of both sports and relaxation. Swim in the sea in the mornings, enjoy the restaurant in the evenings.",
|
||||
"img": "/kite-beach-hotel.jpg",
|
||||
"color": "from-blue-900/60"
|
||||
},
|
||||
{
|
||||
"id": "volkswagen-kite",
|
||||
"title": "Volkswagen Kite (Surfing)",
|
||||
"tag": "Kite & Surf",
|
||||
"desc": "Professional training and equipment rental at Akçapınar Kite Beach, Turkey's best kitesurf spot. Discover freedom in the diamond waters of the Gulf of Gökova.",
|
||||
"img": "/volkswagen-kite.png",
|
||||
"color": "from-green-900/60"
|
||||
}
|
||||
],
|
||||
"locationsMap": [
|
||||
{
|
||||
"place": "Moy Beach",
|
||||
"addr": "Atatürk 1. Cd No:86, Akyaka, Ula/Muğla",
|
||||
"embed": "https://maps.google.com/maps?q=Moy+Beach+Akyaka&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
},
|
||||
{
|
||||
"place": "Kite Beach Hotel",
|
||||
"addr": "Akçapınar Sok. Kiteboard Sahili, Muğla",
|
||||
"embed": "https://maps.google.com/maps?q=Kite+Beach+Hotel+Muğla&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
},
|
||||
{
|
||||
"place": "Volkswagen Kite (Surfing)",
|
||||
"addr": "Akçapınar Kite Sahili, Ula/Muğla",
|
||||
"embed": "https://maps.google.com/maps?q=Volkswagen+Kite+Surfing+Muğla&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
}
|
||||
],
|
||||
"cta": {
|
||||
"eyebrow": "The Aegean is Waiting for You",
|
||||
"title1": "Unforgettable Moments",
|
||||
"title2": "Are Waiting for You",
|
||||
"subtitle": "Make your reservation, we'll take care of the rest. Contact us for personalized packages.",
|
||||
"btn1": "Make a Reservation",
|
||||
"btn2": "Contact Us",
|
||||
"exploreBtn": "Explore in Detail"
|
||||
}
|
||||
},
|
||||
"corporate": {
|
||||
"title": "About Us",
|
||||
"p1": "With years of experience in the tourism sector, we offer our restaurant, bungalow, and beach services with an environmentally friendly, sustainable, and modern approach.",
|
||||
"p2": "In our restaurants, we present the most delicious and fresh products of local Aegean cuisine with unique presentations, promising an unforgettable journey of taste. In our bungalows intertwined with nature, we provide peaceful, comfortable, and high-quality accommodation experiences; aiming for our guests to get away from the chaos of the city and feel the tranquility of nature.",
|
||||
"p3": "In our beach businesses, we consider all the details for you to spend pleasant moments in the turquoise waters of the Aegean. With our professional, smiling, and sincere team, maximizing guest satisfaction and making every moment special is our primary goal.",
|
||||
"p4": "Acting with an environmentally conscious and sustainable understanding, we are proud to contribute to regional tourism and local development. We invite you to collect unforgettable memories with us in the unique beauties of the Aegean.",
|
||||
"vision": "Our Vision",
|
||||
"visionText": "By blending the unique beauties of the Aegean in Datça, Akyaka, and Akçapınar; to be the leading brand of the tourism sector in Turkey by offering unforgettable holiday experiences to our guests with a nature-respectful, sustainable, and innovative approach.",
|
||||
"mission": "Our Mission",
|
||||
"missionText": "Combining our local values with a modern service understanding; to offer high-quality restaurant, accommodation, and beach services that appeal to our guests' taste, comfort, and peace; while doing this, contributing to the development of regional tourism with our environmentally friendly, sincere, and professional service understanding."
|
||||
},
|
||||
"contact": {
|
||||
"eyebrow": "Contact",
|
||||
"title": "Get in Touch",
|
||||
"subtitle": "For reservations, information, or cooperation, you can directly call our venues or fill out the form.",
|
||||
"formEyebrow": "Send a Message",
|
||||
"formTitle": "How can we help?",
|
||||
"name": "Full Name",
|
||||
"namePlaceholder": "Your Name",
|
||||
"email": "Email",
|
||||
"emailPlaceholder": "email@example.com",
|
||||
"phone": "Phone (Optional)",
|
||||
"phonePlaceholder": "+90 5xx xxx xx xx",
|
||||
"subject": "Subject",
|
||||
"subjectOptions": [
|
||||
"Select...",
|
||||
"Reservation Request",
|
||||
"Kitesurf Lessons",
|
||||
"Corporate Event",
|
||||
"General Info"
|
||||
],
|
||||
"message": "Your Message",
|
||||
"messagePlaceholder": "How can we help you?",
|
||||
"send": "Send",
|
||||
"generalContact": "General Contact",
|
||||
"phoneText": "Phone",
|
||||
"emailText": "Email",
|
||||
"hours": "Working Hours",
|
||||
"monFri": "Monday – Friday",
|
||||
"sat": "Saturday",
|
||||
"sun": "Sunday",
|
||||
"hoursNote": "* Season hours may vary.",
|
||||
"social": "Social Media"
|
||||
},
|
||||
"gallery": {
|
||||
"eyebrow": "Memories",
|
||||
"title": "Moy Group Gallery",
|
||||
"subtitle": "From the warm sun to the cool waters of the Aegean, frames from our venues.",
|
||||
"all": "All",
|
||||
"venues": "Venues",
|
||||
"food": "Food",
|
||||
"activity": "Activity"
|
||||
},
|
||||
"faq": {
|
||||
"eyebrow": "FAQ",
|
||||
"title": "Frequently Asked Questions",
|
||||
"subtitle": "Find quick answers to the questions in your mind. If you can't find it, write to us directly.",
|
||||
"categories": {
|
||||
"accommodation": "Accommodation",
|
||||
"kitesurf": "Kite Surf",
|
||||
"reservation": "Reservation & Cancellation"
|
||||
},
|
||||
"q1": "Do you accept pets?",
|
||||
"a1": "We are happy to welcome your pet friends in our bungalow facility and designated areas of our beaches. Please inform us during reservation.",
|
||||
"q2": "What are the Check-in / Check-out times?",
|
||||
"a2": "Check-in is at 14:00, check-out is at 12:00. Extra charges may apply for early check-in or late check-out — subject to availability.",
|
||||
"q3": "Is breakfast included?",
|
||||
"a3": "At Kite Beach Hotel, there are packages including breakfast depending on the room type. Bungalow accommodations offer their own kitchen facilities.",
|
||||
"q4": "Do I need to bring equipment for kitesurf training?",
|
||||
"a4": "No, all up-to-date equipment you need from beginner to advanced level is provided by our center.",
|
||||
"q5": "Is it necessary to know how to swim to learn kitesurf?",
|
||||
"a5": "Yes, basic swimming knowledge is mandatory. All our students wear life jackets during the training.",
|
||||
"q6": "In which months are kitesurf lessons given?",
|
||||
"a6": "Lessons are given during the active season between April and October. The most suitable wind conditions are in June-September.",
|
||||
"q7": "What is your cancellation policy?",
|
||||
"a7": "Full refund is made for cancellations up to 14 days before the check-in date. 50% between 7-14 days, and no refund for less than 7 days.",
|
||||
"q8": "Is prepayment required for reservation?",
|
||||
"a8": "30% of the total amount is taken as prepayment. The balance is charged on the check-in date.",
|
||||
"q9": "Is there a special price for group reservations?",
|
||||
"a9": "Special pricing is applied for group reservations of 10 people or more. Contact us for details."
|
||||
},
|
||||
"menu": {
|
||||
"eyebrow": "Menu",
|
||||
"title": "Digital Menu",
|
||||
"subtitle": "Fresh flavors of the Aegean, signature dishes prepared with local products, and our special beverage selection.",
|
||||
"sectionTitle": "Seasonal & Local",
|
||||
"sectionSubtitle": "The vast majority of our ingredients are supplied fresh from regional producers.",
|
||||
"starters": "Starters",
|
||||
"mains": "Main Courses",
|
||||
"desserts": "Desserts",
|
||||
"drinks": "Drinks",
|
||||
"note": "Prices include VAT. The menu is subject to change. If you have any allergies, please inform our service team."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"navigation": {
|
||||
"home": "Ana Sayfa",
|
||||
"corporate": "Kurumsal",
|
||||
"venues": "Mekanlar",
|
||||
"gallery": "Galeri",
|
||||
"contact": "İletişim"
|
||||
},
|
||||
"footer": {
|
||||
"description": "Ege'nin eşsiz güzelliklerini Akyaka, Akçapınar ve Datça'da harmanlayarak, doğaya saygılı ve yenilikçi bir yaklaşımla unutulmaz tatil deneyimleri sunuyoruz.",
|
||||
"quickLinks": "Hızlı Bağlantılar",
|
||||
"about": "Hakkımızda",
|
||||
"ourVenues": "Mekanlarımız",
|
||||
"menus": "Menüler",
|
||||
"contactInfo": "İletişim",
|
||||
"rights": "Tüm Hakları Saklıdır."
|
||||
},
|
||||
"home": {
|
||||
"hero": {
|
||||
"eyebrow": "EGE'NİN KALBİNDE",
|
||||
"title": "Unutulmaz Anlara Açılan Kapınız",
|
||||
"subtitle": "Gastronomi, konaklama ve doğa sporlarının eşsiz uyumunu Moy Grup ayrıcalığıyla deneyimleyin.",
|
||||
"cta": "Bizimle İletişime Geçin",
|
||||
"btn1": "Rezervasyon Yap",
|
||||
"btn2": "Mekanları Keşfet",
|
||||
"scroll": "Keşfet"
|
||||
},
|
||||
"stats": {
|
||||
"years": "Yıllık Tecrübe",
|
||||
"venues": "Özel Mekan",
|
||||
"guests": "Mutlu Misafir"
|
||||
},
|
||||
"venues": {
|
||||
"eyebrow": "Lokasyonlarımız",
|
||||
"title": "Mekanlarımızı Keşfedin",
|
||||
"subtitle": "Ege'nin kalbindeki mekanlarımıza ulaşmak çok kolay. Sizin için en uygun rotayı hazırlayalım.",
|
||||
"viewMap": "Haritada Gör"
|
||||
},
|
||||
"venuesList": [
|
||||
{
|
||||
"id": "moy-beach",
|
||||
"title": "Moy Beach",
|
||||
"tag": "Beach & Bar",
|
||||
"desc": "Akyaka'nın eşsiz doğası ve masmavi suları arasında, seçkin bir plaj ve gastronomi deneyimi. Yerel aromalar ile uluslararası lezzetlerin buluştuğu nokta.",
|
||||
"img": "/moy-beach.jpg",
|
||||
"color": "from-amber-900/60"
|
||||
},
|
||||
{
|
||||
"id": "kite-beach-hotel",
|
||||
"title": "Kite Beach Hotel",
|
||||
"tag": "Hotel",
|
||||
"desc": "Akyaka'nın berrak sularına sıfır, hem sporun hem de dinlenmenin mükemmel dengesini sunan butik otel. Sabahları denizde yüzün, akşamları restoranda keyif yapın.",
|
||||
"img": "/kite-beach-hotel.jpg",
|
||||
"color": "from-blue-900/60"
|
||||
},
|
||||
{
|
||||
"id": "volkswagen-kite",
|
||||
"title": "Volkswagen Kite (Surfing)",
|
||||
"tag": "Kite & Surf",
|
||||
"desc": "Akçapınar Kite Sahili'nde, Türkiye'nin en iyi kitesurf noktasında profesyonel eğitim ve ekipman kiralama. Gökova Körfezi'nin pırlanta sularında özgürlüğü keşfedin.",
|
||||
"img": "/volkswagen-kite.png",
|
||||
"color": "from-green-900/60"
|
||||
}
|
||||
],
|
||||
"locationsMap": [
|
||||
{
|
||||
"place": "Moy Beach",
|
||||
"addr": "Atatürk 1. Cd No:86, Akyaka, Ula/Muğla",
|
||||
"embed": "https://maps.google.com/maps?q=Moy+Beach+Akyaka&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
},
|
||||
{
|
||||
"place": "Kite Beach Hotel",
|
||||
"addr": "Akçapınar Sok. Kiteboard Sahili, Muğla",
|
||||
"embed": "https://maps.google.com/maps?q=Kite+Beach+Hotel+Muğla&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
},
|
||||
{
|
||||
"place": "Volkswagen Kite (Surfing)",
|
||||
"addr": "Akçapınar Kite Sahili, Ula/Muğla",
|
||||
"embed": "https://maps.google.com/maps?q=Volkswagen+Kite+Surfing+Muğla&t=&z=15&ie=UTF8&iwloc=&output=embed"
|
||||
}
|
||||
],
|
||||
"cta": {
|
||||
"eyebrow": "Ege Sizi Bekliyor",
|
||||
"title1": "Unutulmaz Anlar",
|
||||
"title2": "Sizi Bekliyor",
|
||||
"subtitle": "Rezervasyonunuzu yapın, gerisini biz düşünelim. Kişiye özel paketlerimiz için bize ulaşın.",
|
||||
"btn1": "Rezervasyon Yap",
|
||||
"btn2": "Bize Ulaşın",
|
||||
"exploreBtn": "Detaylı İncele"
|
||||
}
|
||||
},
|
||||
"corporate": {
|
||||
"title": "Hakkımızda",
|
||||
"p1": "Turizm sektöründe uzun yıllara dayanan tecrübemizle restoran, bungalov ve beach hizmetlerimizi doğaya saygılı, sürdürülebilir ve modern bir yaklaşımla sunuyoruz.",
|
||||
"p2": "Restoranlarımızda yerel Ege mutfağının en lezzetli ve taze ürünlerini, özgün sunumlarla misafirlerimizin beğenisine sunarak damak zevkine unutulmaz bir yolculuk vaat ediyoruz. Doğayla iç içe olan bungalovlarımızda huzurlu, konforlu ve kaliteli konaklama deneyimleri sağlıyor; misafirlerimizin şehrin karmaşasından uzaklaşıp doğanın huzurunu hissetmesini amaçlıyoruz.",
|
||||
"p3": "Beach işletmelerimizde ise Ege’nin turkuaz sularında keyifli anlar geçirmeniz için tüm ayrıntıları düşünüyoruz. Profesyonel, güler yüzlü ve samimi ekibimizle misafir memnuniyetini en üst düzeye çıkararak, her anınızı özel kılmak bizim öncelikli hedefimiz.",
|
||||
"p4": "Çevreye duyarlı ve sürdürülebilir bir anlayışla hareket ederek, bölge turizmine ve yerel kalkınmaya katkı sağlamaktan gurur duyuyoruz. Sizleri, Ege’nin benzersiz güzelliklerinde, bizimle unutulmaz anılar biriktirmeye davet ediyoruz.",
|
||||
"vision": "Vizyonumuz",
|
||||
"visionText": "Ege’nin eşsiz güzelliklerini Datça, Akyaka ve Akçapınar'da harmanlayarak; doğaya saygılı, sürdürülebilir ve yenilikçi bir yaklaşımla misafirlerimize unutulmaz tatil deneyimleri sunarak Türkiye’de turizm sektörünün öncü markası olmak.",
|
||||
"mission": "Misyonumuz",
|
||||
"missionText": "Yerel değerlerimizi modern hizmet anlayışıyla birleştirerek; misafirlerimizin damak tadına, konforuna ve huzuruna hitap eden kaliteli restoran, konaklama ve beach hizmetleri sunmak; bunu yaparken çevreye duyarlı, samimi ve profesyonel hizmet anlayışımızla bölge turizminin gelişimine katkı sağlamak."
|
||||
},
|
||||
"contact": {
|
||||
"eyebrow": "İletişim",
|
||||
"title": "Bize Ulaşın",
|
||||
"subtitle": "Rezervasyon, bilgi veya işbirliği için mekanlarımızı doğrudan arayabilir ya da formu doldurabilirsiniz.",
|
||||
"formEyebrow": "Mesaj Gönderin",
|
||||
"formTitle": "Nasıl Yardımcı Olalım?",
|
||||
"name": "Adınız Soyadınız",
|
||||
"namePlaceholder": "Adınız",
|
||||
"email": "E-Posta",
|
||||
"emailPlaceholder": "email@example.com",
|
||||
"phone": "Telefon (Opsiyonel)",
|
||||
"phonePlaceholder": "+90 5xx xxx xx xx",
|
||||
"subject": "Konu",
|
||||
"subjectOptions": [
|
||||
"Seçiniz...",
|
||||
"Rezervasyon Talebi",
|
||||
"Kitesurf Dersleri",
|
||||
"Kurumsal Etkinlik",
|
||||
"Genel Bilgi"
|
||||
],
|
||||
"message": "Mesajınız",
|
||||
"messagePlaceholder": "Nasıl yardımcı olabiliriz?",
|
||||
"send": "Gönder",
|
||||
"generalContact": "Genel İletişim",
|
||||
"phoneText": "Telefon",
|
||||
"emailText": "E-Posta",
|
||||
"hours": "Çalışma Saatleri",
|
||||
"monFri": "Pazartesi – Cuma",
|
||||
"sat": "Cumartesi",
|
||||
"sun": "Pazar",
|
||||
"hoursNote": "* Sezon saatleri değişiklik gösterebilir.",
|
||||
"social": "Sosyal Medya"
|
||||
},
|
||||
"gallery": {
|
||||
"eyebrow": "Anılar",
|
||||
"title": "Moy Group Galerisi",
|
||||
"subtitle": "Ege'nin sıcak güneşinden serin sularına, mekanlarımızdan kareler.",
|
||||
"all": "Tümü",
|
||||
"venues": "Mekanlar",
|
||||
"food": "Yemek",
|
||||
"activity": "Aktivite"
|
||||
},
|
||||
"faq": {
|
||||
"eyebrow": "SSS",
|
||||
"title": "Sık Sorulan Sorular",
|
||||
"subtitle": "Aklınızdaki sorulara hızlıca yanıt bulun. Bulamadıysanız bize doğrudan yazın.",
|
||||
"categories": {
|
||||
"accommodation": "Konaklama",
|
||||
"kitesurf": "Kite Surf",
|
||||
"reservation": "Rezervasyon & İptal"
|
||||
},
|
||||
"q1": "Evcil hayvan kabul ediyor musunuz?",
|
||||
"a1": "Bungalov tesisimizde ve plajlarımızın belirli alanlarında evcil dostlarınızı ağırlamaktan memnuniyet duyarız. Rezervasyon sırasında bilgi vermenizi rica ederiz.",
|
||||
"q2": "Check-in / Check-out saatleri nelerdir?",
|
||||
"a2": "Check-in saat 14:00, check-out ise saat 12:00'dir. Erken check-in veya geç check-out için ekstra ücret uygulanabilir — müsaitlik durumuna göre değerlendirilir.",
|
||||
"q3": "Kahvaltı dahil mi?",
|
||||
"a3": "Kite Beach Hotel'de oda türüne göre kahvaltı dahil paketler mevcuttur. Bungalov konaklamaları kendi mutfak imkânı sunmaktadır.",
|
||||
"q4": "Kitesurf eğitimi için ekipman getirmem gerekiyor mu?",
|
||||
"a4": "Hayır, başlangıçtan ileri seviyeye kadar ihtiyacınız olan tüm güncel ekipmanlar merkezimiz tarafından temin edilmektedir.",
|
||||
"q5": "Kitesurf öğrenmek için yüzme bilmek şart mı?",
|
||||
"a5": "Evet, temel düzeyde yüzme bilgisi zorunludur. Tüm öğrencilerimiz eğitim boyunca can yeleği giymektedir.",
|
||||
"q6": "Hangi aylarda kitesurf dersi verilmektedir?",
|
||||
"a6": "Nisan–Ekim ayları arasında aktif sezon boyunca ders verilmektedir. En uygun rüzgar koşulları Haziran–Eylül aylarındadır.",
|
||||
"q7": "İptal politikanız nedir?",
|
||||
"a7": "Giriş tarihine 14 gün kalana kadar yapılan iptallerde tam iade yapılmaktadır. 7–14 gün arasında %50, 7 günden kısa sürede ise iade yapılamamaktadır.",
|
||||
"q8": "Rezervasyon için ön ödeme gerekiyor mu?",
|
||||
"a8": "Toplam tutarın %30'u ön ödeme olarak alınmaktadır. Bakiye, giriş tarihinde tahsil edilir.",
|
||||
"q9": "Grup rezervasyonu için özel fiyat var mı?",
|
||||
"a9": "10 kişi ve üzeri grup rezervasyonlarında özel fiyatlandırma uygulanmaktadır. Detaylar için bize ulaşın."
|
||||
},
|
||||
"menu": {
|
||||
"eyebrow": "Menü",
|
||||
"title": "Dijital Menü",
|
||||
"subtitle": "Ege'nin taze lezzetleri, yerel ürünlerle hazırlanan imza yemekler ve özel içecek seçkimiz.",
|
||||
"sectionTitle": "Mevsimsel & Yerel",
|
||||
"sectionSubtitle": "Malzemelerimizin büyük çoğunluğu bölgesel üreticilerden, taze temin edilmektedir.",
|
||||
"starters": "Başlangıçlar",
|
||||
"mains": "Ana Yemekler",
|
||||
"desserts": "Tatlılar",
|
||||
"drinks": "İçecekler",
|
||||
"note": "Fiyatlara KDV dahildir. Menü değişiklik gösterebilir. Alerjiniz varsa lütfen servis ekibimizi bilgilendirin."
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# Moy Group — Web Sitesi Yenileme Promptu
|
||||
|
||||
## Proje Tanımı
|
||||
|
||||
Moy Group, Ege'nin seçkin lokasyonlarında (Akyaka, Akçapınar, Datça, Alaçatı) faaliyet
|
||||
gösteren bir turizm grubudur. Bünyesinde restoran, beach bar, bungalov konaklama, hotel
|
||||
ve kitesurf merkezi hizmetleri sunulmaktadır.
|
||||
|
||||
Mevcut web sitesi (moygroup.com.tr) temel bilgileri içermekle birlikte görsel çekicilik,
|
||||
kullanıcı deneyimi ve içerik derinliği açısından güncellenmesi gerekmektedir.
|
||||
|
||||
---
|
||||
|
||||
## Marka Kimliği & Tasarım Dili
|
||||
|
||||
- **Renk Paleti:** Mevcut altın sarısı ton (#c9a96e) ve koyu arka plan kombinasyonu
|
||||
korunabilir; buna Ege mavisi ve doğal yeşil tonları eklenerek zenginleştirilmeli.
|
||||
- **Yazı Tipi:** Lüks, okunaklı ve modern serif/sans-serif kombinasyonu
|
||||
(örn. *Cormorant Garamond + Montserrat*).
|
||||
- **Genel His:** Doğa ile iç içe, premium tatil deneyimi; minimal ama sıcak ve davetkar.
|
||||
- **Logo:** Marka kimliği tutarlı biçimde tüm sayfalarda yer almalı.
|
||||
|
||||
---
|
||||
|
||||
## Ana Sayfa (Homepage) Yenileme
|
||||
|
||||
- **Hero Bölümü:** Yüksek kaliteli, sinematik video veya parallax slider
|
||||
(Ege denizi, plaj, bungalov, restoran görüntüleri).
|
||||
Kısa ve etkileyici bir slogan (örn. *"Ege'nin Ruhunu Hisset."*).
|
||||
- **CTA Butonları:** "Rezervasyon Yap", "Mekanları Keşfet", "Bize Ulaşın".
|
||||
- **Hizmetler Bölümü:** Restaurant, Beach, Bungalov, Kite Surf ve Hotel kategorileri
|
||||
animasyonlu ikonlar ve hover efektleriyle kart tasarımına dönüştürülmeli.
|
||||
- **Mekanlar Bölümü:** Moy Beach, Kite Beach Hotel ve Kite Beach Akyaka için
|
||||
görsel ağırlıklı, tam ekran kart galerisi.
|
||||
- **Testimonial Slider:** Misafir yorumları ve puanlamaları.
|
||||
- **Instagram Feed / Galeri:** Sosyal medya entegrasyonu veya fotoğraf duvarı.
|
||||
- **Bizi Bulun:** Google Maps embed ile lokasyon bölümü.
|
||||
|
||||
---
|
||||
|
||||
## Eksik Sayfalar & Yeni Özellikler
|
||||
|
||||
### 1. Rezervasyon / Booking Sistemi
|
||||
Online rezervasyon formu veya entegrasyon; bungalov, restoran ve kitesurf dersi
|
||||
için ayrı ayrı rezervasyon akışı.
|
||||
|
||||
### 2. Dijital Menü
|
||||
Restoranlar için interaktif veya PDF tabanlı dijital menü sayfası.
|
||||
|
||||
### 3. Galeri
|
||||
Profesyonel fotoğraf ve videolarla zengin, filtrelenebilir galeri sayfası
|
||||
(mekan ve kategori bazlı).
|
||||
|
||||
### 4. Blog / Haberler
|
||||
Ege tatil rehberleri, kitesurf ipuçları, yerel etkinlikler gibi içeriklerle
|
||||
SEO değerini artıracak blog bölümü.
|
||||
|
||||
### 5. Çoklu Dil Desteği
|
||||
Türkçe + İngilizce zorunlu; Almanca opsiyonel
|
||||
(Akyaka ve Datça'ya gelen yabancı turist profili gözetilerek).
|
||||
|
||||
### 6. SSS Sayfası
|
||||
Konaklama koşulları, kitesurf eğitimi gereksinimleri, ulaşım ve iptal
|
||||
politikaları gibi sık sorulan sorular.
|
||||
|
||||
### 7. Güçlendirilmiş İletişim Sayfası
|
||||
Her mekan için ayrı adres, telefon, e-posta ve harita bilgisi.
|
||||
|
||||
---
|
||||
|
||||
## Teknik Gereksinimler
|
||||
|
||||
- **Responsive Tasarım:** Mobil öncelikli (mobile-first), tüm ekran boyutlarına uyumlu.
|
||||
- **Performans:** Lazy loading, WebP görsel formatı, CDN kullanımı ile hızlı yükleme.
|
||||
- **SEO:** Mekan bazlı anahtar kelimeler
|
||||
(örn. "Akyaka beach bar", "Akçapınar bungalov", "kitesurf eğitimi Muğla").
|
||||
- **Güvenlik & Uyum:** SSL sertifikası, KVKK uyumu, çerez politikası banner'ı.
|
||||
- **Analitik:** Google Analytics 4 ve Meta Pixel entegrasyonu.
|
||||
- **CMS:** WordPress veya Webflow; içerik yönetiminin kolaylaştırılması için
|
||||
güçlü bir yönetim paneli.
|
||||
|
||||
---
|
||||
|
||||
## Hedef Kitle
|
||||
|
||||
Ege tatilini deniz, doğa ve gastronomi odaklı keşfetmek isteyen; 25–50 yaş arası,
|
||||
orta-üst gelir grubundan yerli ve yabancı turistler ile kitesurf meraklıları.
|
||||
|
||||
---
|
||||
|
||||
## Vizyon & Misyon (Referans)
|
||||
|
||||
> **Vizyon:** Ege'nin eşsiz güzelliklerini Datça, Akyaka ve Alaçatı'da harmanlayarak;
|
||||
> doğaya saygılı, sürdürülebilir ve yenilikçi bir yaklaşımla misafirlerimize unutulmaz
|
||||
> tatil deneyimleri sunarak Türkiye'de turizm sektörünün öncü markası olmak.
|
||||
|
||||
> **Misyon:** Yerel değerlerimizi modern hizmet anlayışıyla birleştirerek; misafirlerimizin
|
||||
> damak tadına, konforuna ve huzuruna hitap eden kaliteli restoran, konaklama ve beach
|
||||
> hizmetleri sunmak; bunu yaparken çevreye duyarlı, samimi ve profesyonel hizmet
|
||||
> anlayışımızla bölge turizminin gelişimine katkı sağlamak.
|
||||
|
||||
---
|
||||
|
||||
*Bu doküman bir web geliştirici ajansına veya tasarım ekibine proje brief'i olarak sunulabilir.*
|
||||
@@ -0,0 +1,3 @@
|
||||
export function cn(...classes: (string | undefined | null | false | 0)[]): string {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
+8
-1
@@ -1,7 +1,14 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
Generated
+53
@@ -8,6 +8,8 @@
|
||||
"name": "moygrup",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.40.0",
|
||||
"lucide-react": "^1.17.0",
|
||||
"next": "16.2.7",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
@@ -3762,6 +3764,33 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.40.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz",
|
||||
"integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.40.0",
|
||||
"motion-utils": "^12.39.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",
|
||||
@@ -5032,6 +5061,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz",
|
||||
"integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==",
|
||||
"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",
|
||||
@@ -5099,6 +5137,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.40.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
|
||||
"integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.39.0"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.39.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
|
||||
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.40.0",
|
||||
"lucide-react": "^1.17.0",
|
||||
"next": "16.2.7",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { i18n } from './app/dictionaries';
|
||||
|
||||
function getLocale(request: NextRequest): string {
|
||||
// Always return default locale for now (can be improved to read headers)
|
||||
return i18n.defaultLocale;
|
||||
}
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
const pathname = request.nextUrl.pathname;
|
||||
|
||||
// Skip public files and _next requests
|
||||
if (
|
||||
pathname.startsWith('/_next') ||
|
||||
pathname.includes('.') ||
|
||||
pathname.startsWith('/api')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there is any supported locale in the pathname
|
||||
const pathnameIsMissingLocale = i18n.locales.every(
|
||||
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
|
||||
);
|
||||
|
||||
if (pathnameIsMissingLocale) {
|
||||
const locale = getLocale(request);
|
||||
return NextResponse.redirect(
|
||||
new URL(`/${locale}${pathname === '/' ? '' : pathname}`, request.url)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 493 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 817 KiB |
Reference in New Issue
Block a user