first commit

This commit is contained in:
mstfyldz
2026-06-03 03:59:46 +03:00
parent 5882fc12c1
commit bba389b513
30 changed files with 3153 additions and 149 deletions
+56 -27
View File
@@ -1,36 +1,65 @@
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).
# Kozmos Beach & More
**"Denizin Ormana Kıyısında" / "Where the Sea Meets the Forest"**
Kozmos Beach & More is a modern, responsive, and bilingual (Turkish & English) corporate website built with Next.js 16 (App Router). It features a mobile-first design, a seamless single-page scrolling experience, and dedicated pages like Accommodation, all wrapped in a boho-chic, Aegean-inspired aesthetic.
## Features
- **Next.js 16 (App Router):** Fast, SEO-friendly, and modern architecture.
- **i18n Support:** Fully bilingual (TR/EN) using `next-intl`.
- **Styling:** Tailwind CSS v4 with custom gradients, colors, and an elegant typography system (Marcellus).
- **Animations:** Smooth micro-interactions and scroll animations powered by `framer-motion`.
- **Responsive Design:** Mobile-first approach, ensuring a flawless experience across all devices.
- **Lightbox Gallery:** Custom-built image gallery with a full-screen lightbox feature.
- **PWA & SEO Ready:** Pre-configured `manifest.ts`, `robots.ts`, `sitemap.ts`, and metadata.
## Tech Stack
- **Framework:** [Next.js](https://nextjs.org/)
- **Language:** [TypeScript](https://www.typescriptlang.org/)
- **Styling:** [Tailwind CSS v4](https://tailwindcss.com/)
- **Animations:** [Framer Motion](https://www.framer.com/motion/)
- **Icons:** [Lucide React](https://lucide.dev/)
- **Internationalization:** [next-intl](https://next-intl-docs.vercel.app/)
## Environment Variables
To run the project locally, create a `.env.local` file with the following variables:
```env
# Contact number for WhatsApp reservation buttons
NEXT_PUBLIC_WHATSAPP_NUMBER="905000000000"
# URL for the QR menu (if applicable)
NEXT_PUBLIC_MENU_URL="https://qr.kozmosbeach.com"
```
## Getting Started
First, run the development server:
1. **Install dependencies:**
```bash
npm install
```
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
2. **Run the development server:**
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
3. **Build for production:**
```bash
npm run build
npm run start
```
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
## Development Commands
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.
- `npm run dev`: Starts the development server.
- `npm run build`: Builds the app for production.
- `npm run start`: Runs the built app in production mode.
- `npm run lint`: Runs ESLint.
## 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.
*Created by [ayris.tech](https://ayris.tech)*
+24
View File
@@ -0,0 +1,24 @@
import Navbar from '@/components/Navbar';
import Accommodation from '@/components/Accommodation';
import Footer from '@/components/Footer';
import WhatsAppButton from '@/components/WhatsAppButton';
export default async function AccommodationPage({
params
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params;
return (
<main className="min-h-screen bg-sand selection:bg-coral selection:text-white flex flex-col">
<Navbar locale={locale} />
{/* Spacer to push content below fixed navbar */}
<div className="flex-grow pt-24">
<Accommodation />
</div>
<Footer />
<WhatsAppButton />
</main>
);
}
+47
View File
@@ -0,0 +1,47 @@
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
import { Marcellus } from 'next/font/google';
import '../globals.css';
const marcellus = Marcellus({
subsets: ['latin'],
variable: '--font-marcellus',
weight: ['400'],
});
export const metadata = {
title: 'Kozmos Beach & More',
description: 'Denizin Ormana Kıyısında — Akyaka, Gökova',
};
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
if (!routing.locales.includes(locale as any)) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale} data-scroll-behavior="smooth" className={`${marcellus.variable}`}>
<body className="bg-sand text-midnight font-body antialiased relative">
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
+35
View File
@@ -0,0 +1,35 @@
import Navbar from '@/components/Navbar';
import Hero from '@/components/Hero';
import About from '@/components/About';
import Accommodation from '@/components/Accommodation';
import Beach from '@/components/Beach';
import Dining from '@/components/Dining';
import Events from '@/components/Events';
import Gallery from '@/components/Gallery';
import Contact from '@/components/Contact';
import Footer from '@/components/Footer';
import WhatsAppButton from '@/components/WhatsAppButton';
export default async function Page({
params
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params;
return (
<main className="min-h-screen bg-cream selection:bg-turquoise selection:text-white">
<Navbar locale={locale} />
<Hero />
<About />
<Accommodation />
<Beach />
<Dining />
<Events />
<Gallery />
<Contact />
<Footer />
<WhatsAppButton />
</main>
);
}
+148 -17
View File
@@ -1,26 +1,157 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
@theme {
--color-sand: #FFF8EE;
--color-sandy: #F5E6CC;
--color-coral: #FF5A36;
--color-coral-light: #FF7A5A;
--color-amber: #FFA827;
--color-gold: #FFD166;
--color-aqua: #00BFA5;
--color-cyan: #00E5FF;
--color-midnight: #080D18;
--color-deep: #0E1B30;
--color-navy: #1A2F4A;
/* backward compat */
--color-cream: #FFF8EE;
--color-beige: #F5E6CC;
--color-turquoise: #00BFA5;
--color-forest: #0E1B30;
--color-dark-brown: #080D18;
--font-heading: var(--font-marcellus);
--font-body: var(--font-marcellus);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@layer base {
body {
@apply bg-sand text-midnight font-body antialiased;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
h1, h2, h3, h4, h5, h6 {
@apply font-heading;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
html {
scroll-behavior: smooth;
}
/* ── CSS custom property for animated border ──── */
@property --border-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
/* ── Animated conic border classes ─────────────── */
.border-spin-cyan {
background:
linear-gradient(#0E1B30, #0E1B30) padding-box,
conic-gradient(from var(--border-angle), #00E5FF 0deg, transparent 70deg, transparent 290deg, #00E5FF 360deg) border-box;
border: 1px solid transparent;
animation: border-rotate 3s linear infinite;
}
.border-spin-coral {
background:
linear-gradient(#0E1B30, #0E1B30) padding-box,
conic-gradient(from var(--border-angle), #FF5A36 0deg, transparent 70deg, transparent 290deg, #FF5A36 360deg) border-box;
border: 1px solid transparent;
animation: border-rotate 4s linear infinite;
}
.border-spin-amber {
background:
linear-gradient(#0E1B30, #0E1B30) padding-box,
conic-gradient(from var(--border-angle), #FFA827 0deg, transparent 70deg, transparent 290deg, #FFA827 360deg) border-box;
border: 1px solid transparent;
animation: border-rotate 5s linear infinite;
}
/* ── Keyframes ─────────────────────────────────── */
@keyframes shimmer {
0% { background-position: -400% center; }
100% { background-position: 400% center; }
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-16px); }
}
@keyframes border-rotate {
to { --border-angle: 360deg; }
}
@keyframes eq-bar {
0%, 100% { transform: scaleY(0.3); }
50% { transform: scaleY(1); }
}
@keyframes grain {
0%, 100% { transform: translate(0, 0 ); }
10% { transform: translate(-5%, -5% ); }
20% { transform: translate(-10%, 5% ); }
30% { transform: translate(5%, -10%); }
40% { transform: translate(-5%, 15%); }
50% { transform: translate(-10%, 5%); }
60% { transform: translate(15%, 0% ); }
70% { transform: translate(0%, 10%); }
80% { transform: translate(-15%, 0% ); }
90% { transform: translate(10%, 5%); }
}
/* ── Utility classes ───────────────────────────── */
.shimmer-text {
background: linear-gradient(
90deg,
#ffffff 0%,
#ffffff 35%,
#FFD166 45%,
#FF5A36 50%,
#FFD166 55%,
#ffffff 65%,
#ffffff 100%
);
background-size: 400% auto;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: shimmer 4s linear infinite;
}
.gradient-text {
background: linear-gradient(135deg, #FF5A36, #FFA827, #00BFA5);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.gradient-text-warm {
background: linear-gradient(135deg, #FF5A36, #FFA827);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.gradient-text-cool {
background: linear-gradient(135deg, #00E5FF, #00BFA5);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.grain-overlay::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
z-index: 1;
opacity: 0.045;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
animation: grain 8s steps(10) infinite;
}
-33
View File
@@ -1,33 +0,0 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
</html>
);
}
+25
View File
@@ -0,0 +1,25 @@
import { MetadataRoute } from 'next';
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Kozmos Beach & More',
short_name: 'Kozmos',
description: 'Where the Sea Meets the Forest',
start_url: '/',
display: 'standalone',
background_color: '#F5F0E8',
theme_color: '#4AAFA8',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
};
}
-65
View File
@@ -1,65 +0,0 @@
import Image from "next/image";
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
);
}
+11
View File
@@ -0,0 +1,11 @@
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: 'https://kozmosbeach.com/sitemap.xml',
};
}
+20
View File
@@ -0,0 +1,20 @@
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://kozmosbeach.com';
return [
{
url: `${baseUrl}/tr`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 1,
},
{
url: `${baseUrl}/en`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 1,
},
];
}
+94
View File
@@ -0,0 +1,94 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import { Waves, Leaf, Music } from 'lucide-react';
import Image from 'next/image';
const features = [
{ icon: Waves, titleKey: 'card1_title', descKey: 'card1_desc', color: 'text-aqua', bg: 'bg-aqua/10' },
{ icon: Leaf, titleKey: 'card2_title', descKey: 'card2_desc', color: 'text-amber', bg: 'bg-amber/10' },
{ icon: Music, titleKey: 'card3_title', descKey: 'card3_desc', color: 'text-coral', bg: 'bg-coral/10' },
];
export default function About() {
const t = useTranslations('About');
return (
<section id="about" className="py-28 bg-sand text-midnight relative">
<div className="container mx-auto px-6 lg:px-12">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-20 items-center">
{/* Left content */}
<motion.div
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="space-y-8"
>
<div>
<span className="text-xs tracking-[0.4em] uppercase font-black text-coral mb-4 block">
Beach &amp; More
</span>
<h2 className="font-heading text-5xl md:text-6xl font-black leading-tight text-midnight mb-6">
{t('title')}
</h2>
<p className="text-lg leading-relaxed text-midnight/60">{t('description')}</p>
</div>
<div className="space-y-2">
{features.map(({ icon: Icon, titleKey, descKey, color, bg }, i) => (
<motion.div
key={titleKey}
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1 + 0.2 }}
className="flex items-start gap-4 p-4 rounded-2xl hover:bg-sandy/50 transition-colors"
>
<div className={`p-3 rounded-xl ${bg} ${color} flex-shrink-0`}>
<Icon size={20} />
</div>
<div>
<h3 className="font-heading text-lg font-bold mb-0.5">{t(titleKey)}</h3>
<p className="text-midnight/55 text-sm leading-relaxed">{t(descKey)}</p>
</div>
</motion.div>
))}
</div>
</motion.div>
{/* Right image */}
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
className="relative"
>
{/* Glow ring */}
<div
className="absolute inset-0 rounded-3xl opacity-30 blur-2xl scale-105"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827, #00BFA5)' }}
/>
<div className="relative h-[560px] w-full rounded-3xl overflow-hidden shadow-2xl">
<Image
src="https://picsum.photos/800/1100?random=2"
alt="Kozmos atmosferi"
fill
sizes="(max-width: 1024px) 100vw, 50vw"
className="object-cover"
/>
<div
className="absolute inset-0"
style={{ background: 'linear-gradient(to top, rgba(8,13,24,0.35) 0%, transparent 55%)' }}
/>
</div>
</motion.div>
</div>
</div>
</section>
);
}
+149
View File
@@ -0,0 +1,149 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import Image from 'next/image';
import { Users, Check, Sparkles } from 'lucide-react';
export default function Accommodation() {
const t = useTranslations('Accommodation');
const openWhatsApp = () => {
const phone = process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || '905000000000';
window.open(`https://wa.me/${phone}`, '_blank');
};
const rooms = [
{
id: 'standard',
image: 'https://picsum.photos/600/400?random=3',
capacity: 2,
features: t.raw('rooms.standard.features') as string[],
featured: false,
gradient: 'linear-gradient(135deg, #00BFA5, #00E5FF)',
},
{
id: 'sea_view',
image: 'https://picsum.photos/600/400?random=4',
capacity: 2,
features: t.raw('rooms.sea_view.features') as string[],
featured: true,
gradient: 'linear-gradient(135deg, #FF5A36, #FFA827)',
},
{
id: 'garden_suite',
image: 'https://picsum.photos/600/400?random=5',
capacity: 4,
features: t.raw('rooms.garden_suite.features') as string[],
featured: false,
gradient: 'linear-gradient(135deg, #00BFA5, #00E5FF)',
},
];
return (
<section id="accommodation" className="py-28 bg-sandy/25 text-midnight">
<div className="container mx-auto px-6 lg:px-12">
<div className="text-center max-w-3xl mx-auto mb-16">
<motion.span
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-xs tracking-[0.4em] uppercase font-black text-coral mb-4 block"
>
Konaklama
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="font-heading text-5xl md:text-6xl font-black mb-6 text-midnight"
>
{t('title')}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-lg text-midnight/60"
>
{t('description')}
</motion.p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{rooms.map((room, index) => (
<motion.div
key={room.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1, duration: 0.6 }}
className={`relative rounded-3xl overflow-hidden bg-white shadow-lg hover:shadow-2xl transition-all duration-500 hover:-translate-y-1 ${
room.featured ? 'outline outline-2 outline-offset-2 outline-coral' : ''
}`}
>
{/* Featured badge */}
{room.featured && (
<div
className="absolute top-4 right-4 z-10 flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[11px] font-black text-white tracking-wider"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827)' }}
>
<Sparkles size={10} />
FEATURED
</div>
)}
{/* Image */}
<div className="relative h-52 w-full overflow-hidden">
<Image
src={room.image}
alt={t(`rooms.${room.id}.name`)}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="object-cover transition-transform duration-700 hover:scale-110"
/>
<div
className="absolute inset-0"
style={{ background: 'linear-gradient(to top, rgba(8,13,24,0.5), transparent)' }}
/>
</div>
{/* Content */}
<div className="p-7">
<h3 className="font-heading text-xl font-black mb-2 text-midnight">
{t(`rooms.${room.id}.name`)}
</h3>
<div className="flex items-center gap-2 text-midnight/45 mb-5 text-sm">
<Users size={14} />
<span>
{t('capacity')} {room.capacity} {t('person')}
</span>
</div>
<ul className="space-y-2 mb-7">
{room.features.map((feature, i) => (
<li key={i} className="flex items-center gap-2.5 text-sm text-midnight/65">
<Check size={13} className="text-aqua flex-shrink-0" />
<span>{feature}</span>
</li>
))}
</ul>
<button
onClick={openWhatsApp}
className="w-full py-3.5 rounded-2xl font-black text-sm text-white transition-all duration-300 hover:opacity-90 hover:scale-[1.02]"
style={{ background: room.gradient }}
>
{t('book_whatsapp')}
</button>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}
+139
View File
@@ -0,0 +1,139 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import Image from 'next/image';
import { Sun } from 'lucide-react';
export default function Beach() {
const t = useTranslations('Beach');
const openWhatsApp = () => {
const phone = process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || '905000000000';
window.open(
`https://wa.me/${phone}?text=Merhaba,%20şezlong%20rezervasyonu%20yaptırmak%20istiyorum.`,
'_blank',
);
};
const stats = [
{ value: '∞', label: 'Şezlong' },
{ value: '7/7', label: 'Açık' },
{ value: 'VIP', label: 'Cabana' },
];
return (
<section id="beach" className="pt-28 pb-36 bg-sand text-midnight relative overflow-hidden">
<div className="container mx-auto px-6 lg:px-12">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
{/* Left masonry photos */}
<div className="grid grid-cols-2 gap-3">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="space-y-3"
>
<div className="relative h-64 rounded-2xl overflow-hidden shadow-md">
<Image
src="https://picsum.photos/400/600?random=10"
alt="Plaj"
fill
sizes="(max-width: 1024px) 50vw, 25vw"
className="object-cover hover:scale-105 transition-transform duration-700"
/>
</div>
<div className="relative h-48 rounded-2xl overflow-hidden shadow-md">
<Image
src="https://picsum.photos/400/400?random=11"
alt="Şezlong"
fill
sizes="(max-width: 1024px) 50vw, 25vw"
className="object-cover hover:scale-105 transition-transform duration-700"
/>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.15 }}
className="space-y-3 pt-8"
>
<div className="relative h-48 rounded-2xl overflow-hidden shadow-md">
<Image
src="https://picsum.photos/400/400?random=12"
alt="Deniz"
fill
sizes="(max-width: 1024px) 50vw, 25vw"
className="object-cover hover:scale-105 transition-transform duration-700"
/>
</div>
<div className="relative h-64 rounded-2xl overflow-hidden shadow-md">
<Image
src="https://picsum.photos/400/600?random=13"
alt="Koy"
fill
sizes="(max-width: 1024px) 50vw, 25vw"
className="object-cover hover:scale-105 transition-transform duration-700"
/>
</div>
</motion.div>
</div>
{/* Right content */}
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
className="space-y-7"
>
<div className="inline-flex items-center gap-2 font-black text-amber tracking-wider uppercase text-sm">
<Sun size={18} />
Beach Club
</div>
<h2 className="font-heading text-5xl md:text-6xl font-black text-midnight leading-tight">
{t('title')}
</h2>
<p className="text-lg text-midnight/60 leading-relaxed">{t('description')}</p>
{/* Stats */}
<div className="grid grid-cols-3 gap-4 py-6 border-y border-sandy">
{stats.map(({ value, label }) => (
<div key={label} className="text-center">
<div className="font-heading text-2xl font-black gradient-text-warm">{value}</div>
<div className="text-[11px] text-midnight/45 mt-1 uppercase tracking-wider">{label}</div>
</div>
))}
</div>
{/* Info & CTA */}
<div className="p-6 rounded-2xl bg-sandy/50 border border-sandy">
<p className="text-midnight/70 text-sm font-medium mb-5">{t('reservation_info')}</p>
<button
onClick={openWhatsApp}
className="px-8 py-3.5 rounded-full font-black text-sm text-midnight transition-all duration-300 hover:scale-105 hover:shadow-lg"
style={{ background: 'linear-gradient(135deg, #FFA827, #FFD166)' }}
>
{t('book_lounger')}
</button>
</div>
</motion.div>
</div>
</div>
{/* Wave → midnight */}
<div className="absolute bottom-0 left-0 w-full leading-none pointer-events-none">
<svg viewBox="0 0 1440 64" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" className="w-full h-16">
<path d="M0,32 C240,64 480,0 720,32 C960,64 1200,0 1440,32 L1440,64 L0,64 Z" fill="#080D18" />
</svg>
</div>
</section>
);
}
+200
View File
@@ -0,0 +1,200 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import { MapPin, Phone, Send } from 'lucide-react';
import { Instagram } from '@/components/InstagramIcon';
import { useState } from 'react';
export default function Contact() {
const t = useTranslations('Contact');
const [formData, setFormData] = useState({ name: '', date: '', guests: '', message: '' });
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const phone = process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || '905000000000';
const text = `*Yeni Rezervasyon Talebi*%0A%0A*Ad Soyad:* ${formData.name}%0A*Tarih:* ${formData.date}%0A*Kişi Sayısı:* ${formData.guests}%0A*Mesaj:* ${formData.message}`;
window.open(`https://wa.me/${phone}?text=${text}`, '_blank');
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const contactItems = [
{
Icon: MapPin,
label: 'Adres',
value: t('address'),
color: 'text-coral',
bg: 'bg-coral/10',
},
{
Icon: Phone,
label: 'Telefon / WhatsApp',
value: `+${process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || '90 500 000 00 00'}`,
color: 'text-aqua',
bg: 'bg-aqua/10',
},
{
Icon: Instagram,
label: 'Instagram',
value: '@kozmosbeach',
href: 'https://www.instagram.com/kozmosbeach/',
color: 'text-amber',
bg: 'bg-amber/10',
},
];
const inputClass =
'w-full bg-sand border border-sandy rounded-xl px-4 py-3 text-sm focus:outline-none focus:border-coral focus:ring-1 focus:ring-coral transition-all placeholder:text-midnight/30';
return (
<section id="contact" className="py-28 bg-sandy/25 text-midnight">
<div className="container mx-auto px-6 lg:px-12">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<span className="text-xs tracking-[0.4em] uppercase font-black text-coral mb-4 block">
Bize Ulaşın
</span>
<h2 className="font-heading text-5xl md:text-6xl font-black text-midnight">
{t('title')}
</h2>
</motion.div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16">
{/* Left: info + form */}
<motion.div
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="space-y-8"
>
{/* Contact info */}
<div className="space-y-5">
{contactItems.map(({ Icon, label, value, href, color, bg }) => (
<div key={label} className="flex items-start gap-4">
<div className={`p-3 rounded-xl ${bg} ${color} flex-shrink-0`}>
<Icon size={19} />
</div>
<div>
<p className="font-bold text-sm text-midnight/50 mb-0.5">{label}</p>
{href ? (
<a
href={href}
target="_blank"
rel="noreferrer"
className={`${color} font-medium hover:underline`}
>
{value}
</a>
) : (
<p className="text-midnight font-medium">{value}</p>
)}
</div>
</div>
))}
</div>
{/* Form */}
<form
onSubmit={handleSubmit}
className="bg-white p-8 rounded-3xl shadow-sm border border-sandy space-y-5"
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<div className="space-y-1.5">
<label className="text-[11px] font-black uppercase tracking-wider text-midnight/45">
{t('form.name')}
</label>
<input
required
type="text"
name="name"
value={formData.name}
onChange={handleChange}
className={inputClass}
/>
</div>
<div className="space-y-1.5">
<label className="text-[11px] font-black uppercase tracking-wider text-midnight/45">
{t('form.date')}
</label>
<input
required
type="date"
name="date"
value={formData.date}
onChange={handleChange}
className={inputClass}
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[11px] font-black uppercase tracking-wider text-midnight/45">
{t('form.guests')}
</label>
<input
required
type="number"
min="1"
name="guests"
value={formData.guests}
onChange={handleChange}
className={inputClass}
/>
</div>
<div className="space-y-1.5">
<label className="text-[11px] font-black uppercase tracking-wider text-midnight/45">
{t('form.message')}
</label>
<textarea
rows={3}
name="message"
value={formData.message}
onChange={handleChange}
className={inputClass + ' resize-none'}
/>
</div>
<button
type="submit"
className="w-full py-4 rounded-2xl font-black text-sm text-white flex items-center justify-center gap-2 hover:opacity-90 hover:scale-[1.01] transition-all shadow-md"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827)' }}
>
<Send size={15} />
{t('form.submit')}
</button>
</form>
</motion.div>
{/* Right: map */}
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="min-h-[420px] lg:min-h-[600px] w-full rounded-3xl overflow-hidden shadow-2xl"
>
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d12754.717654763133!2d28.3144!3d36.9507!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14bf76ccf312ba37%3A0xcdaaa97b102b4bb7!2sAkyaka%2C%20Ula%2FMu%C4%9Fla!5e0!3m2!1str!2str!4v1700000000000!5m2!1str!2str"
width="100%"
height="100%"
style={{ border: 0 }}
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
/>
</motion.div>
</div>
</div>
</section>
);
}
+116
View File
@@ -0,0 +1,116 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import { Utensils, Wine, Coffee, ArrowRight } from 'lucide-react';
const CARDS = [
{ id: 'seafood', icon: Utensils, accent: '#FF5A36' },
{ id: 'cocktails', icon: Wine, accent: '#00BFA5' },
{ id: 'breakfast', icon: Coffee, accent: '#FFA827' },
];
export default function Dining() {
const t = useTranslations('Dining');
const menuUrl = process.env.NEXT_PUBLIC_MENU_URL || '#';
return (
<section
id="dining"
className="pt-20 pb-28 text-white relative overflow-hidden"
style={{ background: '#080D18' }}
>
{/* Background blobs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div
className="absolute bottom-0 left-1/4 w-[500px] h-[500px] rounded-full opacity-20"
style={{ background: 'radial-gradient(circle, #FF5A36 0%, transparent 70%)' }}
/>
<div
className="absolute top-0 right-1/4 w-[400px] h-[400px] rounded-full opacity-15"
style={{ background: 'radial-gradient(circle, #00BFA5 0%, transparent 70%)' }}
/>
</div>
<div className="container mx-auto px-6 lg:px-12 relative z-10">
<div className="text-center max-w-3xl mx-auto mb-16">
<motion.span
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-xs tracking-[0.4em] uppercase font-black text-amber mb-4 block"
>
Yeme &amp; İçme
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="font-heading text-5xl md:text-6xl font-black mb-6"
>
<span className="gradient-text">{t('title')}</span>
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-lg text-white/50 mb-8"
>
{t('description')}
</motion.p>
<motion.a
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
href={menuUrl}
target={menuUrl !== '#' ? '_blank' : '_self'}
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-8 py-3.5 rounded-full font-black text-sm text-midnight transition-all hover:scale-105"
style={{ background: 'linear-gradient(135deg, #FFA827, #FFD166)' }}
>
{t('view_menu')}
<ArrowRight size={15} />
</motion.a>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{CARDS.map(({ id, icon: Icon, accent }, index) => (
<motion.div
key={id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 + 0.2 }}
className="group relative p-8 rounded-3xl border border-white/10 hover:border-white/25 transition-all duration-300"
style={{ background: 'rgba(255,255,255,0.04)' }}
>
{/* Icon */}
<div
className="w-14 h-14 rounded-2xl mb-6 flex items-center justify-center"
style={{ background: `${accent}18`, color: accent }}
>
<Icon size={26} />
</div>
<h3 className="font-heading text-2xl font-black mb-3 text-white">
{t(`cards.${id}.title`)}
</h3>
<p className="text-white/45 text-sm leading-relaxed">{t(`cards.${id}.desc`)}</p>
{/* Bottom accent line on hover */}
<div
className="absolute bottom-0 left-8 right-8 h-[1px] opacity-0 group-hover:opacity-100 transition-opacity duration-300 rounded-full"
style={{ background: `linear-gradient(90deg, transparent, ${accent}, transparent)` }}
/>
</motion.div>
))}
</div>
</div>
</section>
);
}
+366
View File
@@ -0,0 +1,366 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import { Calendar, Music, Disc3, CalendarDays, Zap } from 'lucide-react';
/* ── static data ───────────────────────────────── */
const WEEK_DAYS = ['Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'];
const ACTIVE_DAYS: Record<number, string> = {
3: '#FF5A36',
4: '#00E5FF',
5: '#00E5FF',
};
/* ── sub-components ────────────────────────────── */
function VinylDisc() {
return (
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 4, repeat: Infinity, ease: 'linear' }}
className="w-36 h-36 md:w-44 md:h-44 rounded-full relative flex-shrink-0"
style={{
background:
'repeating-conic-gradient(#0a0f1e 0deg 18deg, #141e30 18deg 36deg)',
boxShadow: '0 0 48px rgba(0,229,255,0.35), 0 0 80px rgba(0,229,255,0.12)',
}}
>
{/* Grooves */}
{[55, 45, 35].map((size) => (
<div
key={size}
className="absolute rounded-full border border-white/[0.04]"
style={{ inset: `${(100 - size) / 2}%` }}
/>
))}
{/* Label */}
<div
className="absolute inset-[35%] rounded-full flex items-center justify-center"
style={{ background: 'radial-gradient(circle, #00E5FF, #0084A8)' }}
>
<div className="w-2 h-2 rounded-full bg-white/80" />
</div>
</motion.div>
);
}
function EqBars({ color }: { color: string }) {
const heights = [0.4, 0.8, 0.5, 1, 0.65, 0.9, 0.45, 0.75, 0.55, 0.85];
return (
<div className="flex items-end gap-[3px] h-8">
{heights.map((h, i) => (
<motion.div
key={i}
className="w-1.5 rounded-full origin-bottom"
style={{ background: color, height: `${h * 100}%` }}
animate={{ scaleY: [h, h > 0.6 ? 0.2 : 1, h] }}
transition={{
duration: 0.5 + Math.random() * 0.4,
repeat: Infinity,
repeatType: 'mirror',
delay: i * 0.07,
ease: 'easeInOut',
}}
/>
))}
</div>
);
}
function WineGlassIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M8 22h8" />
<path d="M12 15v7" />
<path d="M12 15a5 5 0 0 0 5-5c0-2-.5-4-2-8H9c-1.5 4-2 6-2 8a5 5 0 0 0 5 5Z" />
<path d="M7 10h10" />
</svg>
);
}
/* ── main component ────────────────────────────── */
export default function Events() {
const t = useTranslations('Events');
return (
<section
id="events"
className="pt-28 pb-36 text-white relative overflow-hidden"
style={{ background: '#080D18' }}
>
{/* Aurora background blobs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div
className="absolute top-[-20%] left-[-10%] w-[600px] h-[600px] rounded-full opacity-[0.12]"
style={{ background: 'radial-gradient(circle, #00E5FF 0%, transparent 70%)' }}
/>
<div
className="absolute bottom-[-10%] right-[-5%] w-[500px] h-[500px] rounded-full opacity-[0.1]"
style={{ background: 'radial-gradient(circle, #FF5A36 0%, transparent 70%)' }}
/>
<div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] h-[700px] rounded-full opacity-[0.05]"
style={{ background: 'radial-gradient(circle, #FFA827 0%, transparent 70%)' }}
/>
</div>
{/* Fine dot grid */}
<div
className="absolute inset-0 opacity-[0.035] pointer-events-none"
style={{
backgroundImage: 'radial-gradient(circle, rgba(255,255,255,0.9) 1px, transparent 1px)',
backgroundSize: '36px 36px',
}}
/>
<div className="container mx-auto px-6 lg:px-12 relative z-10 space-y-10">
{/* ── Header ───────────────────────────────── */}
<div className="flex flex-col lg:flex-row lg:items-end justify-between gap-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
>
<div className="flex items-center gap-2 mb-4">
<Zap size={13} className="text-coral" />
<span className="text-xs tracking-[0.4em] uppercase font-black text-coral">
Live Events
</span>
</div>
<h2 className="font-heading text-5xl md:text-6xl font-black leading-tight">
{t('title')}
</h2>
<p className="text-white/45 mt-3 text-base max-w-md leading-relaxed">
{t('description')}
</p>
</motion.div>
<motion.button
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
className="self-start lg:self-auto flex items-center gap-2 px-7 py-3.5 rounded-full font-black text-sm text-white flex-shrink-0 transition-all hover:scale-105"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827)' }}
>
<CalendarDays size={15} />
{t('view_calendar')}
</motion.button>
</div>
{/* ── Weekly schedule bar ───────────────────── */}
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="flex items-center gap-2 md:gap-4 bg-white/[0.04] rounded-2xl px-6 py-4 border border-white/[0.07]"
>
{WEEK_DAYS.map((day, i) => {
const accent = ACTIVE_DAYS[i];
const isActive = !!accent;
return (
<div key={day} className="flex-1 flex flex-col items-center gap-2">
<span
className={`text-[10px] font-black uppercase tracking-wider ${
isActive ? 'text-white' : 'text-white/25'
}`}
>
{day}
</span>
<div
className={`w-1.5 h-1.5 rounded-full transition-all ${isActive ? 'scale-125' : ''}`}
style={{
background: isActive ? accent : 'rgba(255,255,255,0.12)',
boxShadow: isActive ? `0 0 8px ${accent}` : 'none',
}}
/>
</div>
);
})}
</motion.div>
{/* ── Featured Event: DJ Nights ────────────── */}
<motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7 }}
className="border-spin-cyan rounded-3xl p-px overflow-hidden"
>
<div
className="rounded-3xl p-8 md:p-10 flex flex-col md:flex-row items-center gap-8 overflow-hidden relative"
style={{ background: '#0C1828' }}
>
{/* Glow center */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
'radial-gradient(ellipse at 80% 50%, rgba(0,229,255,0.08) 0%, transparent 60%)',
}}
/>
{/* Left: text */}
<div className="flex-1 relative z-10">
<div className="flex items-center gap-3 mb-5">
<span
className="text-[10px] font-black uppercase tracking-[0.35em] px-3 py-1.5 rounded-full"
style={{ background: '#00E5FF1A', color: '#00E5FF' }}
>
EN POPÜLER
</span>
<div className="flex items-center gap-1.5 text-white/40 text-xs">
<Calendar size={11} />
<span>{t('cards.dj.time')}</span>
</div>
</div>
<h3 className="font-heading text-4xl md:text-5xl font-black mb-3 text-white leading-tight">
{t('cards.dj.title')}
</h3>
<div className="flex items-center gap-3 mb-7">
<Disc3 size={18} style={{ color: '#00E5FF' }} />
<span className="text-white/50 text-sm">Open-air · Beach Stage</span>
</div>
<EqBars color="#00E5FF" />
</div>
{/* Right: vinyl */}
<div className="relative z-10 flex-shrink-0">
<VinylDisc />
</div>
</div>
</motion.div>
{/* ── Secondary events: 2-col grid ─────────── */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
{/* Rakı Geceleri */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="border-spin-coral rounded-3xl p-px group cursor-pointer"
>
<div
className="rounded-3xl p-7 h-full flex flex-col gap-5 relative overflow-hidden transition-colors duration-300 group-hover:bg-[#110a0a]"
style={{ background: '#0C0808' }}
>
<div
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-400 pointer-events-none"
style={{
background:
'radial-gradient(ellipse at 20% 50%, rgba(255,90,54,0.1) 0%, transparent 65%)',
}}
/>
<div className="flex items-start justify-between">
<div
className="w-12 h-12 rounded-2xl flex items-center justify-center"
style={{ background: '#FF5A361A', color: '#FF5A36' }}
>
<WineGlassIcon />
</div>
<span
className="text-[10px] font-black uppercase tracking-wider px-2.5 py-1 rounded-full"
style={{ background: '#FF5A361A', color: '#FF5A36' }}
>
PER
</span>
</div>
<div>
<h3 className="font-heading text-2xl font-black text-white mb-2">
{t('cards.raki.title')}
</h3>
<div className="flex items-center gap-1.5 text-sm text-white/40">
<Calendar size={12} />
<span>{t('cards.raki.time')}</span>
</div>
</div>
<div className="mt-auto opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<EqBars color="#FF5A36" />
</div>
</div>
</motion.div>
{/* Canlı Müzik */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.2 }}
className="border-spin-amber rounded-3xl p-px group cursor-pointer"
>
<div
className="rounded-3xl p-7 h-full flex flex-col gap-5 relative overflow-hidden transition-colors duration-300 group-hover:bg-[#110d04]"
style={{ background: '#0C0A04' }}
>
<div
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-400 pointer-events-none"
style={{
background:
'radial-gradient(ellipse at 80% 50%, rgba(255,168,39,0.1) 0%, transparent 65%)',
}}
/>
<div className="flex items-start justify-between">
<div
className="w-12 h-12 rounded-2xl flex items-center justify-center"
style={{ background: '#FFA8271A', color: '#FFA827' }}
>
<Music size={22} />
</div>
<span
className="text-[10px] font-black uppercase tracking-wider px-2.5 py-1 rounded-full"
style={{ background: '#FFA8271A', color: '#FFA827' }}
>
ÖZEL
</span>
</div>
<div>
<h3 className="font-heading text-2xl font-black text-white mb-2">
{t('cards.live_music.title')}
</h3>
<div className="flex items-center gap-1.5 text-sm text-white/40">
<Calendar size={12} />
<span>{t('cards.live_music.time')}</span>
</div>
</div>
<div className="mt-auto opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<EqBars color="#FFA827" />
</div>
</div>
</motion.div>
</div>
</div>
{/* Wave → sand */}
<div className="absolute bottom-0 left-0 w-full leading-none pointer-events-none">
<svg viewBox="0 0 1440 64" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" className="w-full h-16">
<path d="M0,20 C360,64 720,0 1080,40 C1260,56 1380,10 1440,20 L1440,64 L0,64 Z" fill="#FFF8EE" />
</svg>
</div>
</section>
);
}
+94
View File
@@ -0,0 +1,94 @@
"use client";
import { useTranslations } from 'next-intl';
import { Instagram } from '@/components/InstagramIcon';
import { Link, usePathname } from '@/i18n/routing';
export default function Footer() {
const t = useTranslations('Footer');
const tNav = useTranslations('Navbar');
const pathname = usePathname();
const navLinks = [
{ href: pathname === '/' ? '#about' : '/#about', label: tNav('about') },
{ href: '/accommodation', label: tNav('accommodation') },
{ href: pathname === '/' ? '#beach' : '/#beach', label: tNav('beach') },
{ href: pathname === '/' ? '#dining' : '/#dining', label: tNav('dining') },
{ href: pathname === '/' ? '#events' : '/#events', label: tNav('events') },
{ href: pathname === '/' ? '#gallery' : '/#gallery', label: tNav('gallery') },
{ href: pathname === '/' ? '#contact' : '/#contact', label: tNav('contact') },
];
return (
<footer
className="text-white py-16 relative overflow-hidden"
style={{ background: '#080D18' }}
>
{/* Rainbow top border */}
<div
className="absolute top-0 left-0 right-0 h-[1px]"
style={{
background:
'linear-gradient(90deg, transparent 0%, #FF5A36 25%, #FFA827 50%, #00BFA5 75%, transparent 100%)',
}}
/>
<div className="container mx-auto px-6 lg:px-12">
<div className="flex flex-col md:flex-row justify-between items-start gap-12 mb-12">
{/* Brand */}
<div>
<Link href="/" className="inline-flex flex-col mb-3">
<span className="font-heading text-5xl font-black uppercase tracking-tighter gradient-text-warm">
Kozmos
</span>
<span className="text-[10px] tracking-[0.5em] font-body uppercase text-white/30 mt-0.5">
Beach &amp; More
</span>
</Link>
<p className="text-white/35 text-sm mt-1">{t('tagline')}</p>
</div>
{/* Nav links */}
<div className="flex flex-wrap gap-x-7 gap-y-3">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-sm text-white/40 hover:text-white transition-colors"
>
{link.label}
</Link>
))}
</div>
{/* Social */}
<a
href="https://www.instagram.com/kozmosbeach/"
target="_blank"
rel="noreferrer"
className="w-12 h-12 rounded-2xl border border-white/10 flex items-center justify-center text-white/40 hover:border-coral hover:text-coral transition-all"
>
<Instagram size={19} />
</a>
</div>
<div className="pt-8 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-white/25">
<div>{t('rights')}</div>
<div>
Created by{' '}
<a
href="https://ayris.tech"
target="_blank"
rel="noopener noreferrer"
className="text-white/40 hover:text-coral transition-colors"
>
ayris.tech
</a>
</div>
</div>
</div>
</footer>
);
}
+162
View File
@@ -0,0 +1,162 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion, AnimatePresence } from 'framer-motion';
import { Instagram } from '@/components/InstagramIcon';
import { useState, useEffect } from 'react';
import { X, ChevronLeft, ChevronRight } from 'lucide-react';
export default function Gallery() {
const t = useTranslations('Gallery');
const images = Array.from({ length: 12 }).map((_, i) => ({
id: i,
src: `https://picsum.photos/600/${400 + (i % 5) * 50}?random=${30 + i}`,
alt: `Gallery ${i + 1}`,
}));
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
// Close lightbox on escape key
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setSelectedIndex(null);
if (e.key === 'ArrowRight' && selectedIndex !== null) {
setSelectedIndex((prev) => (prev !== null ? (prev + 1) % images.length : null));
}
if (e.key === 'ArrowLeft' && selectedIndex !== null) {
setSelectedIndex((prev) => (prev !== null ? (prev - 1 + images.length) % images.length : null));
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [selectedIndex, images.length]);
return (
<section id="gallery" className="py-24 bg-white text-midnight">
<div className="container mx-auto px-6 lg:px-12">
{/* Header */}
<div className="flex flex-col md:flex-row justify-between items-end mb-10">
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
>
<span className="text-xs tracking-[0.4em] uppercase font-black text-coral mb-3 block">
Fotoğraflar
</span>
<h2 className="font-heading text-5xl md:text-6xl font-black text-midnight">
{t('title')}
</h2>
</motion.div>
<motion.a
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
href="https://www.instagram.com/kozmosbeach/"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-5 py-2.5 rounded-full border border-coral/30 text-coral font-bold text-sm hover:bg-coral hover:text-white transition-all"
>
<Instagram size={15} />
{t('follow_instagram')}
</motion.a>
</div>
{/* Masonry grid */}
<div className="columns-1 sm:columns-2 md:columns-3 lg:columns-4 gap-3 space-y-3">
{images.map((img, index) => (
<motion.div
key={img.id}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: (index % 4) * 0.07 }}
onClick={() => setSelectedIndex(index)}
className="relative rounded-2xl overflow-hidden group break-inside-avoid cursor-pointer"
>
<img
src={img.src}
alt={img.alt}
className="w-full h-auto object-cover transition-transform duration-700 group-hover:scale-105"
loading="lazy"
/>
{/* Coral gradient overlay */}
<div
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
style={{
background:
'linear-gradient(135deg, rgba(255,90,54,0.55), rgba(0,191,165,0.4))',
}}
/>
{/* Plus icon */}
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<span className="text-white text-4xl font-thin select-none">+</span>
</div>
</motion.div>
))}
</div>
</div>
{/* Lightbox Overlay */}
<AnimatePresence>
{selectedIndex !== null && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-[100] flex items-center justify-center bg-midnight/95 backdrop-blur-md"
>
{/* Close Button */}
<button
onClick={() => setSelectedIndex(null)}
className="absolute top-6 right-6 z-[110] text-white/70 hover:text-white transition-colors"
>
<X size={36} />
</button>
{/* Left/Right Navigation */}
<button
onClick={(e) => {
e.stopPropagation();
setSelectedIndex((selectedIndex - 1 + images.length) % images.length);
}}
className="absolute left-4 md:left-10 z-[110] p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-all"
>
<ChevronLeft size={30} />
</button>
<button
onClick={(e) => {
e.stopPropagation();
setSelectedIndex((selectedIndex + 1) % images.length);
}}
className="absolute right-4 md:right-10 z-[110] p-3 rounded-full bg-white/10 text-white hover:bg-white/20 transition-all"
>
<ChevronRight size={30} />
</button>
{/* Image */}
<motion.div
key={selectedIndex}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="relative max-w-5xl w-full h-[80vh] px-4 md:px-24"
onClick={(e) => e.stopPropagation()}
>
<img
src={images[selectedIndex].src}
alt={images[selectedIndex].alt}
className="w-full h-full object-contain drop-shadow-2xl"
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</section>
);
}
+156
View File
@@ -0,0 +1,156 @@
"use client";
import { useTranslations } from 'next-intl';
import { motion } from 'framer-motion';
import { ChevronDown, MapPin } from 'lucide-react';
export default function Hero() {
const t = useTranslations('Hero');
const openWhatsApp = () => {
const phone = process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || '905000000000';
window.open(`https://wa.me/${phone}`, '_blank');
};
return (
<section
id="hero"
className="relative w-full h-screen flex items-center justify-center overflow-hidden"
style={{ background: '#080D18' }}
>
{/* ── Ambient gradient blobs ───────────────────── */}
<div className="absolute inset-0 overflow-hidden">
<motion.div
animate={{ x: [0, 60, -20, 0], y: [0, -50, 30, 0], scale: [1, 1.15, 0.95, 1] }}
transition={{ duration: 28, repeat: Infinity, ease: 'easeInOut' }}
className="absolute -top-56 -left-56 w-[700px] h-[700px] rounded-full"
style={{ background: 'radial-gradient(circle, rgba(255,90,54,0.28) 0%, transparent 65%)' }}
/>
<motion.div
animate={{ x: [0, -80, 50, 0], y: [0, 70, -30, 0], scale: [1, 0.85, 1.2, 1] }}
transition={{ duration: 32, repeat: Infinity, ease: 'easeInOut', delay: 6 }}
className="absolute -bottom-40 -right-40 w-[600px] h-[600px] rounded-full"
style={{ background: 'radial-gradient(circle, rgba(0,191,165,0.22) 0%, transparent 65%)' }}
/>
<motion.div
animate={{ x: [0, 100, -60, 0], y: [0, -80, 50, 0], scale: [1, 1.3, 0.85, 1] }}
transition={{ duration: 22, repeat: Infinity, ease: 'easeInOut', delay: 12 }}
className="absolute top-1/2 right-1/3 w-[400px] h-[400px] rounded-full"
style={{ background: 'radial-gradient(circle, rgba(255,168,39,0.18) 0%, transparent 65%)' }}
/>
</div>
{/* Grain overlay */}
<div
className="absolute inset-0 z-[1] opacity-[0.035] pointer-events-none"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
}}
/>
{/* ── Content ──────────────────────────────────── */}
<div className="relative z-10 container mx-auto px-6 flex flex-col items-center text-center">
{/* Location */}
<motion.p
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7 }}
className="text-[11px] tracking-[0.45em] uppercase text-coral font-bold mb-10"
>
Akyaka &nbsp;·&nbsp; Gökova Körfezi
</motion.p>
{/* Brand name — static gradient, no shimmer */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.9, delay: 0.15 }}
className="font-heading font-black uppercase tracking-tight leading-none gradient-text-warm mb-4"
style={{ fontSize: 'clamp(3rem, 8vw, 6.5rem)' }}
>
KOZMOS
</motion.h1>
{/* Thin divider */}
<motion.div
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.8, delay: 0.35 }}
className="w-16 h-px mb-5"
style={{ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent)' }}
/>
{/* Subtitle */}
<motion.p
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.45 }}
className="text-sm md:text-base tracking-[0.55em] uppercase text-white/40 font-medium mb-6"
>
Beach &amp; More
</motion.p>
{/* Tagline */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.6 }}
className="text-white/55 text-sm md:text-base max-w-xs leading-relaxed mb-12"
>
{t('tagline')}
</motion.p>
{/* CTAs */}
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.75 }}
className="flex flex-col sm:flex-row gap-3"
>
<button
onClick={openWhatsApp}
className="px-8 py-3.5 rounded-full font-bold text-sm tracking-wider text-white transition-all duration-300 hover:scale-105 hover:shadow-[0_0_32px_rgba(255,90,54,0.45)]"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827)' }}
>
{t('cta_reservation')}
</button>
<a
href="#contact"
className="flex items-center justify-center gap-2 px-7 py-3.5 rounded-full text-sm tracking-wider font-medium text-white/70 border border-white/15 hover:border-white/35 hover:text-white transition-all duration-300"
>
<MapPin size={14} />
{t('cta_location')}
</a>
</motion.div>
</div>
{/* ── Wave divider ─────────────────────────────── */}
<div className="absolute bottom-0 left-0 w-full z-10 leading-none">
<svg
viewBox="0 0 1440 72"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="none"
className="w-full h-[72px]"
>
<path
d="M0,36 C240,72 480,0 720,36 C960,72 1200,0 1440,36 L1440,72 L0,72 Z"
fill="#FFF8EE"
/>
</svg>
</div>
{/* Scroll cue */}
<motion.div
className="absolute bottom-10 left-1/2 -translate-x-1/2 z-20 text-white/35"
animate={{ y: [0, 7, 0] }}
transition={{ repeat: Infinity, duration: 1.8 }}
>
<a href="#about" aria-label="Scroll down">
<ChevronDown size={24} />
</a>
</motion.div>
</section>
);
}
+22
View File
@@ -0,0 +1,22 @@
import React from 'react';
export function Instagram({ size = 24, className = "" }: { size?: number, className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<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>
);
}
+173
View File
@@ -0,0 +1,173 @@
"use client";
import { useTranslations } from 'next-intl';
import { Link, usePathname, useRouter } from '@/i18n/routing';
import { useState, useEffect } from 'react';
import { Menu, X } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
export default function Navbar({ locale }: { locale: string }) {
const t = useTranslations('Navbar');
const pathname = usePathname();
const router = useRouter();
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 50);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const switchLocale = (newLocale: string) => {
router.replace(pathname, { locale: newLocale });
};
const navLinks = [
{ href: pathname === '/' ? '#about' : '/#about', label: t('about') },
{ href: '/accommodation', label: t('accommodation') },
{ href: pathname === '/' ? '#beach' : '/#beach', label: t('beach') },
{ href: pathname === '/' ? '#dining' : '/#dining', label: t('dining') },
{ href: pathname === '/' ? '#events' : '/#events', label: t('events') },
{ href: pathname === '/' ? '#gallery' : '/#gallery', label: t('gallery') },
{ href: pathname === '/' ? '#contact' : '/#contact', label: t('contact') },
];
return (
<nav
className={`fixed top-0 left-0 w-full z-50 transition-all duration-300 ${
isScrolled
? 'bg-white/90 backdrop-blur-lg shadow-sm py-4 border-b border-sandy/40'
: 'bg-transparent py-6'
}`}
>
<div className="container mx-auto px-6 lg:px-12 flex justify-between items-center">
{/* Logo */}
<Link href="/" className="flex flex-col items-start">
<span
className={`font-heading text-2xl font-black uppercase tracking-tighter ${
isScrolled ? 'text-midnight' : 'gradient-text-warm'
}`}
>
Kozmos
</span>
<span
className={`text-[10px] tracking-[0.4em] font-body uppercase ${
isScrolled ? 'text-coral' : 'text-white/40'
}`}
>
Beach &amp; More
</span>
</Link>
{/* Desktop Nav */}
<div className="hidden lg:flex items-center gap-8">
<div className="flex gap-6">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className={`text-sm font-medium tracking-wide transition-colors hover:text-coral ${
isScrolled ? 'text-midnight/70' : 'text-white/80'
}`}
>
{link.label}
</Link>
))}
</div>
<div
className={`flex items-center gap-3 border-l pl-5 ${
isScrolled ? 'border-sandy' : 'border-white/20'
}`}
>
{(['tr', 'en'] as const).map((l) => (
<button
key={l}
onClick={() => switchLocale(l)}
className={`text-[11px] font-black uppercase tracking-wider transition-colors ${
locale === l
? 'text-coral'
: isScrolled
? 'text-midnight/40'
: 'text-white/40'
}`}
>
{l}
</button>
))}
</div>
<a
href="#contact"
className="px-5 py-2.5 rounded-full text-xs font-black text-white tracking-wider transition-all hover:scale-105 hover:shadow-[0_0_24px_rgba(255,90,54,0.45)]"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827)' }}
>
{t('reservation')}
</a>
</div>
{/* Mobile toggle */}
<button
className="lg:hidden"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label="Toggle menu"
>
{isMobileMenuOpen ? (
<X className={isScrolled ? 'text-midnight' : 'text-white'} size={26} />
) : (
<Menu className={isScrolled ? 'text-midnight' : 'text-white'} size={26} />
)}
</button>
</div>
{/* Mobile menu */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="lg:hidden absolute top-full left-0 w-full bg-white/96 backdrop-blur-lg shadow-xl border-t border-sandy/30"
>
<div className="flex flex-col py-6 px-8 gap-3">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-midnight text-base font-heading font-semibold py-2 border-b border-sandy/20 hover:text-coral transition-colors"
onClick={() => setIsMobileMenuOpen(false)}
>
{link.label}
</Link>
))}
<div className="flex gap-4 pt-3">
{(['tr', 'en'] as const).map((l) => (
<button
key={l}
onClick={() => switchLocale(l)}
className={`text-sm font-black uppercase ${locale === l ? 'text-coral' : 'text-midnight/40'}`}
>
{l}
</button>
))}
</div>
<a
href="#contact"
className="mt-2 text-center py-3.5 rounded-full text-sm font-black text-white"
style={{ background: 'linear-gradient(135deg, #FF5A36, #FFA827)' }}
onClick={() => setIsMobileMenuOpen(false)}
>
{t('reservation')}
</a>
</div>
</motion.div>
)}
</AnimatePresence>
</nav>
);
}
+21
View File
@@ -0,0 +1,21 @@
"use client";
import { MessageCircle } from 'lucide-react';
export default function WhatsAppButton() {
const openWhatsApp = () => {
const phone = process.env.NEXT_PUBLIC_WHATSAPP_NUMBER || '905000000000';
window.open(`https://wa.me/${phone}`, '_blank');
};
return (
<button
onClick={openWhatsApp}
className="fixed bottom-6 right-6 z-50 bg-[#25D366] text-white p-4 rounded-full shadow-[0_4px_14px_0_rgba(37,211,102,0.39)] hover:shadow-[0_6px_20px_rgba(37,211,102,0.23)] hover:scale-105 transition-all duration-300 flex items-center justify-center group"
aria-label="WhatsApp ile iletişime geçin"
>
<div className="absolute inset-0 rounded-full bg-[#25D366] animate-ping opacity-20"></div>
<MessageCircle size={28} className="relative z-10" />
</button>
);
}
+16
View File
@@ -0,0 +1,16 @@
import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default
};
});
+10
View File
@@ -0,0 +1,10 @@
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';
export const routing = defineRouting({
locales: ['tr', 'en'],
defaultLocale: 'tr',
localePrefix: 'as-needed'
});
export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);
+114
View File
@@ -0,0 +1,114 @@
{
"Navbar": {
"about": "What is Kozmos?",
"accommodation": "Accommodation",
"beach": "Beach",
"dining": "Dining",
"events": "Events",
"gallery": "Gallery",
"contact": "Contact",
"reservation": "Reservation"
},
"Hero": {
"tagline": "Where the Sea Meets the Forest",
"cta_reservation": "Make a Reservation",
"cta_location": "View Location"
},
"About": {
"title": "What is Kozmos?",
"description": "A unique experience in the heart of nature, where the forest meets the sea in a quiet bay of the Gulf of Gökova. Kozmos is a special living space where you can enjoy the sea and sun on the beach during the day, and satisfy your soul with music and entertainment at night.",
"card1_title": "Beach & Sea",
"card1_desc": "A peaceful day in the cool waters of the Aegean.",
"card2_title": "Nature & Serenity",
"card2_desc": "The meeting point of the green of the forest and the blue of the sea.",
"card3_title": "Music & Nightlife",
"card3_desc": "Rhythm and entertainment starting as the sun sets."
},
"Accommodation": {
"title": "Accommodation",
"description": "Wake up in our bungalows designed without compromising your comfort, surrounded by nature.",
"capacity": "Capacity:",
"person": "Person",
"price_per_night": "Price per night:",
"book_whatsapp": "Book via WhatsApp",
"rooms": {
"standard": {
"name": "Standard Bungalow",
"features": ["Nature View", "Air Conditioning", "Mini Bar", "Private Bathroom"]
},
"sea_view": {
"name": "Sea View Bungalow",
"features": ["Sea View", "Spacious Patio", "Air Conditioning", "Mini Bar", "Private Bathroom"]
},
"garden_suite": {
"name": "Garden Suite",
"features": ["Private Garden", "Spacious Living Area", "Air Conditioning", "Mini Bar", "Private Bathroom"]
}
}
},
"Beach": {
"title": "Beach & Loungers",
"description": "Relax under the shades, facing the turquoise waters. Comfortable loungers and cabanas are available for our daily guests.",
"hours": "Working Hours:",
"price": "Lounger Fee:",
"reservation_info": "Reservations are recommended for daily entry.",
"book_lounger": "Book a Lounger"
},
"Dining": {
"title": "Dining",
"description": "Crown the freshest flavors of Aegean and Mediterranean cuisine with our signature cocktails on our thatched roof terrace.",
"view_menu": "View Menu",
"cards": {
"seafood": {
"title": "Fresh Seafood",
"desc": "Daily and local seafood."
},
"cocktails": {
"title": "Signature Cocktails",
"desc": "Signature flavors from our award-winning mixologists."
},
"breakfast": {
"title": "Mixed Breakfast",
"desc": "A fresh start to the day with natural products."
}
}
},
"Events": {
"title": "Events",
"description": "Entertainment never ends at Kozmos. Catch the rhythm with our weekly events.",
"view_calendar": "Event Calendar",
"cards": {
"raki": {
"title": "Thursday Rakı Nights",
"time": "Every Thursday, 20:00"
},
"dj": {
"title": "DJ Nights",
"time": "Friday & Saturday, 22:00"
},
"live_music": {
"title": "Live Music",
"time": "Special Days & Events"
}
}
},
"Gallery": {
"title": "Gallery",
"follow_instagram": "Follow on Instagram"
},
"Contact": {
"title": "Contact & Location",
"address": "Akyaka, Gulf of Gökova, Muğla",
"form": {
"name": "Full Name",
"date": "Date",
"guests": "Number of Guests",
"message": "Message",
"submit": "Send via WhatsApp"
}
},
"Footer": {
"tagline": "Where the Sea Meets the Forest",
"rights": "© 2025 Kozmos Beach & More. All rights reserved."
}
}
+114
View File
@@ -0,0 +1,114 @@
{
"Navbar": {
"about": "Kozmos Nedir?",
"accommodation": "Konaklama",
"beach": "Plaj",
"dining": "Yeme & İçme",
"events": "Etkinlikler",
"gallery": "Galeri",
"contact": "İletişim",
"reservation": "Rezervasyon"
},
"Hero": {
"tagline": "Denizin Ormana Kıyısında",
"cta_reservation": "Rezervasyon Yap",
"cta_location": "Konumu Gör"
},
"About": {
"title": "Kozmos Nedir?",
"description": "Gökova Körfezi'nin sakin bir koyunda, ormanın denizle buluştuğu noktada, doğanın kalbinde eşsiz bir deneyim. Kozmos; gündüz plajda denizin ve güneşin tadını çıkarabileceğiniz, gece ise müzik ve eğlenceyle ruhunuzu doyurabileceğiniz özel bir yaşam alanıdır.",
"card1_title": "Plaj & Deniz",
"card1_desc": "Ege'nin serin sularında huzurlu bir gün.",
"card2_title": "Doğa & Huzur",
"card2_desc": "Ormanın yeşiliyle denizin mavisinin buluştuğu nokta.",
"card3_title": "Müzik & Gece",
"card3_desc": "Güneş batarken başlayan ritim ve eğlence."
},
"Accommodation": {
"title": "Konaklama",
"description": "Doğanın içinde, konforunuzdan ödün vermeden tasarlanmış bungalowlarımızda uyanın.",
"capacity": "Kapasite:",
"person": "Kişi",
"price_per_night": "Gecelik Fiyat:",
"book_whatsapp": "WhatsApp ile Rezervasyon",
"rooms": {
"standard": {
"name": "Standart Bungalow",
"features": ["Doğa Manzaralı", "Klima", "Mini Bar", "Özel Banyo"]
},
"sea_view": {
"name": "Deniz Manzaralı Bungalow",
"features": ["Deniz Manzaralı", "Geniş Veranda", "Klima", "Mini Bar", "Özel Banyo"]
},
"garden_suite": {
"name": "Bahçe Suit",
"features": ["Özel Bahçe", "Geniş Yaşam Alanı", "Klima", "Mini Bar", "Özel Banyo"]
}
}
},
"Beach": {
"title": "Plaj & Şezlong",
"description": "Turkuaz sulara karşı, gölgeliklerin altında dinlenin. Günübirlik misafirlerimiz için konforlu şezlong ve cabanalarımız mevcuttur.",
"hours": "Çalışma Saatleri:",
"price": "Şezlong Ücreti:",
"reservation_info": "Günübirlik girişler için rezervasyon önerilir.",
"book_lounger": "Şezlong Rezervasyonu"
},
"Dining": {
"title": "Yeme & İçme",
"description": "Hasır çatılı terasımızda, Ege ve Akdeniz mutfağının en taze lezzetlerini imza kokteyllerimizle taçlandırın.",
"view_menu": "Menüyü Gör",
"cards": {
"seafood": {
"title": "Taze Deniz Ürünleri",
"desc": "Günlük ve yöresel deniz mahsulleri."
},
"cocktails": {
"title": "Özel Kokteyller",
"desc": "Ödüllü miksologlarımızdan imza lezzetler."
},
"breakfast": {
"title": "Serpme Kahvaltı",
"desc": "Doğal ürünlerle güne taze bir başlangıç."
}
}
},
"Events": {
"title": "Etkinlikler",
"description": "Kozmos'ta eğlence hiç bitmez. Haftalık etkinliklerimizle ritmi yakalayın.",
"view_calendar": "Etkinlik Takvimi",
"cards": {
"raki": {
"title": "Perşembe Rakı Geceleri",
"time": "Her Perşembe, 20:00"
},
"dj": {
"title": "DJ Geceleri",
"time": "Cuma & Cumartesi, 22:00"
},
"live_music": {
"title": "Canlı Müzik",
"time": "Özel Günler & Etkinlikler"
}
}
},
"Gallery": {
"title": "Galeri",
"follow_instagram": "Instagram'da Takip Et"
},
"Contact": {
"title": "İletişim & Konum",
"address": "Akyaka, Gökova Körfezi, Muğla",
"form": {
"name": "Ad Soyad",
"date": "Tarih",
"guests": "Kişi Sayısı",
"message": "Mesaj",
"submit": "WhatsApp ile Gönder"
}
},
"Footer": {
"tagline": "Denizin Ormana Kıyısında",
"rights": "© 2025 Kozmos Beach & More. Tüm hakları saklıdır."
}
}
+17
View File
@@ -0,0 +1,17 @@
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// A list of all locales that are supported
locales: ['tr', 'en'],
// Used when no locale matches
defaultLocale: 'tr',
localePrefix: 'as-needed'
});
export const config = {
// Match all pathnames except for
// - … if they start with `/api`, `/_next` or `/_vercel`
// - … the ones containing a dot (e.g. `favicon.ico`)
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
+12 -2
View File
@@ -1,7 +1,17 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
const nextConfig: NextConfig = {
/* config options here */
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'picsum.photos',
},
],
},
};
export default nextConfig;
export default withNextIntl(nextConfig);
+806 -4
View File
@@ -8,9 +8,14 @@
"name": "kozmosweb",
"version": "0.1.0",
"dependencies": {
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
"lucide-react": "^1.17.0",
"next": "16.2.7",
"next-intl": "^4.13.0",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"tailwind-merge": "^3.6.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -453,6 +458,36 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@formatjs/fast-memoize": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz",
"integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==",
"license": "MIT"
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "3.5.10",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.10.tgz",
"integrity": "sha512-XeJihYLy1lCe19xfK1KWKG/betBOK2rB0luL8lSkjfvJj0zP+LTJvkC+RKd0jsFI8mWxN71LrarHSrEXE8xxOQ==",
"license": "MIT",
"dependencies": {
"@formatjs/icu-skeleton-parser": "2.1.9"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.9.tgz",
"integrity": "sha512-rsxswgHMfU1zUgB2byc08fesf83wLGjFnzLCEtuf00mx2doiqc6pYrf67raI37XqdRcGUviQepk2UKGqpng74Q==",
"license": "MIT"
},
"node_modules/@formatjs/intl-localematcher": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.9.tgz",
"integrity": "sha512-GmB0F/gYh4Hdl4rLWjgDsgT+x4pB54fkJeRh8kAZ4XFzKeCK8dGs+SBJWXO42QZtOUni+IDWKNuCw6wiL4lTvw==",
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "3.1.5"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -1306,6 +1341,331 @@
"node": ">=12.4.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1313,6 +1673,228 @@
"dev": true,
"license": "MIT"
},
"node_modules/@schummar/icu-type-parser": {
"version": "1.21.5",
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
"license": "MIT"
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.40.tgz",
"integrity": "sha512-PaYyclfmQ++77D8ityYvmmVzHv9aG8ROwt2GfG6/ccloy4Hgf80qtOnzb9VYvPsUT7Ty1uhuDRhv3XYpf62qhQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.40.tgz",
"integrity": "sha512-HbbPzvfLBUXjIB1Ezks+//lNUjmLjfyd63XSwprJgrZaXYdm70kohXPJUWdqKZozolFxbPaO+xtBaiUp6BoueA==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.40.tgz",
"integrity": "sha512-SlRZsCjOCPR2LvFs0Ri/Xrx/5o5TCt8vl4gW6mX1hEZOG0a625RxzRHpHdAQNGykmAN/7IeaFAJG+QnNmxlHcA==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.40.tgz",
"integrity": "sha512-Q8byxJt2fh8CR3EUX6snBpy47AoBVm+In/+Z3rjDHMjC38ZvR9/gtUUNCT0tfrn4EdVsO8/QPi59nxrxvqxvBQ==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.40.tgz",
"integrity": "sha512-4z0MgHU+7M0pZDqBN1El7mFXDI1SBwinfcUkAyA4v8QrhOIUOZltySt2aStQLZGrdXVXM4Y4ylfiTC04ED+MoQ==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-ppc64-gnu": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.40.tgz",
"integrity": "sha512-fLI4iUgeSZu0eRWUXwe6YzPFx9gHbFiPkl8Rp3mJfP8OpNR3nTQCGPvHdDh9xniW7mVvgMY4ni7A4VzqI1KrpA==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-s390x-gnu": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.40.tgz",
"integrity": "sha512-YqeKMAb7d4nQSGMJQ454IlaCENpzcDqhvBE9+CPfdnYpnUXxd+BSrB6Xk0YjW8UyoEhUj4p6quATCxbsp6J3jg==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.40.tgz",
"integrity": "sha512-7HOuS1iGcme/j/TuL1TfmmLGiMQrjv/GmjyZeydl00FKPtpGXEldwqfI56xgd1YzrzoB2svWjxbGGyQ0TEASxg==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.40.tgz",
"integrity": "sha512-h4kZYHc7dpc9P9u4brRJaS8Pl7tPVHAeiLSzw7T5RfIJgAoSdaCMKzI/2Uay9gFhaw8uyCDl0L5q37r0EpAfIA==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.40.tgz",
"integrity": "sha512-+mQgKZXSj6mV38Zh05QaxSjUDmGP/R2JWlXZTDLSPkDzHU6p3GxN9eeSf5dfyDVU86946fmCvSzyl/ucImx8+A==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.40.tgz",
"integrity": "sha512-yvwdPLGd25mcj/mNatjNQ0lZujtQD6psH3v9PNmMb+fSzjbNG8KIDxjFWrcV+fsFVLOkyOmdJsFmX7NAFjVyPw==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.40.tgz",
"integrity": "sha512-OXtKsLU1bVtInzzDEAY2sYiF/rl4tvAnLLLpuMp3HzAOQZ5A+i69AKDhA1YLQTaMAqO3vzyYNVAYVRMPtSYD4w==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1322,6 +1904,15 @@
"tslib": "^2.8.0"
}
},
"node_modules/@swc/types": {
"version": "0.1.26",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz",
"integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==",
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
@@ -2771,6 +3362,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2953,7 +3553,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -3762,6 +4361,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",
@@ -4067,6 +4693,21 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/icu-minify": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.13.0.tgz",
"integrity": "sha512-SIFMeUHZJjzS5RvIGvybKvWoHjDm9cGVEs2EpJ8PmywOdJLWyblPm7TdPLLoUtkJtwQD7iGhl2WMptZ+N0on+w==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"license": "MIT",
"dependencies": {
"@formatjs/icu-messageformat-parser": "^3.4.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4119,6 +4760,16 @@
"node": ">= 0.4"
}
},
"node_modules/intl-messageformat": {
"version": "11.2.7",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.7.tgz",
"integrity": "sha512-+q6Ktg119nULZEpZ8YTuGOst9MyEzFtjD63FTGBlN1mLz0Z/MOUYDIvnpVKwq17eezIEh+cfJIebfJoCetpiNw==",
"license": "BSD-3-Clause",
"dependencies": {
"@formatjs/fast-memoize": "3.1.5",
"@formatjs/icu-messageformat-parser": "3.5.10"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4281,7 +4932,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -4327,7 +4977,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -5032,6 +5681,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 +5757,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",
@@ -5147,6 +5820,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/next": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.7.tgz",
@@ -5200,6 +5882,83 @@
}
}
},
"node_modules/next-intl": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.13.0.tgz",
"integrity": "sha512-OvNq2v5XLx4EkQOsAhVE9g+6zdb83XHusADCXXtIW4LILYnjEVaeINdr1lkVWKSjzwNUiMSlH5N4K0OQTRiv6A==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"license": "MIT",
"dependencies": {
"@formatjs/intl-localematcher": "^0.8.1",
"@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2",
"icu-minify": "^4.13.0",
"negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "^4.13.0",
"po-parser": "^2.1.1",
"use-intl": "^4.13.0"
},
"peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/next-intl-swc-plugin-extractor": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.13.0.tgz",
"integrity": "sha512-6S/fJI0KXvLCL8nhBo9P8eGaJPzmwJBTCzX0NaUIj0VyU8U89d//T+vjMLdNIXl5MlLaYH7B9MbAjb8Mvu+tqQ==",
"license": "MIT"
},
"node_modules/next-intl/node_modules/@swc/core": {
"version": "1.15.40",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.40.tgz",
"integrity": "sha512-2kwzJikRvgtNAG7MwVZY2vEzZjTxKIq5jXOihuSV/8U+Hej8Va22t65aKnJZs3P+NwojZvR8Mf8kyM7O+V8sQg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.26"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.40",
"@swc/core-darwin-x64": "1.15.40",
"@swc/core-linux-arm-gnueabihf": "1.15.40",
"@swc/core-linux-arm64-gnu": "1.15.40",
"@swc/core-linux-arm64-musl": "1.15.40",
"@swc/core-linux-ppc64-gnu": "1.15.40",
"@swc/core-linux-s390x-gnu": "1.15.40",
"@swc/core-linux-x64-gnu": "1.15.40",
"@swc/core-linux-x64-musl": "1.15.40",
"@swc/core-win32-arm64-msvc": "1.15.40",
"@swc/core-win32-ia32-msvc": "1.15.40",
"@swc/core-win32-x64-msvc": "1.15.40"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -5228,6 +5987,12 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -5507,6 +6272,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/po-parser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
"license": "MIT"
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -6242,6 +7013,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tailwind-merge": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
"integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
@@ -6603,6 +7384,27 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-intl": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.13.0.tgz",
"integrity": "sha512-fAFDrWaASxlhXOipcOyb5VDD+YONqj6+8O8EcG/J7RBoOUF3A8YahRWLN+mBxYMrlMQB8N6Voqk5X+YC+HSL0A==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/amannn"
}
],
"license": "MIT",
"dependencies": {
"@formatjs/fast-memoize": "^3.1.0",
"@schummar/icu-type-parser": "1.21.5",
"icu-minify": "^4.13.0",
"intl-messageformat": "^11.1.0"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+6 -1
View File
@@ -9,9 +9,14 @@
"lint": "eslint"
},
"dependencies": {
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
"lucide-react": "^1.17.0",
"next": "16.2.7",
"next-intl": "^4.13.0",
"react": "19.2.4",
"react-dom": "19.2.4"
"react-dom": "19.2.4",
"tailwind-merge": "^3.6.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",