first commit

This commit is contained in:
2026-06-16 19:17:37 +03:00
parent 2d149f1178
commit 6fa7cc6630
25 changed files with 1378 additions and 167 deletions
+107
View File
@@ -0,0 +1,107 @@
"use client";
import * as React from "react";
import { motion } from "framer-motion";
import { mockData } from "@/lib/mock-data";
import { useTranslations } from "next-intl";
import { toast } from "sonner";
import { Loader2 } from "lucide-react";
export function ContactContent() {
const t = useTranslations("contact");
const [isLoading, setIsLoading] = React.useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
// Simulate network request
await new Promise((resolve) => setTimeout(resolve, 1500));
setIsLoading(false);
toast.success("Talebiniz başarıyla alındı!", {
description: "En kısa sürede size dönüş yapacağız.",
});
};
return (
<div className="container mx-auto px-4 py-24 relative overflow-hidden">
{/* Decorative background blob */}
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] bg-slate-200/50 dark:bg-slate-800/30 rounded-full blur-[100px] -z-10 opacity-50" />
<motion.h1
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-5xl font-black text-slate-900 dark:text-white mb-16 text-center tracking-tight"
>
{t("title")}
</motion.h1>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
{/* Contact Info */}
<motion.div
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="bg-white/60 dark:bg-slate-900/60 backdrop-blur-3xl p-10 rounded-[2rem] border border-white dark:border-slate-800 shadow-[0_8px_30px_rgb(0,0,0,0.04)]"
>
<h2 className="text-3xl font-bold text-slate-900 dark:text-white mb-10">{t("title")}</h2>
<div className="space-y-8">
<div className="group">
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{t("email")}</p>
<p className="text-xl font-medium text-slate-800 dark:text-slate-200">{mockData.contact.email}</p>
</div>
<div className="group">
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{t("phone")}</p>
<p className="text-xl font-medium text-slate-800 dark:text-slate-200">{mockData.contact.phone}</p>
</div>
<div className="group">
<p className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-2 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">{t("address")}</p>
<p className="text-xl font-medium text-slate-800 dark:text-slate-200 leading-relaxed max-w-xs">{mockData.contact.address}</p>
</div>
</div>
</motion.div>
{/* Contact Form */}
<motion.div
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="bg-white dark:bg-slate-900 p-10 rounded-[2rem] shadow-[0_20px_50px_rgb(0,0,0,0.08)] border border-slate-100 dark:border-slate-800"
>
<h2 className="text-3xl font-bold text-slate-900 dark:text-white mb-10">{t("reservationReq")}</h2>
<form className="space-y-6" onSubmit={handleSubmit}>
<div>
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">{t("email")}</label>
<input
type="email"
required
className="w-full border-none bg-slate-50 dark:bg-slate-950 rounded-xl px-5 py-4 focus:ring-2 focus:ring-slate-900 dark:focus:ring-slate-100 outline-none transition-all hover:bg-slate-100 dark:hover:bg-slate-800 placeholder:text-slate-400 dark:placeholder:text-slate-600 text-slate-900 dark:text-white"
placeholder="ornek@email.com"
/>
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">{t("reservationNo")} <span className="text-slate-400 dark:text-slate-500 font-normal">(Opsiyonel)</span></label>
<input
type="text"
className="w-full border-none bg-slate-50 dark:bg-slate-950 rounded-xl px-5 py-4 focus:ring-2 focus:ring-slate-900 dark:focus:ring-slate-100 outline-none transition-all hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-900 dark:text-white"
/>
</div>
<div className="pt-4">
<button
type="submit"
disabled={isLoading}
className="w-full flex justify-center items-center gap-2 bg-slate-900 dark:bg-white text-white dark:text-slate-900 hover:bg-slate-800 dark:hover:bg-slate-100 px-8 py-5 rounded-xl font-bold text-lg tracking-wide transition-all hover:shadow-xl hover:-translate-y-1 active:translate-y-0 disabled:opacity-70 disabled:hover:translate-y-0 disabled:hover:shadow-none"
>
{isLoading ? <Loader2 className="w-6 h-6 animate-spin" /> : t("send")}
</button>
</div>
</form>
</motion.div>
</div>
</div>
);
}
+82
View File
@@ -0,0 +1,82 @@
"use client";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { mockData } from "@/lib/mock-data";
import { Share2, Mail } from "lucide-react";
const FacebookIcon = ({ className }: { className?: string }) => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
</svg>
);
const InstagramIcon = ({ className }: { className?: string }) => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" 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"></rect>
<path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
<line x1="17.5" x2="17.51" y1="6.5" y2="6.5"></line>
</svg>
);
export function Footer() {
const t = useTranslations("footer");
return (
<footer className="w-full bg-surface-container-high dark:bg-inverse-surface border-t border-outline-variant/20 dark:border-outline/20">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 px-4 md:px-12 py-24 max-w-7xl mx-auto">
<div className="space-y-6">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={mockData.logos.main} alt="Kordon Apart Logo" className="h-10 w-auto" />
<p className="font-body-md text-on-surface-variant dark:text-outline pr-4">
{t("desc")}
</p>
<div className="flex gap-4">
<Link href={mockData.contact.social.facebook} className="w-10 h-10 rounded-full bg-primary/5 dark:bg-primary-fixed-dim/10 flex items-center justify-center text-primary dark:text-primary-fixed-dim hover:bg-primary dark:hover:bg-primary-fixed-dim hover:text-white dark:hover:text-primary-fixed transition-all">
<FacebookIcon className="w-5 h-5" />
</Link>
<Link href={mockData.contact.social.instagram} className="w-10 h-10 rounded-full bg-primary/5 dark:bg-primary-fixed-dim/10 flex items-center justify-center text-primary dark:text-primary-fixed-dim hover:bg-primary dark:hover:bg-primary-fixed-dim hover:text-white dark:hover:text-primary-fixed transition-all">
<InstagramIcon className="w-5 h-5" />
</Link>
</div>
</div>
<div className="space-y-6">
<h4 className="font-label-sm text-[12px] text-primary dark:text-primary-fixed-dim uppercase font-bold tracking-widest">{t("pages")}</h4>
<ul className="space-y-3">
<li><Link className="font-body-md text-on-surface-variant dark:text-outline hover:text-secondary dark:hover:text-primary-fixed transition-colors" href="/">{t("accommodations")}</Link></li>
<li><Link className="font-body-md text-on-surface-variant dark:text-outline hover:text-secondary dark:hover:text-primary-fixed transition-colors" href="/">{t("about")}</Link></li>
<li><Link className="font-body-md text-on-surface-variant dark:text-outline hover:text-secondary dark:hover:text-primary-fixed transition-colors" href="/">{t("reviews")}</Link></li>
</ul>
</div>
<div className="space-y-6">
<h4 className="font-label-sm text-[12px] text-primary dark:text-primary-fixed-dim uppercase font-bold tracking-widest">{t("legal")}</h4>
<ul className="space-y-3">
<li><Link className="font-body-md text-on-surface-variant dark:text-outline hover:text-secondary dark:hover:text-primary-fixed transition-colors" href="/">{t("privacyPolicy")}</Link></li>
<li><Link className="font-body-md text-on-surface-variant dark:text-outline hover:text-secondary dark:hover:text-primary-fixed transition-colors" href="/">{t("userAgreement")}</Link></li>
<li><Link className="font-body-md text-on-surface-variant dark:text-outline hover:text-secondary dark:hover:text-primary-fixed transition-colors" href="/">{t("cookiePolicy")}</Link></li>
</ul>
</div>
<div className="space-y-6">
<h4 className="font-label-sm text-[12px] text-primary dark:text-primary-fixed-dim uppercase font-bold tracking-widest">{t("contact")}</h4>
<p className="font-body-md text-on-surface-variant dark:text-outline">{mockData.contact.address}</p>
<p className="font-body-md text-on-surface-variant dark:text-outline">{mockData.contact.phone}</p>
<p className="font-body-md text-on-surface-variant dark:text-outline">{mockData.contact.email}</p>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 md:px-12 py-8 border-t border-outline-variant/30 dark:border-outline/20 flex flex-col md:flex-row justify-between items-center gap-4">
<span className="font-body-md text-on-surface-variant/70 dark:text-outline/70">
© {new Date().getFullYear()} Kordon Apart. {t("rights")}
</span>
<div className="flex gap-8 items-center">
<Link href="https://ayris.tech" target="_blank" rel="noopener noreferrer" className="text-primary dark:text-primary-fixed-dim font-semibold font-body-md hover:underline">
Created by ayris.tech
</Link>
</div>
</div>
</footer>
);
}
+108
View File
@@ -0,0 +1,108 @@
"use client";
import { useTranslations } from "next-intl";
import { motion } from "framer-motion";
import { mockData } from "@/lib/mock-data";
import Link from "next/link";
import Image from "next/image";
export function Hero() {
const t = useTranslations("hero");
return (
<>
<section
className="relative h-[921px] min-h-[600px] w-full overflow-hidden"
aria-label="Hero Section"
>
{/* Background Image - Using Next.js Image for optimization */}
<div className="absolute inset-0 z-0">
<motion.div
initial={{ scale: 1.05 }}
animate={{ scale: 1 }}
transition={{ duration: 15, ease: "easeOut" }}
style={{ willChange: "transform" }}
className="relative w-full h-full motion-reduce:!transform-none"
>
<Image
src={mockData.heroSlides[1]}
alt="Kordon Apart - Fethiye Marina manzarası"
fill
className="object-cover object-[center_top]"
priority
sizes="100vw"
/>
</motion.div>
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-b from-[#002045]/80 via-[#002045]/50 to-[#002045]/90" />
</div>
{/* Hero Content */}
<div className="relative z-10 h-full max-w-7xl mx-auto px-4 md:px-12 flex flex-col justify-center items-start pt-20">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="max-w-2xl motion-reduce:!transform-none motion-reduce:!opacity-100"
>
<span className="font-label-sm text-[12px] text-white/80 uppercase tracking-widest mb-4 block font-bold">
{t("subtitle")}
</span>
<h1 className="font-heading text-5xl md:text-6xl lg:text-7xl text-white mb-6 leading-tight font-extrabold tracking-tight">
{t("title")}
</h1>
<p className="font-body-lg text-lg text-white/90 mb-8 max-w-lg leading-relaxed" style={{ maxWidth: "65ch" }}>
{t("desc")}
</p>
<div className="flex flex-wrap gap-4">
<Link
href="#"
className="bg-[#CA8A04] text-white px-8 py-4 rounded-lg font-label-sm uppercase tracking-widest text-[12px] hover:bg-[#CA8A04]/90 transition-colors duration-200 shadow-lg cursor-pointer focus:ring-2 focus:ring-[#CA8A04]/50 focus:outline-none min-h-[48px] flex items-center"
role="button"
>
{t("explore")}
</Link>
<Link
href="#"
className="bg-white/10 backdrop-blur-md border border-white/20 text-white px-8 py-4 rounded-lg font-label-sm uppercase tracking-widest text-[12px] hover:bg-white/20 transition-colors duration-200 cursor-pointer focus:ring-2 focus:ring-white/50 focus:outline-none min-h-[48px] flex items-center"
role="button"
>
{t("virtualTour")}
</Link>
</div>
</motion.div>
</div>
</section>
{/* Search Bar Utility (Floating over Hero bottom) */}
<div className="relative z-20 max-w-5xl mx-auto px-4 -mt-24 hidden md:block">
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
className="bg-white p-8 rounded-xl shadow-2xl shadow-black/10 grid grid-cols-4 gap-6 items-end border border-gray-100 motion-reduce:!transform-none motion-reduce:!opacity-100"
>
<div className="space-y-2">
<label htmlFor="check-in" className="block font-label-sm text-[12px] text-gray-500 uppercase tracking-widest">{t("checkIn")}</label>
<input id="check-in" className="w-full bg-transparent border-0 border-b border-gray-200 py-2 focus:ring-0 focus:border-gray-900 transition-colors duration-200 text-gray-900 cursor-pointer min-h-[44px]" type="date" />
</div>
<div className="space-y-2">
<label htmlFor="check-out" className="block font-label-sm text-[12px] text-gray-500 uppercase tracking-widest">{t("checkOut")}</label>
<input id="check-out" className="w-full bg-transparent border-0 border-b border-gray-200 py-2 focus:ring-0 focus:border-gray-900 transition-colors duration-200 text-gray-900 cursor-pointer min-h-[44px]" type="date" />
</div>
<div className="space-y-2">
<label htmlFor="guests" className="block font-label-sm text-[12px] text-gray-500 uppercase tracking-widest">{t("guests")}</label>
<select id="guests" className="w-full bg-transparent border-0 border-b border-gray-200 py-2 focus:ring-0 focus:border-gray-900 transition-colors duration-200 text-gray-900 cursor-pointer min-h-[44px]">
<option value="1">{t("adult1")}</option>
<option value="2">{t("adult2")}</option>
<option value="3+">{t("adult3")}</option>
</select>
</div>
<button className="bg-[#CA8A04] text-white min-h-[48px] rounded-lg font-label-sm uppercase tracking-widest text-[12px] hover:bg-[#CA8A04]/90 transition-colors duration-200 font-bold cursor-pointer shadow-lg focus:ring-2 focus:ring-[#CA8A04]/50 focus:outline-none">
{t("checkAvailability")}
</button>
</motion.div>
</div>
</>
);
}
+51
View File
@@ -0,0 +1,51 @@
"use client";
import { useTranslations } from "next-intl";
import { motion } from "framer-motion";
export function Newsletter() {
const t = useTranslations("newsletter");
return (
<section className="py-24 bg-[#002045] text-white overflow-hidden relative" aria-label={t("title")}>
{/* Decorative Background Elements */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0" aria-hidden="true">
<div className="absolute -top-[20%] -left-[10%] w-[50%] h-[150%] bg-white/5 blur-3xl transform rotate-12 rounded-full" />
<div className="absolute -bottom-[20%] -right-[10%] w-[50%] h-[150%] bg-[#CA8A04]/10 blur-3xl transform -rotate-12 rounded-full" />
</div>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6 }}
className="max-w-7xl mx-auto px-4 md:px-12 relative z-10 text-center motion-reduce:!transform-none motion-reduce:!opacity-100"
>
<h2 className="font-heading text-4xl md:text-5xl text-white mb-6 font-bold tracking-tight">
{t("title")}
</h2>
<p className="font-body-lg text-lg text-white/70 mb-10 max-w-xl mx-auto" style={{ maxWidth: "65ch" }}>
{t("desc")}
</p>
<form className="max-w-md mx-auto flex flex-col md:flex-row gap-4" onSubmit={(e) => e.preventDefault()}>
<label htmlFor="newsletter-email" className="sr-only">{t("label")}</label>
<input
id="newsletter-email"
className="flex-1 bg-white/10 border border-white/20 text-white placeholder-white/40 rounded-lg px-6 py-4 focus:ring-2 focus:ring-[#CA8A04]/50 focus:border-[#CA8A04] outline-none transition-colors duration-200 min-h-[48px]"
placeholder={t("placeholder")}
type="email"
required
autoComplete="email"
/>
<button
type="submit"
className="bg-[#CA8A04] text-white px-8 py-4 rounded-lg font-label-sm text-[12px] uppercase tracking-widest font-bold hover:bg-[#CA8A04]/90 transition-colors duration-200 shadow-lg cursor-pointer focus:ring-2 focus:ring-[#CA8A04]/50 focus:outline-none min-h-[48px]"
>
{t("subscribe")}
</button>
</form>
</motion.div>
</section>
);
}
+116
View File
@@ -0,0 +1,116 @@
"use client";
import { useTranslations } from "next-intl";
import { mockData } from "@/lib/mock-data";
import Link from "next/link";
import Image from "next/image";
import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";
export function RoomList() {
const t = useTranslations("accommodations");
// We take the first 3 rooms for the bento grid
const featuredRoom = mockData.accommodations[0];
const sideRooms = mockData.accommodations.slice(1, 3);
return (
<section className="py-24 max-w-7xl mx-auto px-4 md:px-12" aria-label="Odalarımız">
<div className="flex justify-between items-end mb-12">
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.6 }}
className="space-y-2 motion-reduce:!transform-none motion-reduce:!opacity-100"
>
<h2 className="font-heading text-3xl md:text-4xl font-bold text-primary dark:text-primary-fixed-dim">{t("title")}</h2>
<p className="font-body-md text-on-surface-variant dark:text-outline text-lg" style={{ maxWidth: "65ch" }}>{t("desc")}</p>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.6 }}
className="motion-reduce:!transform-none motion-reduce:!opacity-100"
>
<Link href="/odalar" className="font-label-sm text-[12px] uppercase tracking-widest text-primary dark:text-primary-fixed-dim flex items-center gap-2 group font-bold cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-md px-2 py-1 min-h-[44px]">
{t("viewAll")}
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform duration-200" />
</Link>
</motion.div>
</div>
{/* Bento-style Grid */}
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.6 }}
className="grid grid-cols-1 md:grid-cols-12 gap-6 h-auto md:h-[600px] motion-reduce:!transform-none motion-reduce:!opacity-100"
>
<Link
href={`/odalar/${featuredRoom.slug}`}
className="md:col-span-8 group relative overflow-hidden rounded-xl cursor-pointer focus:ring-2 focus:ring-primary/50 focus:outline-none"
aria-label={`${featuredRoom.name} - ${featuredRoom.type}`}
>
<div className="relative w-full h-full min-h-[300px]">
<Image
src={featuredRoom.image}
alt={`${featuredRoom.name} - ${featuredRoom.type} oda görseli`}
fill
className="object-cover transition-transform duration-500 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 66vw"
loading="lazy"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-t from-[#002045]/90 via-[#002045]/20 to-transparent opacity-80" />
<div className="absolute bottom-0 left-0 p-8 text-white w-full">
<div className="flex gap-2 mb-3">
<span className="bg-[#CA8A04] text-white px-3 py-1 rounded-full font-label-sm text-[10px] uppercase font-bold tracking-wider">
{t("bestseller")}
</span>
</div>
<h3 className="font-heading text-3xl md:text-4xl font-bold mb-2">{featuredRoom.name}</h3>
<p className="font-body-md text-white/80 text-lg">
{featuredRoom.type} {t("bedrooms", { count: featuredRoom.bedrooms })} {t("persons", { count: featuredRoom.capacity })}
</p>
</div>
</Link>
{/* Side Stacked Items */}
<div className="md:col-span-4 grid grid-rows-2 gap-6">
{sideRooms.map((room) => (
<Link
href={`/odalar/${room.slug}`}
key={room.id}
className="group relative overflow-hidden rounded-xl cursor-pointer focus:ring-2 focus:ring-primary/50 focus:outline-none"
aria-label={`${room.name} - ${room.type}`}
>
<div className="relative w-full h-full min-h-[150px]">
<Image
src={room.image}
alt={`${room.name} - ${room.type} oda görseli`}
fill
className="object-cover transition-transform duration-500 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 33vw"
loading="lazy"
/>
</div>
<div className="absolute inset-0 bg-gradient-to-t from-[#002045]/80 to-transparent opacity-90" />
<div className="absolute bottom-0 left-0 p-6 text-white w-full">
<h4 className="font-label-sm text-[12px] uppercase tracking-wider font-bold text-[#CA8A04] mb-1">{room.type}</h4>
<h3 className="font-heading text-xl font-semibold mb-1">{room.name}</h3>
<p className="font-body-md text-white/80 text-sm">
{t("persons", { count: room.capacity })}
</p>
</div>
</Link>
))}
</div>
</motion.div>
</section>
);
}
+107
View File
@@ -0,0 +1,107 @@
"use client";
import { useTranslations } from "next-intl";
import { motion } from "framer-motion";
import { Waves, UtensilsCrossed, ConciergeBell } from "lucide-react";
export function Services() {
const t = useTranslations("services");
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.15,
}
}
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: "easeOut" } }
};
const services = [
{
icon: Waves,
title: t("service1_title"),
desc: t("service1_desc"),
},
{
icon: UtensilsCrossed,
title: t("service2_title"),
desc: t("service2_desc"),
},
{
icon: ConciergeBell,
title: t("service3_title"),
desc: t("service3_desc"),
},
];
return (
<section className="bg-surface-container-low dark:bg-inverse-surface/50 py-24" aria-label="Hizmetlerimiz">
<div className="max-w-7xl mx-auto px-4 md:px-12">
<div className="text-center max-w-2xl mx-auto mb-16">
<motion.span
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="font-label-sm text-[12px] text-[#CA8A04] dark:text-[#F8BC4B] uppercase tracking-widest mb-4 block font-bold motion-reduce:!transform-none motion-reduce:!opacity-100"
>
{t("subtitle")}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="font-heading text-4xl text-primary dark:text-primary-fixed-dim mb-4 font-bold motion-reduce:!transform-none motion-reduce:!opacity-100"
>
{t("title")}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className="font-body-md text-on-surface-variant dark:text-outline text-lg motion-reduce:!transform-none motion-reduce:!opacity-100"
style={{ maxWidth: "65ch", margin: "0 auto" }}
>
{t("desc")}
</motion.p>
</div>
<motion.div
variants={container}
initial="hidden"
whileInView="show"
viewport={{ once: true, margin: "-50px" }}
className="grid grid-cols-1 md:grid-cols-3 gap-12"
role="list"
>
{services.map((service) => (
<motion.div
variants={item}
key={service.title}
className="text-center space-y-6 px-4 motion-reduce:!transform-none motion-reduce:!opacity-100"
role="listitem"
>
<div className="w-20 h-20 bg-white dark:bg-primary-container rounded-full flex items-center justify-center mx-auto shadow-md text-primary dark:text-primary-fixed-dim" aria-hidden="true">
<service.icon className="w-8 h-8" strokeWidth={1.5} />
</div>
<div>
<h3 className="font-heading text-2xl text-primary dark:text-primary-fixed-dim mb-3 font-semibold">{service.title}</h3>
<p className="font-body-md text-on-surface-variant dark:text-outline leading-relaxed" style={{ maxWidth: "45ch", margin: "0 auto" }}>
{service.desc}
</p>
</div>
</motion.div>
))}
</motion.div>
</div>
</section>
);
}
+45
View File
@@ -0,0 +1,45 @@
"use client";
import { useTranslations } from "next-intl";
import Link from "next/link";
import Image from "next/image";
import { mockData } from "@/lib/mock-data";
import { ThemeToggle } from "@/components/theme-toggle";
export function Navbar() {
const t = useTranslations("nav");
return (
<header className="fixed top-0 w-full z-50 bg-surface/80 dark:bg-inverse-surface/80 backdrop-blur-md shadow-sm shadow-primary-container/5 transition-colors duration-200">
<nav className="flex justify-between items-center px-4 md:px-12 py-4 max-w-7xl mx-auto" aria-label="Main Navigation">
<div className="flex items-center gap-4">
<Link href="/" className="flex items-center space-x-2 group cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-md min-h-[44px]" aria-label="Ana Sayfa - Kordon Apart">
<Image src={mockData.logos.main} alt="Kordon Apart Logo" width={120} height={40} className="h-10 w-auto group-hover:opacity-80 transition-opacity duration-200" priority />
</Link>
</div>
<div className="hidden md:flex items-center gap-8">
<Link href="/" className="text-primary dark:text-primary-fixed-dim font-bold border-b border-primary dark:border-primary-fixed-dim font-label-sm uppercase tracking-widest text-[12px] cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-sm py-1 min-h-[44px] flex items-center">{t("home")}</Link>
<Link href="/odalar" className="text-on-surface-variant dark:text-outline hover:text-primary dark:hover:text-primary-fixed-dim transition-colors duration-200 font-label-sm uppercase tracking-widest text-[12px] cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-sm py-1 min-h-[44px] flex items-center">{t("accommodations")}</Link>
<Link href="/galeri" className="text-on-surface-variant dark:text-outline hover:text-primary dark:hover:text-primary-fixed-dim transition-colors duration-200 font-label-sm uppercase tracking-widest text-[12px] cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-sm py-1 min-h-[44px] flex items-center">{t("gallery")}</Link>
<Link href="/iletisim" className="text-on-surface-variant dark:text-outline hover:text-primary dark:hover:text-primary-fixed-dim transition-colors duration-200 font-label-sm uppercase tracking-widest text-[12px] cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-sm py-1 min-h-[44px] flex items-center">{t("contact")}</Link>
</div>
<div className="flex items-center gap-6">
<div className="hidden md:flex gap-3 text-on-surface-variant dark:text-outline font-label-sm text-[12px]">
<Link href="/tr" className="hover:text-primary dark:hover:text-primary-fixed-dim transition-colors duration-200 cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-sm min-h-[44px] min-w-[44px] flex items-center justify-center" aria-label="Türkçe">TR</Link>
<Link href="/en" className="hover:text-primary dark:hover:text-primary-fixed-dim transition-colors duration-200 cursor-pointer focus:ring-2 focus:ring-primary/30 focus:outline-none rounded-sm min-h-[44px] min-w-[44px] flex items-center justify-center" aria-label="English">EN</Link>
</div>
<div className="w-px h-4 bg-outline-variant dark:bg-outline mx-2 hidden md:block" aria-hidden="true" />
<ThemeToggle />
<Link
href="/iletisim"
className="hidden md:flex bg-[#CA8A04] text-white px-6 py-2 rounded-full font-label-sm uppercase tracking-widest text-[12px] hover:bg-[#CA8A04]/90 transition-colors duration-200 shadow-md cursor-pointer focus:ring-2 focus:ring-[#CA8A04]/50 focus:outline-none min-h-[44px] items-center"
>
{t("bookNow")}
</Link>
</div>
</nav>
</header>
);
}
+90
View File
@@ -0,0 +1,90 @@
"use client"
import * as React from "react"
type Theme = "light" | "dark" | "system"
const ThemeContext = React.createContext<{
theme: Theme
setTheme: (theme: Theme) => void
resolvedTheme: "light" | "dark"
}>({
theme: "system",
setTheme: () => {},
resolvedTheme: "light",
})
export function useTheme() {
return React.useContext(ThemeContext)
}
function getSystemTheme(): "light" | "dark" {
if (typeof window === "undefined") return "light"
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
}
export function ThemeProvider({
children,
attribute = "class",
defaultTheme = "system",
enableSystem = true,
}: {
children: React.ReactNode
attribute?: string
defaultTheme?: Theme
enableSystem?: boolean
disableTransitionOnChange?: boolean
}) {
const [theme, setThemeState] = React.useState<Theme>(defaultTheme)
const [resolvedTheme, setResolvedTheme] = React.useState<"light" | "dark">("light")
const [mounted, setMounted] = React.useState(false)
// Read saved theme from localStorage on mount
React.useEffect(() => {
const saved = localStorage.getItem("theme") as Theme | null
if (saved) {
setThemeState(saved)
}
setMounted(true)
}, [])
// Resolve system theme and apply to document
React.useEffect(() => {
const resolved = theme === "system" ? getSystemTheme() : theme
setResolvedTheme(resolved)
const root = document.documentElement
root.classList.remove("light", "dark")
root.classList.add(resolved)
}, [theme])
// Listen for system theme changes
React.useEffect(() => {
if (!enableSystem) return
const mql = window.matchMedia("(prefers-color-scheme: dark)")
const handler = () => {
if (theme === "system") {
const resolved = getSystemTheme()
setResolvedTheme(resolved)
const root = document.documentElement
root.classList.remove("light", "dark")
root.classList.add(resolved)
}
}
mql.addEventListener("change", handler)
return () => mql.removeEventListener("change", handler)
}, [theme, enableSystem])
const setTheme = React.useCallback((newTheme: Theme) => {
setThemeState(newTheme)
localStorage.setItem("theme", newTheme)
}, [])
return (
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
{children}
</ThemeContext.Provider>
)
}
+54
View File
@@ -0,0 +1,54 @@
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "@/components/theme-provider"
import { motion } from "framer-motion"
export function ThemeToggle() {
const { theme, setTheme, resolvedTheme } = useTheme()
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return <div className="w-9 h-9" />
}
const isDark = resolvedTheme === "dark"
return (
<button
onClick={() => setTheme(isDark ? "light" : "dark")}
className="relative p-2 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors outline-none focus-visible:ring-2 focus-visible:ring-primary overflow-hidden"
aria-label="Toggle theme"
>
<motion.div
initial={false}
animate={{
scale: isDark ? 0 : 1,
opacity: isDark ? 0 : 1,
rotate: isDark ? -90 : 0
}}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="absolute inset-0 flex items-center justify-center"
>
<Sun className="h-4 w-4" />
</motion.div>
<motion.div
initial={false}
animate={{
scale: isDark ? 1 : 0,
opacity: isDark ? 1 : 0,
rotate: isDark ? 0 : 90
}}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="relative flex items-center justify-center"
>
<Moon className="h-4 w-4" />
</motion.div>
</button>
)
}