first commit
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, type Variants } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { Utensils, Umbrella, Home, Wind, Hotel, Star, MapPin, ArrowRight } from "lucide-react";
|
||||
import { Button } from "@/app/components/ui/button";
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Marquee } from "@/app/components/ui/marquee";
|
||||
import { AnimatedNumber } from "@/app/components/ui/animated-number";
|
||||
import { TextReveal } from "@/app/components/ui/text-reveal";
|
||||
import { ShimmerButton } from "@/app/components/ui/shimmer-button";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
|
||||
const HERO_BG =
|
||||
"https://images.unsplash.com/photo-1499793983690-e29da59ef1c2?auto=format&fit=crop&q=80&w=2070";
|
||||
|
||||
const MARQUEE_ITEMS = [
|
||||
"Akyaka",
|
||||
"Akçapınar",
|
||||
"Datça",
|
||||
"Beach Bar",
|
||||
"Kite Surf",
|
||||
"Restaurant",
|
||||
"Bungalov",
|
||||
"Hotel",
|
||||
"Ege Denizi",
|
||||
];
|
||||
|
||||
const STATS = [
|
||||
{ value: 15, suffix: "+", label: "Yıl Deneyim" },
|
||||
{ value: 3, suffix: "", label: "Lokasyon" },
|
||||
{ value: 5000, suffix: "+", label: "Mutlu Misafir" },
|
||||
{ value: 5, suffix: "", label: "Hizmet Kategorisi" },
|
||||
];
|
||||
|
||||
const SERVICES = [
|
||||
{
|
||||
id: "restaurant",
|
||||
title: "Restaurant",
|
||||
icon: Utensils,
|
||||
desc: "Ege'nin taze lezzetleri, yerel malzemeler ve şef imzalı tatlar.",
|
||||
bg: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "beach",
|
||||
title: "Beach Bar",
|
||||
icon: Umbrella,
|
||||
desc: "Altın kumlar, masmavi deniz ve özenle seçilmiş içecekler.",
|
||||
bg: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "bungalow",
|
||||
title: "Bungalov",
|
||||
icon: Home,
|
||||
desc: "Doğa ile iç içe, huzurlu ve modern konaklama deneyimi.",
|
||||
bg: "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "kitesurf",
|
||||
title: "Kite Surf",
|
||||
icon: Wind,
|
||||
desc: "Rüzgarın enerjisini hissedin; başlangıç ve ileri seviye dersler.",
|
||||
bg: "https://images.unsplash.com/photo-1526685514088-290076a0d426?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
{
|
||||
id: "hotel",
|
||||
title: "Hotel",
|
||||
icon: Hotel,
|
||||
desc: "Premium konfor ve Ege'nin eşsiz manzarasıyla özel tatil.",
|
||||
bg: "https://images.unsplash.com/photo-1566073771259-6a8506099945?auto=format&fit=crop&q=80&w=800",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
const TESTIMONIALS = [
|
||||
{
|
||||
name: "Ayşe Y.",
|
||||
origin: "İstanbul",
|
||||
comment:
|
||||
"Ege'de şimdiye kadar geçirdiğim en güzel tatildi. Hizmet kalitesi muazzamdı, her detay özenle düşünülmüş.",
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
name: "John S.",
|
||||
origin: "London",
|
||||
comment:
|
||||
"The kite surfing instructors were incredibly professional. The location is breathtaking — I'll be back next summer.",
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
name: "Mehmet K.",
|
||||
origin: "Ankara",
|
||||
comment:
|
||||
"Bungalovlar son derece huzurlu. Doğanın içindeyken şehrin konforundan hiç taviz vermiyorsunuz.",
|
||||
rating: 5,
|
||||
},
|
||||
];
|
||||
|
||||
const fadeUp: Variants = {
|
||||
hidden: { opacity: 0, y: 30 },
|
||||
visible: (i = 0) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.65, delay: i * 0.12, ease: [0.16, 1, 0.3, 1] as [number, number, number, number] },
|
||||
}),
|
||||
};
|
||||
|
||||
export default function HomeClient({ lang, dict }: { lang: string; dict: any }) {
|
||||
const [activeMap, setActiveMap] = useState(0);
|
||||
|
||||
const VENUES: { id: string; title: string; tag: string; desc: string; img: string; color: string }[] = dict.venuesList;
|
||||
const LOCATIONS_MAP: { place: string; addr: string; embed: string }[] = dict.locationsMap;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
|
||||
{/* ── Hero ── */}
|
||||
<section className="relative h-screen flex items-center justify-center overflow-hidden moy-grain">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center bg-no-repeat scale-105"
|
||||
style={{ backgroundImage: `url(${HERO_BG})` }}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/30 to-black/60" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
||||
{/* Eyebrow */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-[10px] uppercase tracking-[0.3em] text-[var(--color-moy-gold)] mb-8 font-medium"
|
||||
>
|
||||
{dict.hero.eyebrow}
|
||||
</motion.p>
|
||||
|
||||
{/* Main heading */}
|
||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-serif text-white mb-8 leading-[0.95] tracking-tight">
|
||||
<TextReveal text={dict.hero.title} delay={0.2} />
|
||||
</h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.9 }}
|
||||
className="text-base md:text-lg text-gray-300/90 mb-12 font-light max-w-xl mx-auto leading-relaxed"
|
||||
>
|
||||
{dict.hero.subtitle}
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 1.1 }}
|
||||
className="flex flex-col sm:flex-row gap-4 justify-center"
|
||||
>
|
||||
<Link href={`/${lang}/iletisim`}>
|
||||
<Button variant="primary" size="lg" shimmer className="group">
|
||||
{dict.hero.btn1}
|
||||
</Button>
|
||||
</Link>
|
||||
<Button variant="outline-light" size="lg" className="group" as="button">
|
||||
<Link href={`/${lang}/#mekanlar`} className="flex items-center gap-2">
|
||||
{dict.hero.btn2}
|
||||
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</Button>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.6, duration: 0.6 }}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2"
|
||||
>
|
||||
<span className="text-[9px] uppercase tracking-[0.25em] text-white/50">{dict.hero.scroll}</span>
|
||||
<motion.div
|
||||
animate={{ y: [0, 8, 0] }}
|
||||
transition={{ duration: 1.6, repeat: Infinity, ease: "easeInOut" }}
|
||||
className="w-[1px] h-8 bg-gradient-to-b from-white/50 to-transparent"
|
||||
/>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* ── Marquee Strip ── */}
|
||||
<div className="bg-[var(--color-moy-gold)] py-4 overflow-hidden">
|
||||
<Marquee
|
||||
items={MARQUEE_ITEMS}
|
||||
separator="·"
|
||||
speed="normal"
|
||||
itemClassName="text-[var(--color-moy-dark)] font-semibold"
|
||||
separatorClassName="text-[var(--color-moy-dark)] opacity-40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── Stats ── */}
|
||||
<section className="bg-[var(--color-moy-dark)] py-20">
|
||||
<div className="max-w-5xl mx-auto px-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-12 md:gap-6">
|
||||
{STATS.map((s, i) => (
|
||||
<motion.div
|
||||
key={s.label}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={fadeUp}
|
||||
className="text-center"
|
||||
>
|
||||
<p className="text-5xl md:text-6xl font-serif text-[var(--color-moy-gold)] mb-2 leading-none">
|
||||
<AnimatedNumber value={s.value} suffix={s.suffix} duration={1600} />
|
||||
</p>
|
||||
<p className="text-[10px] uppercase tracking-[0.2em] text-gray-500 mt-3">
|
||||
{s.label}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Services (Geçici olarak gizlendi) ── */}
|
||||
{/*
|
||||
<section id="hizmetler" className="py-28 bg-[var(--color-moy-light)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<SectionHeader
|
||||
eyebrow="Ne Sunuyoruz"
|
||||
title="Hizmetlerimiz"
|
||||
subtitle="Her biri kendi hikâyesini taşıyan beş kategori — hepsi Ege'nin özüyle harmanlanmış."
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-5">
|
||||
{SERVICES.map((service, i) => (
|
||||
<motion.div
|
||||
key={service.id}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={fadeUp}
|
||||
whileHover={{ y: -10 }}
|
||||
className="group relative h-80 overflow-hidden cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-110"
|
||||
style={{ backgroundImage: `url(${service.bg})` }}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[var(--color-moy-dark)] via-[var(--color-moy-dark)]/50 to-transparent" />
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-[var(--color-moy-gold)] scale-x-0 group-hover:scale-x-100 transition-transform duration-500 origin-left" />
|
||||
|
||||
<div className="absolute inset-0 p-6 flex flex-col justify-end items-center text-center">
|
||||
<div className="w-14 h-14 border border-[var(--color-moy-gold)]/60 text-[var(--color-moy-gold)] flex items-center justify-center mb-4 group-hover:bg-[var(--color-moy-gold)] group-hover:text-[var(--color-moy-dark)] group-hover:border-[var(--color-moy-gold)] transition-all duration-300">
|
||||
<service.icon size={24} />
|
||||
</div>
|
||||
<h3 className="text-xl font-serif text-white mb-2">{service.title}</h3>
|
||||
<p className="text-xs text-gray-300/80 opacity-0 translate-y-3 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300 leading-relaxed">
|
||||
{service.desc}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
|
||||
{/* ── Venues ── */}
|
||||
<section id="mekanlar" className="py-28 bg-white">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<SectionHeader
|
||||
eyebrow={dict.venues.eyebrow}
|
||||
title={dict.venues.title}
|
||||
subtitle={dict.venues.subtitle}
|
||||
/>
|
||||
|
||||
<div className="space-y-20">
|
||||
{VENUES.map((venue, i) => (
|
||||
<motion.div
|
||||
key={venue.id}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-60px" }}
|
||||
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={`flex flex-col ${i % 2 === 1 ? "md:flex-row-reverse" : "md:flex-row"} gap-10 items-center`}
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="w-full md:w-[55%] group relative overflow-hidden shadow-[var(--shadow-xl)]">
|
||||
<img
|
||||
src={venue.img}
|
||||
alt={venue.title}
|
||||
className="w-full h-[420px] object-cover transition-transform duration-700 group-hover:scale-105"
|
||||
/>
|
||||
<div className={`absolute inset-0 bg-gradient-to-t ${venue.color} to-transparent opacity-50`} />
|
||||
<Badge variant="light" className="absolute bottom-6 left-6 shadow-md">
|
||||
{venue.tag}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Text */}
|
||||
<div className="w-full md:w-[45%] px-2 md:px-6">
|
||||
<p className="text-[10px] uppercase tracking-[0.22em] text-[var(--color-moy-gold-dark)] mb-4">
|
||||
{venue.tag}
|
||||
</p>
|
||||
<h3 className="text-3xl md:text-4xl font-serif text-[var(--color-moy-dark)] mb-6 leading-tight">
|
||||
{venue.title}
|
||||
</h3>
|
||||
<span className="moy-gold-line mb-6 block" />
|
||||
<p className="text-gray-500 mb-8 text-base leading-relaxed">{venue.desc}</p>
|
||||
<Link
|
||||
href={`/${lang}/mekanlar/${venue.id}`}
|
||||
className="inline-flex items-center gap-2 text-[11px] uppercase tracking-[0.18em] font-semibold text-[var(--color-moy-dark)] border-b border-[var(--color-moy-gold)] pb-1 hover:text-[var(--color-moy-gold-dark)] transition-colors group"
|
||||
>
|
||||
{dict.cta.exploreBtn}
|
||||
<ArrowRight size={12} className="group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Testimonials (Geçici olarak gizlendi) ── */}
|
||||
{/*
|
||||
<section className="py-28 bg-[var(--color-moy-blue)] relative overflow-hidden">
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[600px] rounded-full bg-[var(--color-moy-gold)]/5 blur-3xl pointer-events-none" />
|
||||
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 relative">
|
||||
<SectionHeader
|
||||
eyebrow="Misafir Deneyimleri"
|
||||
title="Misafirlerimiz Ne Diyor?"
|
||||
light
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{TESTIMONIALS.map((t, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={fadeUp}
|
||||
className="relative bg-white/6 backdrop-blur-sm border border-white/10 p-8 hover:border-[var(--color-moy-gold)]/30 transition-colors duration-300"
|
||||
>
|
||||
<span className="absolute top-6 right-8 text-6xl font-serif text-[var(--color-moy-gold)]/15 leading-none select-none">
|
||||
"
|
||||
</span>
|
||||
|
||||
<div className="flex gap-1 mb-6">
|
||||
{[...Array(t.rating)].map((_, j) => (
|
||||
<Star key={j} size={14} className="fill-[var(--color-moy-gold)] text-[var(--color-moy-gold)]" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-300/85 text-sm leading-relaxed mb-8 italic">
|
||||
"{t.comment}"
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-[1px] bg-[var(--color-moy-gold)]/50" />
|
||||
<div>
|
||||
<p className="font-serif text-white text-base">{t.name}</p>
|
||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mt-0.5">{t.origin}</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
*/}
|
||||
|
||||
{/* ── CTA Banner ── */}
|
||||
<section className="relative py-32 overflow-hidden bg-[var(--color-moy-dark)]">
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center opacity-20"
|
||||
style={{
|
||||
backgroundImage: `url(https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=60&w=1600)`,
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[var(--color-moy-dark)] via-transparent to-[var(--color-moy-dark)]" />
|
||||
|
||||
<div className="relative max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-6">
|
||||
{dict.cta.eyebrow}
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-6xl font-serif text-white mb-6 leading-tight">
|
||||
{dict.cta.title1}<br />
|
||||
<span className="italic text-[var(--color-moy-gold)]">{dict.cta.title2}</span>
|
||||
</h2>
|
||||
<p className="text-gray-400 text-base mb-12 max-w-md mx-auto leading-relaxed">
|
||||
{dict.cta.subtitle}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href={`/${lang}/iletisim`}>
|
||||
<ShimmerButton
|
||||
shimmerColor="rgba(201,169,110,0.6)"
|
||||
background="var(--color-moy-gold)"
|
||||
className="text-[var(--color-moy-dark)] font-bold"
|
||||
>
|
||||
{dict.cta.btn1}
|
||||
</ShimmerButton>
|
||||
</Link>
|
||||
<Link href={`/${lang}/iletisim`}>
|
||||
<Button variant="outline-light" size="lg">
|
||||
{dict.cta.btn2}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── Map / Location ── */}
|
||||
<section className="py-28 bg-[var(--color-moy-light)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col lg:flex-row gap-16 items-start">
|
||||
<div className="w-full lg:w-1/3">
|
||||
<SectionHeader
|
||||
eyebrow={dict.venues.eyebrow}
|
||||
title={dict.venues.title}
|
||||
align="left"
|
||||
/>
|
||||
<p className="text-gray-500 mb-10 -mt-8 leading-relaxed">
|
||||
{dict.venues.subtitle}
|
||||
</p>
|
||||
<ul className="space-y-4">
|
||||
{LOCATIONS_MAP.map((loc, idx) => (
|
||||
<li
|
||||
key={loc.place}
|
||||
className={`flex items-start gap-3 p-4 rounded-xl cursor-pointer transition-all duration-300 ${activeMap === idx ? 'bg-white shadow-[var(--shadow-sm)] translate-x-2' : 'hover:bg-white/50'}`}
|
||||
onClick={() => setActiveMap(idx)}
|
||||
>
|
||||
<MapPin className={`${activeMap === idx ? 'text-[var(--color-moy-gold)]' : 'text-gray-400'} mt-0.5 shrink-0 transition-colors`} size={18} />
|
||||
<div>
|
||||
<strong className={`block text-sm font-medium mb-0.5 transition-colors ${activeMap === idx ? 'text-[var(--color-moy-dark)]' : 'text-gray-600'}`}>
|
||||
{loc.place}
|
||||
</strong>
|
||||
<span className="text-xs text-gray-500">{loc.addr}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-2/3 h-[420px] bg-white overflow-hidden shadow-[var(--shadow-lg)] relative rounded-2xl">
|
||||
<iframe
|
||||
src={LOCATIONS_MAP[activeMap].embed}
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{ border: 0 }}
|
||||
allowFullScreen={false}
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
className="absolute inset-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { X, ZoomIn } from "lucide-react";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
|
||||
export default function GaleriClient({ lang, dict }: { lang: string; dict: any }) {
|
||||
const t = dict.gallery;
|
||||
|
||||
const CATEGORIES = [t.all, t.venues, "Beach", "Bungalov", "Kite Surf", "Hotel"] as const;
|
||||
type Category = string;
|
||||
|
||||
const IMAGES = [
|
||||
{ src: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?auto=format&fit=crop&q=80&w=800", cat: t.venues, title: "Akşam Servisi" },
|
||||
{ src: "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=800", cat: "Beach", title: "Moy Beach Alaçatı" },
|
||||
{ src: "https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&q=80&w=800", cat: "Bungalov", title: "Doğada Konaklama" },
|
||||
{ src: "https://images.unsplash.com/photo-1526685514088-290076a0d426?auto=format&fit=crop&q=80&w=800", cat: "Kite Surf", title: "Gökova Körfezi" },
|
||||
{ src: "https://images.unsplash.com/photo-1566073771259-6a8506099945?auto=format&fit=crop&q=80&w=800", cat: "Hotel", title: "Kite Beach Hotel" },
|
||||
{ src: "https://images.unsplash.com/photo-1519046904884-53103b34b206?auto=format&fit=crop&q=80&w=800", cat: "Beach", title: "Gün Batımı" },
|
||||
{ src: "https://images.unsplash.com/photo-1571003123894-1f0594d2b5d9?auto=format&fit=crop&q=80&w=800", cat: "Hotel", title: "Oda Manzarası" },
|
||||
{ src: "https://images.unsplash.com/photo-1614214227038-f9d9cf98b1ec?auto=format&fit=crop&q=80&w=800", cat: "Kite Surf", title: "Sabah Seansı" },
|
||||
{ src: "https://images.unsplash.com/photo-1490197415175-074fd86b1fcc?auto=format&fit=crop&q=80&w=800", cat: t.venues, title: "Taze Deniz Ürünleri" },
|
||||
];
|
||||
|
||||
const [active, setActive] = useState<Category>(t.all);
|
||||
const [lightbox, setLightbox] = useState<{src:string, cat:string, title:string} | null>(null);
|
||||
|
||||
const filtered = active === t.all ? IMAGES : IMAGES.filter((img) => img.cat === active);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-moy-dark)]">
|
||||
{/* Hero */}
|
||||
<div className="pt-40 pb-16 text-center px-6">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">{t.eyebrow}</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6">{t.title}</h1>
|
||||
<p className="text-gray-400 text-base max-w-md mx-auto leading-relaxed">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Filter pills */}
|
||||
<div className="flex flex-wrap justify-center gap-3 px-6 mb-14">
|
||||
{CATEGORIES.map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => setActive(cat)}
|
||||
className={[
|
||||
"px-5 py-2 text-[10px] uppercase tracking-[0.16em] font-medium transition-all duration-200",
|
||||
active === cat
|
||||
? "bg-[var(--color-moy-gold)] text-[var(--color-moy-dark)]"
|
||||
: "border border-white/15 text-gray-400 hover:border-[var(--color-moy-gold)]/50 hover:text-[var(--color-moy-gold)]",
|
||||
].join(" ")}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 pb-28">
|
||||
<motion.div
|
||||
layout
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{filtered.map((img) => (
|
||||
<motion.div
|
||||
key={img.src}
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.94 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.94 }}
|
||||
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="group relative aspect-square cursor-pointer overflow-hidden"
|
||||
onClick={() => setLightbox(img)}
|
||||
>
|
||||
<img
|
||||
src={img.src}
|
||||
alt={img.title}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[var(--color-moy-dark)]/0 group-hover:bg-[var(--color-moy-dark)]/50 transition-all duration-300 flex flex-col items-center justify-center gap-3">
|
||||
<ZoomIn
|
||||
size={28}
|
||||
className="text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300 translate-y-2 group-hover:translate-y-0"
|
||||
/>
|
||||
<span className="text-white text-sm font-serif opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
{img.title}
|
||||
</span>
|
||||
</div>
|
||||
<Badge
|
||||
variant="dark"
|
||||
className="absolute top-3 left-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
>
|
||||
{img.cat}
|
||||
</Badge>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Lightbox */}
|
||||
<AnimatePresence>
|
||||
{lightbox && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-[300] bg-black/90 flex items-center justify-center p-6"
|
||||
onClick={() => setLightbox(null)}
|
||||
>
|
||||
<button
|
||||
className="absolute top-6 right-6 text-white/60 hover:text-white transition-colors"
|
||||
onClick={() => setLightbox(null)}
|
||||
>
|
||||
<X size={28} />
|
||||
</button>
|
||||
<motion.div
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="max-w-4xl w-full"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<img
|
||||
src={lightbox.src}
|
||||
alt={lightbox.title}
|
||||
className="w-full max-h-[80vh] object-contain"
|
||||
/>
|
||||
<p className="text-center text-white font-serif text-xl mt-4">{lightbox.title}</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
import GaleriClient from "./GaleriClient";
|
||||
|
||||
export const metadata = {
|
||||
title: "Galeri | Moy Group",
|
||||
};
|
||||
|
||||
export default async function GaleriPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
|
||||
return <GaleriClient lang={locale} dict={dict} />;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { MapPin, Phone, Mail } from "lucide-react";
|
||||
import { Input, Textarea } from "@/app/components/ui/input";
|
||||
import { Button } from "@/app/components/ui/button";
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
|
||||
const LOCATIONS = [
|
||||
{
|
||||
name: "Moy Beach",
|
||||
tag: "Beach & Bar",
|
||||
address: "Akyaka, Atatürk 1. Cd No:86, 48640 Ula/Muğla",
|
||||
phones: ["+90 534 465 62 35"],
|
||||
email: "beach@moygroup.com.tr",
|
||||
variant: "gold" as const,
|
||||
},
|
||||
{
|
||||
name: "Kite Beach Hotel",
|
||||
tag: "Hotel",
|
||||
address: "Akçapınar Mah. Akçapınar Sok. Kiteboard Sahili, Muğla",
|
||||
phones: ["+90 533 081 61 81"],
|
||||
email: "hotel@moygroup.com.tr",
|
||||
variant: "blue" as const,
|
||||
},
|
||||
{
|
||||
name: "Volkswagen Kite (Surfing)",
|
||||
tag: "Kite Surf",
|
||||
address: "Akçapınar kite sahili, Ula, Turkey 48640",
|
||||
phones: ["+90 537 882 31 37", "+90 505 204 98 56"],
|
||||
email: "kite@moygroup.com.tr",
|
||||
variant: "green" as const,
|
||||
},
|
||||
];
|
||||
|
||||
export default async function IletisimPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
const t = dict.contact;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-moy-light)]">
|
||||
{/* Hero */}
|
||||
<div className="bg-[var(--color-moy-dark)] pt-40 pb-20">
|
||||
<div className="max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">
|
||||
{t.eyebrow}
|
||||
</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6 leading-tight">
|
||||
{t.title}
|
||||
</h1>
|
||||
<p className="text-gray-400 text-base leading-relaxed max-w-lg mx-auto">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Locations */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-10 pb-24">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 mb-24">
|
||||
{LOCATIONS.map((loc) => (
|
||||
<div
|
||||
key={loc.name}
|
||||
className="bg-white border border-gray-100 p-8 shadow-[var(--shadow-sm)] hover:shadow-[var(--shadow-md)] transition-shadow duration-300"
|
||||
>
|
||||
<Badge variant={loc.variant} className="mb-5">{loc.tag}</Badge>
|
||||
<h3 className="text-xl font-serif text-[var(--color-moy-dark)] mb-5">{loc.name}</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={15} className="text-[var(--color-moy-gold)] mt-0.5 shrink-0" />
|
||||
<span className="text-sm text-gray-500">{loc.address}</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Phone size={15} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col gap-1">
|
||||
{loc.phones.map((phone, i) => (
|
||||
<a key={i} href={`tel:${phone.replace(/\s/g, "")}`} className="text-sm text-gray-500 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
{phone}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Mail size={15} className="text-[var(--color-moy-gold)] shrink-0" />
|
||||
<a href={`mailto:${loc.email}`} className="text-sm text-gray-500 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
{loc.email}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contact Form + Info */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-12">
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-3 bg-white border border-gray-100 p-10 shadow-[var(--shadow-sm)]">
|
||||
<SectionHeader
|
||||
eyebrow={t.formEyebrow}
|
||||
title={t.formTitle}
|
||||
align="left"
|
||||
className="mb-10"
|
||||
/>
|
||||
<form className="space-y-5">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||
<Input label={t.name} placeholder={t.namePlaceholder} id="name" />
|
||||
<Input label={t.email} type="email" placeholder={t.emailPlaceholder} id="email" />
|
||||
</div>
|
||||
<Input label={t.phone} type="tel" placeholder={t.phonePlaceholder} id="phone" />
|
||||
<div>
|
||||
<label className="text-[10px] font-medium uppercase tracking-[0.16em] text-gray-500 block mb-1.5">
|
||||
{t.subject}
|
||||
</label>
|
||||
<select className="w-full px-4 py-3.5 bg-white border border-gray-200 text-sm text-[var(--color-moy-dark)] focus:outline-none focus:ring-2 focus:ring-[var(--color-moy-gold)]/40 focus:border-[var(--color-moy-gold)] transition-all duration-200">
|
||||
{t.subjectOptions.map((opt: string, i: number) => (
|
||||
<option key={i} value={i === 0 ? "" : opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<Textarea label={t.message} placeholder={t.messagePlaceholder} id="message" rows={5} />
|
||||
<Button variant="primary" size="lg" shimmer className="w-full group mt-2">
|
||||
{t.send}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Sidebar info */}
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
<div className="bg-[var(--color-moy-dark)] p-8">
|
||||
<h3 className="font-serif text-xl text-white mb-6">{t.generalContact}</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-center gap-3">
|
||||
<Phone size={16} className="text-[var(--color-moy-gold)]" />
|
||||
<div>
|
||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mb-0.5">{t.phoneText}</p>
|
||||
<a href="tel:+905344656235" className="text-sm text-gray-300 hover:text-[var(--color-moy-gold)] transition-colors">+90 534 465 62 35</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-center gap-3">
|
||||
<Mail size={16} className="text-[var(--color-moy-gold)]" />
|
||||
<div>
|
||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mb-0.5">{t.emailText}</p>
|
||||
<a href="mailto:info@moygroup.com.tr" className="text-sm text-gray-300 hover:text-[var(--color-moy-gold)] transition-colors">info@moygroup.com.tr</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 p-8">
|
||||
<h3 className="font-serif text-xl text-[var(--color-moy-dark)] mb-6">{t.hours}</h3>
|
||||
<ul className="space-y-2 text-sm">
|
||||
{[
|
||||
[t.monFri, "09:00 – 23:00"],
|
||||
[t.sat, "10:00 – 24:00"],
|
||||
[t.sun, "10:00 – 23:00"],
|
||||
].map(([day, hours]) => (
|
||||
<li key={day as string} className="flex justify-between text-gray-500">
|
||||
<span>{day as string}</span>
|
||||
<span className="font-medium text-[var(--color-moy-dark)]">{hours as string}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-xs text-[var(--color-moy-gold-dark)] mt-4">{t.hoursNote}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-gray-100 p-8">
|
||||
<h3 className="font-serif text-xl text-[var(--color-moy-dark)] mb-4">{t.social}</h3>
|
||||
<div className="flex gap-4">
|
||||
<a href="#" aria-label="Instagram" className="w-10 h-10 border border-gray-200 flex items-center justify-center hover:border-[var(--color-moy-gold)] hover:text-[var(--color-moy-gold)] text-gray-400 transition-all duration-200">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="20" x="2" y="2" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/></svg>
|
||||
</a>
|
||||
<a href="#" aria-label="Facebook" className="w-10 h-10 border border-gray-200 flex items-center justify-center hover:border-[var(--color-moy-gold)] hover:text-[var(--color-moy-gold)] text-gray-400 transition-all duration-200">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
|
||||
export const metadata = {
|
||||
title: "Kurumsal | Moy Group",
|
||||
};
|
||||
|
||||
export default async function KurumsalPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
const t = dict.corporate;
|
||||
|
||||
return (
|
||||
<div className="pt-32 pb-24 min-h-screen bg-[var(--color-moy-light)]">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{/* HAKKIMIZDA */}
|
||||
<section className="mb-20">
|
||||
<h1 className="text-4xl md:text-5xl font-serif text-[var(--color-moy-dark)] mb-6 text-center">{t.title}</h1>
|
||||
<div className="w-24 h-1 bg-[var(--color-moy-gold)] mx-auto mb-10"></div>
|
||||
<div className="bg-white p-8 md:p-12 rounded-2xl shadow-sm border border-gray-100 text-gray-700 leading-relaxed space-y-6">
|
||||
<p>{t.p1}</p>
|
||||
<p>{t.p2}</p>
|
||||
<p>{t.p3}</p>
|
||||
<p>{t.p4}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* VİZYONUMUZ & MİSYONUMUZ */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||
|
||||
<section>
|
||||
<div className="bg-[var(--color-moy-dark)] text-white p-8 md:p-10 rounded-2xl shadow-lg h-full border border-gray-800">
|
||||
<h2 className="text-2xl font-serif text-[var(--color-moy-gold)] mb-6 uppercase tracking-wider">{t.vision}</h2>
|
||||
<p className="text-gray-300 leading-relaxed font-light">
|
||||
{t.visionText}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div className="bg-white p-8 md:p-10 rounded-2xl shadow-sm h-full border border-gray-200">
|
||||
<h2 className="text-2xl font-serif text-[var(--color-moy-gold-dark)] mb-6 uppercase tracking-wider">{t.mission}</h2>
|
||||
<p className="text-gray-600 leading-relaxed">
|
||||
{t.missionText}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Cormorant_Garamond, Montserrat } from "next/font/google";
|
||||
import Navbar from "../components/Navbar";
|
||||
import Footer from "../components/Footer";
|
||||
import "../globals.css";
|
||||
import { Locale, getDictionary } from "../dictionaries";
|
||||
|
||||
const cormorant = Cormorant_Garamond({
|
||||
variable: "--font-cormorant",
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
const montserrat = Montserrat({
|
||||
variable: "--font-montserrat",
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Moy Group | Ege'nin Ruhunu Hisset",
|
||||
description: "Akyaka, Akçapınar ve Datça'da restoran, beach, bungalov ve kitesurf hizmetleriyle doğa ile iç içe premium tatil deneyimi.",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ lang: string }>;
|
||||
}>) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
|
||||
return (
|
||||
<html
|
||||
lang={locale}
|
||||
className={`${cormorant.variable} ${montserrat.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">
|
||||
<Navbar lang={locale} dict={dict.navigation} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer lang={locale} dict={dict.footer} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
|
||||
export const metadata = {
|
||||
title: "Dijital Menü | Moy Group",
|
||||
};
|
||||
|
||||
export default async function MenuPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
const t = dict.menu;
|
||||
|
||||
const MENU_SECTIONS = [
|
||||
{
|
||||
title: t.starters,
|
||||
badge: "Starters",
|
||||
variant: "gold" as const,
|
||||
items: [
|
||||
{ name: "Zeytinyağlı Enginar", desc: "Taze dereotu ve limon", price: "₺ 185" },
|
||||
{ name: "Ahtapot Izgara", desc: "Kapari salatası, fesleğen yağı", price: "₺ 340" },
|
||||
{ name: "Kabak Çiçeği Dolması", desc: "Lor peyniri, nane", price: "₺ 210" },
|
||||
{ name: "Ezme Tabağı", desc: "Ev yapımı ekmek ile", price: "₺ 145" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.mains,
|
||||
badge: "Mains",
|
||||
variant: "blue" as const,
|
||||
items: [
|
||||
{ name: "Levrek Izgara", desc: "Mevsim yeşillikleri, limon yağı", price: "₺ 680" },
|
||||
{ name: "Çipura Fırın", desc: "Ege otları, domates, zeytinyağı", price: "₺ 620" },
|
||||
{ name: "Kuzu Pirzola", desc: "Kekik soslu, ızgara sebze", price: "₺ 750" },
|
||||
{ name: "Mantar Risotto", desc: "Porcini, parmesan, taze kekik", price: "₺ 390" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.desserts,
|
||||
badge: "Desserts",
|
||||
variant: "green" as const,
|
||||
items: [
|
||||
{ name: "Incir Tatlısı", desc: "Kaymak, ceviz, pekmez", price: "₺ 175" },
|
||||
{ name: "Dondurma Tabağı", desc: "Yerel bademli, mevsim meyve", price: "₺ 145" },
|
||||
{ name: "Kemalpaşa", desc: "Sıcak servis, dövme ceviz", price: "₺ 120" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.drinks,
|
||||
badge: "Drinks",
|
||||
variant: "dark" as const,
|
||||
items: [
|
||||
{ name: "Datça Badem Şerbeti", desc: "Ev yapımı, taze", price: "₺ 95" },
|
||||
{ name: "Limonata", desc: "Taze sıkım, nane", price: "₺ 85" },
|
||||
{ name: "Lokal Şarap", desc: "Günlük seçki", price: "₺ 195 / bardak" },
|
||||
{ name: "Soda & Maden Suyu", desc: "", price: "₺ 45" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[var(--color-moy-light)]">
|
||||
{/* Hero */}
|
||||
<div className="bg-[var(--color-moy-dark)] pt-40 pb-20">
|
||||
<div className="max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">{t.eyebrow}</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6">{t.title}</h1>
|
||||
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 py-24 space-y-16">
|
||||
<SectionHeader
|
||||
eyebrow="Moy Restaurant"
|
||||
title={t.sectionTitle}
|
||||
subtitle={t.sectionSubtitle}
|
||||
/>
|
||||
|
||||
{MENU_SECTIONS.map((section) => (
|
||||
<div key={section.title}>
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<h2 className="text-2xl font-serif text-[var(--color-moy-dark)]">{section.title}</h2>
|
||||
<Badge variant={section.variant}>{section.badge}</Badge>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-gray-200/60">
|
||||
{section.items.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="flex items-baseline justify-between py-5 group hover:px-3 transition-all duration-200"
|
||||
>
|
||||
<div>
|
||||
<h3 className="font-serif text-lg text-[var(--color-moy-dark)] group-hover:text-[var(--color-moy-gold-dark)] transition-colors">
|
||||
{item.name}
|
||||
</h3>
|
||||
{item.desc && (
|
||||
<p className="text-xs text-gray-400 mt-1 tracking-wide">{item.desc}</p>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-sm font-medium text-[var(--color-moy-dark)] ml-8 shrink-0">
|
||||
{item.price}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<p className="text-xs text-center text-gray-400 border-t border-gray-200 pt-8">
|
||||
{t.note}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getDictionary, Locale } from "../dictionaries";
|
||||
import HomeClient from "./HomeClient";
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: Locale }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const dict = await getDictionary(lang);
|
||||
|
||||
return <HomeClient lang={lang} dict={dict.home} />;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Plus, Minus } from "lucide-react";
|
||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||
import { Badge } from "@/app/components/ui/badge";
|
||||
|
||||
function FaqItem({ q, a }: { q: string; a: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<div className="border-b border-gray-100 last:border-0">
|
||||
<button
|
||||
className="w-full flex items-center justify-between py-5 text-left group"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
>
|
||||
<span className="font-serif text-lg text-[var(--color-moy-dark)] group-hover:text-[var(--color-moy-gold-dark)] transition-colors pr-4">
|
||||
{q}
|
||||
</span>
|
||||
<span className="shrink-0 w-7 h-7 border border-gray-200 group-hover:border-[var(--color-moy-gold)] flex items-center justify-center transition-colors">
|
||||
{open
|
||||
? <Minus size={14} className="text-[var(--color-moy-gold)]" />
|
||||
: <Plus size={14} className="text-gray-400 group-hover:text-[var(--color-moy-gold)] transition-colors" />
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<p className="text-gray-500 text-sm leading-relaxed pb-5">{a}</p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SSSClient({ lang, dict }: { lang: string; dict: any }) {
|
||||
const t = dict.faq;
|
||||
|
||||
const FAQS = [
|
||||
{
|
||||
category: t.categories.accommodation,
|
||||
variant: "blue" as const,
|
||||
items: [
|
||||
{ q: t.q1, a: t.a1 },
|
||||
{ q: t.q2, a: t.a2 },
|
||||
{ q: t.q3, a: t.a3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t.categories.kitesurf,
|
||||
variant: "green" as const,
|
||||
items: [
|
||||
{ q: t.q4, a: t.a4 },
|
||||
{ q: t.q5, a: t.a5 },
|
||||
{ q: t.q6, a: t.a6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t.categories.reservation,
|
||||
variant: "gold" as const,
|
||||
items: [
|
||||
{ q: t.q7, a: t.a7 },
|
||||
{ q: t.q8, a: t.a8 },
|
||||
{ q: t.q9, a: t.a9 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{/* Hero */}
|
||||
<div className="bg-[var(--color-moy-dark)] pt-40 pb-20">
|
||||
<div className="max-w-3xl mx-auto px-6 text-center">
|
||||
<p className="text-[10px] uppercase tracking-[0.28em] text-[var(--color-moy-gold)] mb-4">{t.eyebrow}</p>
|
||||
<h1 className="text-5xl md:text-6xl font-serif text-white mb-6">{t.title}</h1>
|
||||
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-24 space-y-14">
|
||||
{FAQS.map((group) => (
|
||||
<div key={group.category}>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Badge variant={group.variant}>{group.category}</Badge>
|
||||
</div>
|
||||
<div className="bg-[var(--color-moy-light)] px-6">
|
||||
{group.items.map((item) => (
|
||||
<FaqItem key={item.q} q={item.q} a={item.a} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { getDictionary, Locale } from "../../dictionaries";
|
||||
import SSSClient from "./SSSClient";
|
||||
|
||||
export const metadata = {
|
||||
title: "SSS | Moy Group",
|
||||
};
|
||||
|
||||
export default async function SSSPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ lang: string }>;
|
||||
}) {
|
||||
const { lang } = await params;
|
||||
const locale = lang as Locale;
|
||||
const dict = await getDictionary(locale);
|
||||
|
||||
return <SSSClient lang={locale} dict={dict} />;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import Link from "next/link";
|
||||
import { MapPin, Phone, Mail } from "lucide-react";
|
||||
import { Locale } from "../dictionaries";
|
||||
|
||||
export default function Footer({ lang, dict }: { lang: Locale; dict: any }) {
|
||||
return (
|
||||
<footer className="bg-[var(--color-moy-dark)] text-white pt-16 pb-8 border-t border-gray-800">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-12">
|
||||
{/* Brand */}
|
||||
<div className="space-y-4">
|
||||
<img src="/logo.png" alt="Moy Group Logo" className="h-12 w-auto mb-6" />
|
||||
<p className="text-gray-400 text-sm leading-relaxed">
|
||||
{dict.description}
|
||||
</p>
|
||||
<div className="flex gap-4 pt-4">
|
||||
<a href="#" className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="20" x="2" y="2" rx="5" ry="5" /><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" /><line x1="17.5" x2="17.51" y1="6.5" y2="6.5" /></svg>
|
||||
</a>
|
||||
<a href="#" className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h3 className="text-lg font-serif mb-6 text-white uppercase tracking-wider">{dict.quickLinks}</h3>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<Link href={`/${lang}/kurumsal`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">{dict.about}</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`/${lang}/galeri`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Galeri</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link href={`/${lang}/iletisim`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">{dict.contactInfo}</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Locations */}
|
||||
<div>
|
||||
<h3 className="text-lg font-serif mb-6 text-white uppercase tracking-wider">{dict.ourVenues}</h3>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<Link href={`/${lang}/#mekanlar`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Moy Beach</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`/${lang}/#mekanlar`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Kite Beach Hotel</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`/${lang}/#mekanlar`} className="text-gray-400 hover:text-[var(--color-moy-gold)] transition-colors text-sm">Volkswagen Kite</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Contact */}
|
||||
<div>
|
||||
<h3 className="text-lg font-serif mb-6 text-white uppercase tracking-wider">{dict.contactInfo}</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={18} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white text-sm mb-1">Moy Beach</span>
|
||||
<span className="text-gray-400 text-xs">Atatürk 1. Cd No:86, Akyaka, Ula/Muğla</span>
|
||||
<a href="tel:+905344656235" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs mt-1">+90 534 465 62 35</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={18} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white text-sm mb-1">Kite Beach Hotel</span>
|
||||
<span className="text-gray-400 text-xs">Akçapınar Sok. Kiteboard Sahili, Muğla</span>
|
||||
<a href="tel:+905330816181" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs mt-1">+90 533 081 61 81</a>
|
||||
</div>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<MapPin size={18} className="text-[var(--color-moy-gold)] shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-white text-sm mb-1">Volkswagen Kite</span>
|
||||
<span className="text-gray-400 text-xs">Akçapınar Kite Sahili, Ula/Muğla</span>
|
||||
<div className="flex flex-col gap-1 mt-1">
|
||||
<a href="tel:+905378823137" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs">+90 537 882 31 37</a>
|
||||
<a href="tel:+905052049856" className="text-[var(--color-moy-gold)] hover:text-white transition-colors text-xs">+90 505 204 98 56</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom */}
|
||||
<div className="pt-8 border-t border-gray-800 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p className="text-gray-500 text-xs">
|
||||
© {new Date().getFullYear()} Moy Group. {dict.rights}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a href="https://ayris.tech" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-[var(--color-moy-gold)] transition-colors text-xs flex items-center gap-1">
|
||||
created by <span className="font-medium">ayris.tech</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, X, Globe } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Locale } from "../dictionaries";
|
||||
|
||||
export default function Navbar({ lang, dict }: { lang: Locale; dict: any }) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const isHome = pathname === `/${lang}` || pathname === `/${lang}/`;
|
||||
|
||||
const navLinks = [
|
||||
{ name: dict.home, href: `/${lang}` },
|
||||
{ name: dict.corporate, href: `/${lang}/kurumsal` },
|
||||
{ name: dict.venues, href: `/${lang}/#mekanlar` },
|
||||
{ name: dict.gallery, href: `/${lang}/galeri` },
|
||||
{ name: dict.contact, href: `/${lang}/iletisim` },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 50);
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
const switchLanguage = (newLang: string) => {
|
||||
if (!pathname) return '/';
|
||||
// Replace the current lang code in the URL with the new one
|
||||
const segments = pathname.split('/');
|
||||
if (segments[1] === 'tr' || segments[1] === 'en') {
|
||||
segments[1] = newLang;
|
||||
} else {
|
||||
segments.splice(1, 0, newLang);
|
||||
}
|
||||
return segments.join('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
|
||||
isScrolled || !isHome
|
||||
? "bg-[var(--color-moy-dark)]/90 backdrop-blur-md py-4 shadow-lg"
|
||||
: "bg-transparent py-6"
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center">
|
||||
{/* Logo */}
|
||||
<Link href={`/${lang}`} className="flex-shrink-0">
|
||||
<img src="/logo.png" alt="Moy Group Logo" className="h-10 w-auto" />
|
||||
</Link>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
className="text-white hover:text-[var(--color-moy-gold)] transition-colors text-sm uppercase tracking-wider font-medium"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{/* Language Switcher */}
|
||||
<div className="flex items-center gap-2 ml-4 border-l border-gray-600 pl-4">
|
||||
<Globe size={16} className="text-[var(--color-moy-gold)]" />
|
||||
<Link href={switchLanguage('tr')} className={`text-sm font-medium ${lang === 'tr' ? 'text-white' : 'text-gray-400 hover:text-white'}`}>TR</Link>
|
||||
<span className="text-gray-600">|</span>
|
||||
<Link href={switchLanguage('en')} className={`text-sm font-medium ${lang === 'en' ? 'text-white' : 'text-gray-400 hover:text-white'}`}>EN</Link>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="md:hidden text-white"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
>
|
||||
{mobileMenuOpen ? <X size={28} /> : <Menu size={28} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Nav */}
|
||||
<AnimatePresence>
|
||||
{mobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
className="absolute top-full left-0 w-full bg-[var(--color-moy-dark)] border-t border-gray-800 shadow-xl md:hidden"
|
||||
>
|
||||
<div className="px-4 pt-2 pb-6 space-y-1">
|
||||
{navLinks.map((link) => (
|
||||
<Link
|
||||
key={link.name}
|
||||
href={link.href}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="block px-3 py-4 text-white border-b border-gray-800 hover:text-[var(--color-moy-gold)] uppercase tracking-wider text-sm"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
))}
|
||||
<div className="flex items-center gap-4 px-3 py-4 text-white">
|
||||
<Globe size={18} className="text-[var(--color-moy-gold)]" />
|
||||
<Link href={switchLanguage('tr')} onClick={() => setMobileMenuOpen(false)} className={`text-sm ${lang === 'tr' ? 'text-[var(--color-moy-gold)] font-bold' : ''}`}>Türkçe</Link>
|
||||
<Link href={switchLanguage('en')} onClick={() => setMobileMenuOpen(false)} className={`text-sm ${lang === 'en' ? 'text-[var(--color-moy-gold)] font-bold' : ''}`}>English</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useInView } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AnimatedNumberProps {
|
||||
value: number;
|
||||
suffix?: string;
|
||||
prefix?: string;
|
||||
duration?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AnimatedNumber({
|
||||
value,
|
||||
suffix = "",
|
||||
prefix = "",
|
||||
duration = 1800,
|
||||
className,
|
||||
}: AnimatedNumberProps) {
|
||||
const [count, setCount] = useState(0);
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-80px" });
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInView) return;
|
||||
|
||||
let rafId: number;
|
||||
let startTime: number | null = null;
|
||||
|
||||
const step = (ts: number) => {
|
||||
if (!startTime) startTime = ts;
|
||||
const progress = Math.min((ts - startTime) / duration, 1);
|
||||
// Ease-out cubic
|
||||
const eased = 1 - Math.pow(1 - progress, 3);
|
||||
setCount(Math.round(eased * value));
|
||||
if (progress < 1) rafId = requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
rafId = requestAnimationFrame(step);
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
}, [isInView, value, duration]);
|
||||
|
||||
return (
|
||||
<span ref={ref} className={cn(className)}>
|
||||
{prefix}{count}{suffix}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Variant = "gold" | "blue" | "green" | "dark" | "light";
|
||||
|
||||
interface BadgeProps {
|
||||
variant?: Variant;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const variants: Record<Variant, string> = {
|
||||
gold: "bg-[var(--color-moy-gold-faint)] text-[var(--color-moy-gold-dark)] border border-[var(--color-moy-gold-subtle)]",
|
||||
blue: "bg-[var(--color-moy-blue)]/10 text-[var(--color-moy-blue)] border border-[var(--color-moy-blue)]/20",
|
||||
green: "bg-[var(--color-moy-green)]/10 text-[var(--color-moy-green)] border border-[var(--color-moy-green)]/20",
|
||||
dark: "bg-[var(--color-moy-dark)] text-white border border-[var(--color-moy-dark-border)]",
|
||||
light: "bg-white text-[var(--color-moy-dark)] border border-gray-200",
|
||||
};
|
||||
|
||||
export function Badge({ variant = "gold", children, className }: BadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center px-3 py-1 text-[10px] font-medium uppercase tracking-[0.18em] rounded-full",
|
||||
variants[variant],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { type ButtonHTMLAttributes, forwardRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Variant = "primary" | "secondary" | "ghost" | "outline-light";
|
||||
type Size = "sm" | "md" | "lg";
|
||||
|
||||
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
shimmer?: boolean;
|
||||
as?: "button";
|
||||
}
|
||||
|
||||
const variants: Record<Variant, string> = {
|
||||
primary:
|
||||
"bg-[var(--color-moy-gold)] text-[var(--color-moy-dark)] hover:bg-[var(--color-moy-gold-light)] font-bold shadow-[var(--shadow-gold)] hover:shadow-[var(--shadow-gold-lg)]",
|
||||
secondary:
|
||||
"border border-[var(--color-moy-gold)] text-[var(--color-moy-gold)] hover:bg-[var(--color-moy-gold)] hover:text-[var(--color-moy-dark)] font-medium",
|
||||
ghost:
|
||||
"text-[var(--color-moy-gold)] hover:bg-[var(--color-moy-gold-faint)] font-medium",
|
||||
"outline-light":
|
||||
"border border-white/50 text-white hover:border-white hover:bg-white/10 font-medium",
|
||||
};
|
||||
|
||||
const sizes: Record<Size, string> = {
|
||||
sm: "px-5 py-2 text-[11px] tracking-[0.15em]",
|
||||
md: "px-8 py-3.5 text-[11px] tracking-[0.18em]",
|
||||
lg: "px-10 py-4 text-[11px] tracking-[0.2em]",
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = "primary", size = "md", shimmer = false, children, ...props }, ref) => (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center uppercase overflow-hidden",
|
||||
"transition-all duration-300",
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{shimmer && (
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 moy-shimmer opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
||||
/>
|
||||
)}
|
||||
<span className="relative">{children}</span>
|
||||
</button>
|
||||
)
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
@@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/* ── Base Card ── */
|
||||
interface CardProps {
|
||||
children: React.ReactNode;
|
||||
hover?: boolean;
|
||||
glass?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Card({ children, hover = true, glass = false, className }: CardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={hover ? { y: -6 } : undefined}
|
||||
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={cn(
|
||||
"rounded-2xl overflow-hidden transition-shadow duration-300",
|
||||
glass
|
||||
? "bg-white/10 backdrop-blur-md border border-white/20"
|
||||
: "bg-white border border-gray-100 shadow-[var(--shadow-sm)] hover:shadow-[var(--shadow-lg)]",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Image Card ── */
|
||||
interface ImageCardProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
badge?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ImageCard({ src, alt, title, subtitle, badge, className }: ImageCardProps) {
|
||||
return (
|
||||
<Card className={cn("group cursor-pointer", className)}>
|
||||
<div className="relative h-64 overflow-hidden">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-108"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-400" />
|
||||
{badge && (
|
||||
<span className="absolute top-4 left-4 text-[10px] uppercase tracking-[0.18em] font-semibold px-3 py-1 bg-[var(--color-moy-gold)] text-[var(--color-moy-dark)] rounded-full">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6 border-t-[1.5px] border-transparent group-hover:border-[var(--color-moy-gold)] transition-[border-color] duration-300">
|
||||
<h3 className="text-xl font-serif text-[var(--color-moy-dark)] mb-2 leading-snug">{title}</h3>
|
||||
{subtitle && (
|
||||
<p className="text-sm text-gray-500 leading-relaxed">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export { Button } from "./button";
|
||||
export { Badge } from "./badge";
|
||||
export { SectionHeader } from "./section-header";
|
||||
export { Card, ImageCard } from "./card";
|
||||
export { Input, Textarea } from "./input";
|
||||
export { AnimatedNumber } from "./animated-number";
|
||||
export { ShimmerButton } from "./shimmer-button";
|
||||
export { Marquee } from "./marquee";
|
||||
export { TextReveal } from "./text-reveal";
|
||||
@@ -0,0 +1,68 @@
|
||||
import { forwardRef, type InputHTMLAttributes, type TextareaHTMLAttributes } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/* ── Input ── */
|
||||
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, label, error, id, ...props }, ref) => (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{label && (
|
||||
<label htmlFor={id} className="text-[10px] font-medium uppercase tracking-[0.16em] text-gray-500">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={cn(
|
||||
"w-full px-4 py-3.5 bg-white border border-gray-200 text-[var(--color-moy-dark)] text-sm",
|
||||
"focus:outline-none focus:ring-2 focus:ring-[var(--color-moy-gold)]/40 focus:border-[var(--color-moy-gold)]",
|
||||
"transition-all duration-200 placeholder:text-gray-300",
|
||||
"rounded-none", // sharp corners = editorial feel
|
||||
error && "border-red-300 focus:ring-red-200/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span className="text-xs text-red-500">{error}</span>}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
/* ── Textarea ── */
|
||||
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, label, error, id, ...props }, ref) => (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{label && (
|
||||
<label htmlFor={id} className="text-[10px] font-medium uppercase tracking-[0.16em] text-gray-500">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<textarea
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={cn(
|
||||
"w-full px-4 py-3.5 bg-white border border-gray-200 text-[var(--color-moy-dark)] text-sm resize-none",
|
||||
"focus:outline-none focus:ring-2 focus:ring-[var(--color-moy-gold)]/40 focus:border-[var(--color-moy-gold)]",
|
||||
"transition-all duration-200 placeholder:text-gray-300",
|
||||
"rounded-none",
|
||||
error && "border-red-300 focus:ring-red-200/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{error && <span className="text-xs text-red-500">{error}</span>}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
Textarea.displayName = "Textarea";
|
||||
@@ -0,0 +1,49 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface MarqueeProps {
|
||||
items: string[];
|
||||
separator?: string;
|
||||
speed?: "slow" | "normal" | "fast";
|
||||
reverse?: boolean;
|
||||
className?: string;
|
||||
itemClassName?: string;
|
||||
separatorClassName?: string;
|
||||
}
|
||||
|
||||
const speeds = { slow: "42s", normal: "28s", fast: "16s" };
|
||||
|
||||
export function Marquee({
|
||||
items,
|
||||
separator = "✦",
|
||||
speed = "normal",
|
||||
reverse = false,
|
||||
className,
|
||||
itemClassName,
|
||||
separatorClassName,
|
||||
}: MarqueeProps) {
|
||||
// Double for seamless infinite loop
|
||||
const doubled = [...items, ...items];
|
||||
|
||||
return (
|
||||
<div className={cn("overflow-hidden whitespace-nowrap select-none", className)}>
|
||||
<div
|
||||
className="inline-flex"
|
||||
style={{
|
||||
animation: `marquee ${speeds[speed]} linear infinite${reverse ? " reverse" : ""}`,
|
||||
willChange: "transform",
|
||||
}}
|
||||
>
|
||||
{doubled.map((item, i) => (
|
||||
<span key={i} className="inline-flex items-center">
|
||||
<span className={cn("px-7 text-[11px] font-medium uppercase tracking-[0.18em]", itemClassName)}>
|
||||
{item}
|
||||
</span>
|
||||
<span className={cn("opacity-50 text-sm", separatorClassName)}>
|
||||
{separator}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
eyebrow?: string;
|
||||
align?: "left" | "center" | "right";
|
||||
light?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SectionHeader({
|
||||
title,
|
||||
subtitle,
|
||||
eyebrow,
|
||||
align = "center",
|
||||
light = false,
|
||||
className,
|
||||
}: SectionHeaderProps) {
|
||||
const alignCls = {
|
||||
left: "items-start text-left",
|
||||
center: "items-center text-center",
|
||||
right: "items-end text-right",
|
||||
}[align];
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col mb-16", alignCls, className)}>
|
||||
{eyebrow && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-[10px] font-medium uppercase tracking-[0.22em] mb-5",
|
||||
light ? "text-[var(--color-moy-gold-light)]" : "text-[var(--color-moy-gold-dark)]"
|
||||
)}
|
||||
>
|
||||
{eyebrow}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<h2
|
||||
className={cn(
|
||||
"text-4xl md:text-[3.25rem] font-serif leading-tight mb-5",
|
||||
light ? "text-white" : "text-[var(--color-moy-dark)]"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
"moy-gold-line",
|
||||
align === "center" && "mx-auto",
|
||||
align === "right" && "ml-auto"
|
||||
)}
|
||||
/>
|
||||
|
||||
{subtitle && (
|
||||
<p
|
||||
className={cn(
|
||||
"mt-6 text-base md:text-lg leading-relaxed max-w-2xl",
|
||||
align === "center" && "mx-auto",
|
||||
light ? "text-gray-300/80" : "text-gray-500"
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ShimmerButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
shimmerColor?: string;
|
||||
background?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ShimmerButton = forwardRef<HTMLButtonElement, ShimmerButtonProps>(
|
||||
(
|
||||
{
|
||||
shimmerColor = "rgba(201,169,110,0.55)",
|
||||
background = "var(--color-moy-dark)",
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group relative inline-flex items-center justify-center overflow-hidden",
|
||||
"px-10 py-4 text-[11px] font-medium uppercase tracking-[0.2em] text-white",
|
||||
"transition-all duration-300",
|
||||
className
|
||||
)}
|
||||
style={{ background }}
|
||||
{...props}
|
||||
>
|
||||
{/* Animated shimmer strip */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background: `linear-gradient(105deg, transparent 35%, ${shimmerColor} 50%, transparent 65%)`,
|
||||
backgroundSize: "200% 100%",
|
||||
animation: "shimmer 2.8s linear infinite",
|
||||
}}
|
||||
/>
|
||||
{/* Inset bg to sit above shimmer */}
|
||||
<span
|
||||
aria-hidden
|
||||
className="absolute inset-[1px]"
|
||||
style={{ background }}
|
||||
/>
|
||||
<span className="relative">{children}</span>
|
||||
</button>
|
||||
)
|
||||
);
|
||||
ShimmerButton.displayName = "ShimmerButton";
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface TextRevealProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
delay?: number;
|
||||
once?: boolean;
|
||||
stagger?: number;
|
||||
}
|
||||
|
||||
export function TextReveal({
|
||||
text,
|
||||
className,
|
||||
delay = 0,
|
||||
once = true,
|
||||
stagger = 0.055,
|
||||
}: TextRevealProps) {
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
const isInView = useInView(ref, { once, margin: "-60px" });
|
||||
const words = text.split(" ");
|
||||
|
||||
return (
|
||||
<span ref={ref} className={cn("inline", className)}>
|
||||
{words.map((word, i) => (
|
||||
<motion.span
|
||||
key={i}
|
||||
className="inline-block mr-[0.28em] last:mr-0"
|
||||
initial={{ opacity: 0, y: 22, filter: "blur(4px)" }}
|
||||
animate={
|
||||
isInView
|
||||
? { opacity: 1, y: 0, filter: "blur(0px)" }
|
||||
: { opacity: 0, y: 22, filter: "blur(4px)" }
|
||||
}
|
||||
transition={{
|
||||
duration: 0.55,
|
||||
delay: delay + i * stagger,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
>
|
||||
{word}
|
||||
</motion.span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'server-only';
|
||||
|
||||
const dictionaries = {
|
||||
en: () => import('../dictionaries/en.json').then((module) => module.default),
|
||||
tr: () => import('../dictionaries/tr.json').then((module) => module.default),
|
||||
};
|
||||
|
||||
export type Locale = keyof typeof dictionaries;
|
||||
export const i18n = {
|
||||
defaultLocale: 'tr',
|
||||
locales: ['tr', 'en'],
|
||||
} as const;
|
||||
|
||||
export const getDictionary = async (locale: Locale) => {
|
||||
return dictionaries[locale]?.() ?? dictionaries.tr();
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
+162
-12
@@ -1,26 +1,176 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
@theme {
|
||||
/* ── Core Palette ── */
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
|
||||
--color-moy-gold: #c9a96e;
|
||||
--color-moy-gold-light: #d6bc8b;
|
||||
--color-moy-gold-dark: #b39152;
|
||||
--color-moy-gold-faint: rgba(201, 169, 110, 0.12);
|
||||
--color-moy-gold-subtle: rgba(201, 169, 110, 0.25);
|
||||
|
||||
--color-moy-blue: #1c3552;
|
||||
--color-moy-blue-light: #2c4d75;
|
||||
--color-moy-blue-dark: #0f1f30;
|
||||
|
||||
--color-moy-green: #3a5342;
|
||||
--color-moy-green-light: #4a6854;
|
||||
|
||||
--color-moy-dark: #121212;
|
||||
--color-moy-dark-lighter: #1e1e1e;
|
||||
--color-moy-dark-border: #2c2c2c;
|
||||
|
||||
--color-moy-light: #f9f6f0;
|
||||
--color-moy-light-warm: #f0ead8;
|
||||
--color-moy-cream: #ede8de;
|
||||
|
||||
/* ── Typography ── */
|
||||
--font-serif: var(--font-cormorant);
|
||||
--font-sans: var(--font-montserrat);
|
||||
|
||||
/* ── Animations ── */
|
||||
--animate-shimmer: shimmer 2.8s linear infinite;
|
||||
--animate-marquee: marquee 28s linear infinite;
|
||||
--animate-marquee-reverse: marquee 28s linear infinite reverse;
|
||||
--animate-float: float 7s ease-in-out infinite;
|
||||
--animate-pulse-gold: pulse-gold 2.4s ease-in-out infinite;
|
||||
--animate-fade-up: fadeUp 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
--animate-scale-in: scaleIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
/* ── Raw CSS tokens (non-Tailwind) ── */
|
||||
:root {
|
||||
--background: #f9f6f0;
|
||||
--foreground: #121212;
|
||||
|
||||
--shadow-xs: 0 1px 2px rgba(0,0,0,0.06);
|
||||
--shadow-sm: 0 2px 8px rgba(0,0,0,0.08);
|
||||
--shadow-md: 0 4px 20px rgba(0,0,0,0.10);
|
||||
--shadow-lg: 0 8px 36px rgba(0,0,0,0.14);
|
||||
--shadow-xl: 0 16px 60px rgba(0,0,0,0.20);
|
||||
--shadow-gold: 0 4px 28px rgba(201,169,110,0.35);
|
||||
--shadow-gold-lg: 0 8px 48px rgba(201,169,110,0.45);
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 18px;
|
||||
--radius-xl: 28px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
--z-below: -1;
|
||||
--z-base: 0;
|
||||
--z-raised: 10;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-modal: 300;
|
||||
--z-toast: 400;
|
||||
|
||||
--ease-expo-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 600ms;
|
||||
--duration-slower: 1000ms;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--background: #121212;
|
||||
--foreground: #f9f6f0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Keyframes ── */
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-14px); }
|
||||
}
|
||||
|
||||
@keyframes pulse-gold {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(201,169,110,0); }
|
||||
50% { box-shadow: 0 0 0 10px rgba(201,169,110,0.15); }
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(28px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from { opacity: 0; transform: scale(0.93); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
@keyframes revealLine {
|
||||
from { transform: scaleX(0); }
|
||||
to { transform: scaleX(1); }
|
||||
}
|
||||
|
||||
/* ── Base ── */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-serif);
|
||||
}
|
||||
|
||||
/* ── Utility classes ── */
|
||||
.moy-shimmer {
|
||||
background: linear-gradient(
|
||||
105deg,
|
||||
transparent 35%,
|
||||
rgba(201, 169, 110, 0.45) 50%,
|
||||
transparent 65%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: var(--animate-shimmer);
|
||||
}
|
||||
|
||||
.moy-gold-line {
|
||||
display: block;
|
||||
width: 3.5rem;
|
||||
height: 1.5px;
|
||||
background: var(--color-moy-gold);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.moy-gold-line-center {
|
||||
display: block;
|
||||
width: 3.5rem;
|
||||
height: 1.5px;
|
||||
background: var(--color-moy-gold);
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Grain texture overlay for hero */
|
||||
.moy-grain::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
|
||||
pointer-events: none;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user