first commit

This commit is contained in:
mstfyldz
2026-06-03 04:27:40 +03:00
parent dcfaf2723c
commit 48277fe8d2
22 changed files with 1610 additions and 117 deletions
+61
View File
@@ -0,0 +1,61 @@
"use client";
import { motion } from "framer-motion";
export default function About() {
return (
<section id="about" className="py-24 md:py-32 bg-primary relative">
<div className="container mx-auto px-6 md:px-12 flex flex-col md:flex-row items-center gap-16">
<motion.div
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8 }}
className="w-full md:w-1/2 relative"
>
<div className="relative aspect-[4/5] md:aspect-square w-full">
<div className="absolute inset-0 bg-accent1/10 translate-x-4 translate-y-4" />
<img
src="https://images.unsplash.com/photo-1574096079513-d8259312b785?q=80&w=1974&auto=format&fit=crop"
alt="Luna Cocktail Vibe"
className="w-full h-full object-cover grayscale-[30%] hover:grayscale-0 transition-all duration-700"
/>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-100px" }}
transition={{ duration: 0.8, delay: 0.2 }}
className="w-full md:w-1/2"
>
<h2 className="font-serif text-4xl md:text-5xl text-text-main mb-6">
Luna is not just a bar.<br />
<span className="text-accent1 italic">It's a lifestyle.</span>
</h2>
<div className="w-16 h-1 bg-accent2 mb-8" />
<p className="text-text-main/80 text-lg leading-relaxed font-light mb-6">
Muğla'nın kalbinde, rooftop konseptiyle premium bir kaçış noktası.
Golden hour esnasında eşsiz kokteyl deneyimiyle başlayan hikayemiz,
gecenin ilerleyen saatlerinde canlı müzik ve DJ performanslarıyla ritmini artırıyor.
</p>
<p className="text-text-main/80 text-lg leading-relaxed font-light mb-10">
Karanlık ama sıcak, lüks ama erişilebilir atmosferimizde; imza kokteyllerimiz,
Soul Club ve Latin Geceleri gibi özel konseptlerimizle
kendinizi müziğin ve lezzetin akışına bırakın.
</p>
<p className="font-serif text-xl text-text-main italic border-l-2 border-accent1 pl-4">
"Nights like these, only at Luna."
</p>
</motion.div>
</div>
</section>
);
}
+101
View File
@@ -0,0 +1,101 @@
"use client";
import { motion } from "framer-motion";
import { MagicCard } from "@/components/ui/MagicCard";
const cocktails = [
{
id: 1,
name: "Luna Sunset",
description: "Tequila, Mezcal, Passion Fruit, Chili, Lime",
price: "450 ₺",
image: "https://images.unsplash.com/photo-1513558161293-cdaf765ed2fd?q=80&w=1974&auto=format&fit=crop"
},
{
id: 2,
name: "Midnight Velvet",
description: "Bourbon, Blackberry, Vermouth, Angostura",
price: "480 ₺",
image: "https://images.unsplash.com/photo-1536935338788-846bb9981813?q=80&w=2069&auto=format&fit=crop"
},
{
id: 3,
name: "Golden Hour",
description: "Gin, Elderflower, Prosecco, Gold Dust",
price: "420 ₺",
image: "https://images.unsplash.com/photo-1551024709-8f23befc6f87?q=80&w=2157&auto=format&fit=crop"
}
];
export default function Cocktails() {
return (
<section id="cocktails" className="py-24 md:py-32 bg-primary relative border-t border-white/5">
<div className="container mx-auto px-6 md:px-12">
<div className="text-center mb-16 md:mb-24">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="font-serif text-4xl md:text-5xl text-text-main mb-6"
>
İmza Kokteyller
</motion.h2>
<motion.div
initial={{ opacity: 0, scale: 0 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}
className="w-16 h-1 bg-accent1 mx-auto"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
{cocktails.map((cocktail, index) => (
<motion.div
key={cocktail.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, delay: index * 0.2 }}
>
<MagicCard className="flex flex-col group relative">
<div className="aspect-[3/4] overflow-hidden relative mb-6">
<img
src={cocktail.image}
alt={cocktail.name}
className="w-full h-full object-cover grayscale-[20%] group-hover:grayscale-0 group-hover:scale-105 transition-all duration-700"
/>
</div>
<div className="flex justify-between items-start border-b border-white/10 pb-4">
<div>
<h3 className="font-serif text-2xl text-text-main mb-2 group-hover:text-accent1 transition-colors">{cocktail.name}</h3>
<p className="text-text-main/60 font-light text-sm max-w-[200px]">{cocktail.description}</p>
</div>
<span className="text-accent2 font-serif text-xl">{cocktail.price}</span>
</div>
</MagicCard>
</motion.div>
))}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.6 }}
className="mt-20 text-center"
>
<a
href="#contact"
className="inline-block border border-accent1 text-text-main hover:bg-accent1 hover:text-primary transition-colors px-8 py-3 uppercase tracking-widest text-sm font-semibold"
>
Tüm Menüyü İncele
</a>
</motion.div>
</div>
</section>
);
}
+115
View File
@@ -0,0 +1,115 @@
"use client";
import { motion } from "framer-motion";
import { Calendar, Clock } from "lucide-react";
import { MagicCard } from "@/components/ui/MagicCard";
const events = [
{
id: 1,
title: "Canlı Konser Gecesi",
artist: "Evrencan Gündüz & Uzaylılar",
date: "12 Temmuz Cuma",
time: "21:30 - 02:00",
category: "Live Music",
image: "https://images.unsplash.com/photo-1540039155732-680e7c58ed8a?q=80&w=2070&auto=format&fit=crop"
},
{
id: 2,
title: "Sunset DJ Party",
artist: "DJ Soul",
date: "14 Temmuz Pazar",
time: "18:00 - 23:00",
category: "Sunset",
image: "https://images.unsplash.com/photo-1571266028243-3716f02d2d2e?q=80&w=2070&auto=format&fit=crop"
},
{
id: 3,
title: "Latin Gecesi",
artist: "Havana Vibes",
date: "20 Temmuz Cumartesi",
time: "22:00 - 03:00",
category: "Dance",
image: "https://images.unsplash.com/photo-1508215885820-4585e5610924?q=80&w=2058&auto=format&fit=crop"
}
];
export default function Events() {
return (
<section id="events" className="py-24 md:py-32 bg-[#0d0d0d] relative">
<div className="container mx-auto px-6 md:px-12">
<div className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="font-serif text-4xl md:text-5xl text-text-main mb-4">Yaklaşan Etkinlikler</h2>
<p className="text-text-main/60 font-light text-lg">No talk, just music. Geceye yön veren performanslar.</p>
</motion.div>
<motion.a
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}
href="#contact"
className="text-accent1 hover:text-text-main transition-colors uppercase tracking-widest text-sm font-semibold flex items-center gap-2"
>
Tüm Programı Gör <span aria-hidden="true">&rarr;</span>
</motion.a>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{events.map((event, index) => (
<motion.div
key={event.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, delay: index * 0.2 }}
>
<MagicCard className="group relative bg-primary overflow-hidden border border-white/5 hover:border-accent1/50 transition-colors h-full">
<div className="aspect-[4/3] overflow-hidden relative">
<div className="absolute inset-0 bg-primary/40 group-hover:bg-transparent transition-colors duration-500 z-10" />
<img
src={event.image}
alt={event.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
/>
<div className="absolute top-4 left-4 z-20 bg-primary/80 backdrop-blur border border-white/10 px-3 py-1 uppercase text-[10px] tracking-widest text-accent1 font-semibold">
{event.category}
</div>
</div>
<div className="p-8">
<h3 className="font-serif text-2xl text-text-main mb-1 group-hover:text-accent1 transition-colors">{event.title}</h3>
<p className="text-text-main/70 font-light mb-6">{event.artist}</p>
<div className="space-y-3 mb-8">
<div className="flex items-center text-sm text-text-main/60 font-light">
<Calendar size={16} className="text-accent2 mr-3" />
{event.date}
</div>
<div className="flex items-center text-sm text-text-main/60 font-light">
<Clock size={16} className="text-accent2 mr-3" />
{event.time}
</div>
</div>
<a
href="#contact"
className="block w-full py-3 text-center border border-white/10 hover:bg-accent1 hover:text-primary hover:border-accent1 transition-all uppercase tracking-widest text-xs font-bold"
>
Rezervasyon
</a>
</div>
</MagicCard>
</motion.div>
))}
</div>
</div>
</section>
);
}
+124
View File
@@ -0,0 +1,124 @@
"use client";
import { motion } from "framer-motion";
import { MapPin, Phone } from "lucide-react";
export default function Footer() {
return (
<footer id="contact" className="bg-[#050505] pt-24 pb-12 relative border-t border-white/5">
<div className="container mx-auto px-6 md:px-12">
<div className="flex flex-col md:flex-row justify-between gap-16 mb-20">
{/* Brand Info */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="md:w-1/3"
>
<a href="#" className="inline-block mb-6">
<div className="flex flex-col group">
<span className="font-serif text-4xl text-text-main group-hover:text-accent1 transition-colors">
LUNA
</span>
<span className="text-[10px] uppercase tracking-[0.3em] text-accent1 mt-1 font-light">
Cocktail and More
</span>
</div>
</a>
<p className="text-text-main/60 font-light leading-relaxed mb-8">
Muğla'nın kalbinde, premium kokteyl ve müzik deneyimi.
Sunset'ten geceye uzanan eşsiz bir atmosfer.
</p>
<div className="flex space-x-4">
<a href="https://www.instagram.com/lunacocktaill/" target="_blank" rel="noreferrer" className="w-10 h-10 border border-white/10 rounded-full flex items-center justify-center text-text-main hover:bg-accent1 hover:text-primary hover:border-accent1 transition-all">
<svg
xmlns="http://www.w3.org/2000/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>
</div>
</motion.div>
{/* Contact Info */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}
className="md:w-1/3 space-y-6"
>
<h4 className="font-serif text-2xl text-text-main mb-6">İletişim</h4>
<a href="tel:05321610234" className="flex items-start group">
<Phone className="text-accent2 mt-1 mr-4 group-hover:text-accent1 transition-colors" size={20} />
<div>
<span className="block text-[10px] uppercase tracking-widest text-text-main/50 mb-1">Rezervasyon</span>
<span className="text-lg text-text-main group-hover:text-accent1 transition-colors font-light">0532 161 02 34</span>
</div>
</a>
<div className="flex items-start group">
<MapPin className="text-accent2 mt-1 mr-4 group-hover:text-accent1 transition-colors" size={20} />
<div>
<span className="block text-[10px] uppercase tracking-widest text-text-main/50 mb-1">Konum</span>
<span className="text-lg text-text-main font-light leading-relaxed">
Muğla Merkez<br />
Türkiye
</span>
</div>
</div>
</motion.div>
{/* Quick Form */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
className="md:w-1/3"
>
<h4 className="font-serif text-2xl text-text-main mb-6">Bize Ulaşın</h4>
<form className="space-y-4" onSubmit={(e) => e.preventDefault()}>
<input
type="text"
placeholder="Adınız Soyadınız"
className="w-full bg-primary/50 border border-white/10 px-4 py-3 text-sm text-text-main focus:outline-none focus:border-accent1 transition-colors"
/>
<input
type="tel"
placeholder="Telefon Numaranız"
className="w-full bg-primary/50 border border-white/10 px-4 py-3 text-sm text-text-main focus:outline-none focus:border-accent1 transition-colors"
/>
<button
type="submit"
className="w-full bg-accent1 text-primary py-3 uppercase tracking-widest text-xs font-bold hover:bg-accent2 transition-colors"
>
Gönder
</button>
</form>
</motion.div>
</div>
<div className="border-t border-white/5 pt-8 flex flex-col md:flex-row justify-between items-center text-xs text-text-main/40 font-light">
<p>&copy; {new Date().getFullYear()} Luna Cocktail & More. Tüm hakları saklıdır.</p>
<p className="mt-2 md:mt-0">Tasarım & Geliştirme</p>
</div>
</div>
</footer>
);
}
+38
View File
@@ -0,0 +1,38 @@
"use client";
import { motion } from "framer-motion";
const photos = [
{ id: 1, src: "https://images.unsplash.com/photo-1566417713940-fe7c737a9ef2?q=80&w=2029&auto=format&fit=crop", style: "md:col-span-2 md:row-span-2" },
{ id: 2, src: "https://images.unsplash.com/photo-1542556398-4432b571ebf1?q=80&w=2070&auto=format&fit=crop", style: "md:col-span-1 md:row-span-1" },
{ id: 3, src: "https://images.unsplash.com/photo-1470229722913-7c092bb21443?q=80&w=1968&auto=format&fit=crop", style: "md:col-span-1 md:row-span-1" },
{ id: 4, src: "https://images.unsplash.com/photo-1525268771113-32d9e9021a97?q=80&w=2080&auto=format&fit=crop", style: "md:col-span-1 md:row-span-2" },
{ id: 5, src: "https://images.unsplash.com/photo-1514361892635-6b07e31e75f9?q=80&w=2070&auto=format&fit=crop", style: "md:col-span-1 md:row-span-1" },
{ id: 6, src: "https://images.unsplash.com/photo-1485872299829-c673f5194813?q=80&w=2054&auto=format&fit=crop", style: "md:col-span-1 md:row-span-1" }
];
export default function Gallery() {
return (
<section id="gallery" className="py-2 bg-primary">
<div className="grid grid-cols-1 md:grid-cols-4 gap-2 md:auto-rows-[300px]">
{photos.map((photo, index) => (
<motion.div
key={photo.id}
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, delay: index * 0.1 }}
className={`relative overflow-hidden group ${photo.style}`}
>
<div className="absolute inset-0 bg-primary/20 group-hover:bg-transparent transition-colors duration-500 z-10" />
<img
src={photo.src}
alt={`Gallery Image ${photo.id}`}
className="w-full h-full object-cover grayscale-[40%] group-hover:grayscale-0 group-hover:scale-105 transition-all duration-700"
/>
</motion.div>
))}
</div>
</section>
);
}
+83
View File
@@ -0,0 +1,83 @@
"use client";
import { motion } from "framer-motion";
import { Meteors } from "@/components/ui/Meteors";
export default function Hero() {
return (
<section className="relative h-screen w-full flex items-center justify-center overflow-hidden">
{/* Background Image / Video overlay */}
<div className="absolute inset-0 z-0">
<div className="absolute inset-0 bg-primary/60 mix-blend-multiply z-10" />
<div className="absolute inset-0 bg-gradient-to-b from-primary/30 via-transparent to-primary z-10" />
<img
src="https://images.unsplash.com/photo-1514362545857-3bc16c4c7d1b?q=80&w=2070&auto=format&fit=crop"
alt="Luna Cocktail Rooftop Vibe"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 z-20 overflow-hidden">
<Meteors number={15} />
</div>
</div>
<div className="relative z-20 text-center px-4 flex flex-col items-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
className="mb-6"
>
<div className="inline-flex items-center rounded-full border border-accent1/25 bg-accent1/10 px-5 py-2 backdrop-blur-sm overflow-hidden relative">
<span className="text-accent1 uppercase tracking-[0.25em] text-xs font-semibold relative z-10">
Muğla'nın En Özel Terası
</span>
<span className="pointer-events-none absolute inset-0 animate-shimmer-slide bg-gradient-to-r from-transparent via-white/15 to-transparent" />
</div>
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
className="font-serif text-5xl md:text-7xl lg:text-8xl text-text-main mb-6 leading-tight max-w-4xl"
>
From Sunset <br />
<span className="text-accent1 italic">to Night</span>
</motion.h1>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="text-text-main/80 text-lg md:text-xl font-light mb-10 max-w-xl mx-auto"
>
Güneş ufukta kaybolurken müzik yükseliyor, kokteyller doluyor ve gece yeni başlıyor.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
>
<a
href="#contact"
className="inline-block bg-transparent border border-accent1 text-accent1 hover:bg-accent1 hover:text-primary transition-colors duration-300 px-10 py-4 uppercase tracking-[0.2em] text-sm font-bold"
>
Rezervasyon Yap / Book Now
</a>
</motion.div>
</div>
{/* Scroll Indicator */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.5, duration: 1 }}
className="absolute bottom-10 left-1/2 -translate-x-1/2 z-20 flex flex-col items-center gap-2"
>
<span className="text-[10px] uppercase tracking-widest text-text-main/50">Scroll</span>
<div className="w-[1px] h-12 bg-gradient-to-b from-text-main/50 to-transparent" />
</motion.div>
</section>
);
}
+113
View File
@@ -0,0 +1,113 @@
"use client";
import { useState, useEffect } from "react";
import { Menu, X } from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
export default function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const navLinks = [
{ name: "Hakkımızda", href: "#about" },
{ name: "Etkinlikler", href: "#events" },
{ name: "Kokteyller", href: "#cocktails" },
{ name: "Galeri", href: "#gallery" },
{ name: "Rezervasyon", href: "#contact" },
];
return (
<nav
className={`fixed top-0 left-0 w-full z-50 transition-all duration-300 ${
isScrolled ? "bg-primary/95 backdrop-blur-md py-4 shadow-lg border-b border-white/5" : "bg-transparent py-6"
}`}
>
<div className="container mx-auto px-6 md:px-12 flex justify-between items-center">
{/* Left Side Links (Desktop) */}
<div className="hidden md:flex space-x-8 w-1/3 justify-start">
{navLinks.slice(0, 2).map((link) => (
<a
key={link.name}
href={link.href}
className="text-text-main/80 hover:text-accent1 transition-colors uppercase tracking-widest text-xs font-semibold"
>
{link.name}
</a>
))}
</div>
{/* Logo (Centered) */}
<div className="w-1/3 flex justify-center text-center">
<a href="#" className="flex flex-col items-center group">
<span className="font-serif text-3xl md:text-4xl text-text-main group-hover:text-accent1 transition-colors border-2 border-transparent group-hover:border-accent1 rounded-full px-6 py-2">
LUNA
</span>
<span className="text-[10px] uppercase tracking-[0.3em] text-accent1 mt-1 font-light opacity-80">
Cocktail and More
</span>
</a>
</div>
{/* Right Side Links (Desktop) */}
<div className="hidden md:flex space-x-8 w-1/3 justify-end items-center">
{navLinks.slice(2, 4).map((link) => (
<a
key={link.name}
href={link.href}
className="text-text-main/80 hover:text-accent1 transition-colors uppercase tracking-widest text-xs font-semibold"
>
{link.name}
</a>
))}
<a
href="#contact"
className="bg-accent1 text-primary px-6 py-2 uppercase tracking-widest text-xs font-bold hover:bg-accent2 transition-colors"
>
Rezervasyon
</a>
</div>
{/* Mobile Menu Toggle */}
<div className="md:hidden w-1/3 flex justify-end">
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="text-text-main hover:text-accent1 focus:outline-none transition-colors"
>
{isMobileMenuOpen ? <X size={28} /> : <Menu size={28} />}
</button>
</div>
</div>
{/* Mobile Menu */}
<AnimatePresence>
{isMobileMenuOpen && (
<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-primary/95 backdrop-blur-lg border-t border-white/10 shadow-2xl py-8 flex flex-col items-center space-y-6 md:hidden"
>
{navLinks.map((link) => (
<a
key={link.name}
href={link.href}
onClick={() => setIsMobileMenuOpen(false)}
className="text-text-main hover:text-accent1 text-lg uppercase tracking-widest font-medium transition-colors"
>
{link.name}
</a>
))}
</motion.div>
)}
</AnimatePresence>
</nav>
);
}
+39
View File
@@ -0,0 +1,39 @@
"use client";
import { useCallback, useRef, useState } from "react";
interface MagicCardProps {
children: React.ReactNode;
className?: string;
}
export function MagicCard({ children, className = "" }: MagicCardProps) {
const ref = useRef<HTMLDivElement>(null);
const [pos, setPos] = useState({ x: -999, y: -999 });
const [hovered, setHovered] = useState(false);
const onMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}, []);
return (
<div
ref={ref}
onMouseMove={onMouseMove}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => { setHovered(false); setPos({ x: -999, y: -999 }); }}
className={className}
>
{children}
<div
className="pointer-events-none absolute inset-0 z-20 transition-opacity duration-500"
style={{
opacity: hovered ? 1 : 0,
background: `radial-gradient(350px circle at ${pos.x}px ${pos.y}px, rgba(200,133,58,0.10), transparent 60%)`,
}}
/>
</div>
);
}
+39
View File
@@ -0,0 +1,39 @@
"use client";
import { useEffect, useState } from "react";
interface MeteorItem {
id: number;
left: string;
delay: string;
duration: string;
}
export function Meteors({ number = 15 }: { number?: number }) {
const [items, setItems] = useState<MeteorItem[]>([]);
useEffect(() => {
setItems(
Array.from({ length: number }, (_, i) => ({
id: i,
left: `${Math.random() * 100}%`,
delay: `${(Math.random() * 3).toFixed(2)}s`,
duration: `${(Math.random() * 6 + 4).toFixed(1)}s`,
}))
);
}, [number]);
return (
<>
{items.map((m) => (
<span
key={m.id}
className="pointer-events-none absolute top-0 size-px rotate-[215deg] animate-meteor rounded-full bg-white/70 shadow-[0_0_0_1px_rgba(255,255,255,0.08)]"
style={{ left: m.left, animationDelay: m.delay, animationDuration: m.duration }}
>
<span className="absolute top-1/2 left-0 -translate-y-1/2 h-px w-20 bg-gradient-to-r from-white/60 to-transparent" />
</span>
))}
</>
);
}