feat: global booking button — header nav, mobile menu, floating CTA
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,13 @@ import { Menu, X } from "lucide-react";
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { BRAND, NAV_LINKS } from "@/lib/constants";
|
import { BRAND, NAV_LINKS } from "@/lib/constants";
|
||||||
import { HeroLogo } from "@/components/ui/HeroLogo";
|
import { HeroLogo } from "@/components/ui/HeroLogo";
|
||||||
|
import { BookingModal } from "@/components/ui/BookingModal";
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const [scrolled, setScrolled] = useState(false);
|
const [scrolled, setScrolled] = useState(false);
|
||||||
const [activeSection, setActiveSection] = useState("");
|
const [activeSection, setActiveSection] = useState("");
|
||||||
|
const [bookingOpen, setBookingOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
@@ -19,6 +21,15 @@ export function Header() {
|
|||||||
return () => window.removeEventListener("scroll", handleScroll);
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Listen for booking open events from other components
|
||||||
|
useEffect(() => {
|
||||||
|
function onOpenBooking() {
|
||||||
|
setBookingOpen(true);
|
||||||
|
}
|
||||||
|
window.addEventListener("open-booking", onOpenBooking);
|
||||||
|
return () => window.removeEventListener("open-booking", onOpenBooking);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sectionIds = NAV_LINKS.map((l) => l.href.replace("#", ""));
|
const sectionIds = NAV_LINKS.map((l) => l.href.replace("#", ""));
|
||||||
const observers: IntersectionObserver[] = [];
|
const observers: IntersectionObserver[] = [];
|
||||||
@@ -99,6 +110,12 @@ export function Header() {
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<button
|
||||||
|
onClick={() => setBookingOpen(true)}
|
||||||
|
className="rounded-full bg-[#c9a96e] px-4 py-1.5 text-sm font-semibold text-black transition-all hover:bg-[#d4b87a] hover:shadow-lg hover:shadow-[#c9a96e]/20 cursor-pointer"
|
||||||
|
>
|
||||||
|
Записаться
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 md:hidden">
|
<div className="flex items-center gap-2 md:hidden">
|
||||||
@@ -136,8 +153,29 @@ export function Header() {
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setMenuOpen(false);
|
||||||
|
setBookingOpen(true);
|
||||||
|
}}
|
||||||
|
className="mt-2 w-full rounded-full bg-[#c9a96e] py-3 text-sm font-semibold text-black transition-all hover:bg-[#d4b87a] cursor-pointer"
|
||||||
|
>
|
||||||
|
Записаться
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Floating booking button — visible on scroll, mobile */}
|
||||||
|
<button
|
||||||
|
onClick={() => setBookingOpen(true)}
|
||||||
|
className={`fixed bottom-6 right-6 z-40 flex items-center gap-2 rounded-full bg-[#c9a96e] px-5 py-3 text-sm font-semibold text-black shadow-lg shadow-[#c9a96e]/25 transition-all duration-500 hover:bg-[#d4b87a] hover:shadow-xl hover:shadow-[#c9a96e]/30 cursor-pointer md:hidden ${
|
||||||
|
scrolled ? "translate-y-0 opacity-100" : "translate-y-16 opacity-0 pointer-events-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Записаться
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<BookingModal open={bookingOpen} onClose={() => setBookingOpen(false)} />
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { siteContent } from "@/data/content";
|
import { siteContent } from "@/data/content";
|
||||||
import { Button } from "@/components/ui/Button";
|
import { Button } from "@/components/ui/Button";
|
||||||
import { FloatingHearts } from "@/components/ui/FloatingHearts";
|
import { FloatingHearts } from "@/components/ui/FloatingHearts";
|
||||||
import { HeroLogo } from "@/components/ui/HeroLogo";
|
import { HeroLogo } from "@/components/ui/HeroLogo";
|
||||||
import { BookingModal } from "@/components/ui/BookingModal";
|
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
|
|
||||||
export function Hero() {
|
export function Hero() {
|
||||||
const { hero } = siteContent;
|
const { hero } = siteContent;
|
||||||
const [bookingOpen, setBookingOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative flex min-h-svh items-center justify-center overflow-hidden bg-[#050505]">
|
<section className="relative flex min-h-svh items-center justify-center overflow-hidden bg-[#050505]">
|
||||||
@@ -66,12 +63,10 @@ export function Hero() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="hero-cta mt-12">
|
<div className="hero-cta mt-12">
|
||||||
<Button size="lg" onClick={() => setBookingOpen(true)}>
|
<Button size="lg" onClick={() => window.dispatchEvent(new Event("open-booking"))}>
|
||||||
{hero.ctaText}
|
{hero.ctaText}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BookingModal open={bookingOpen} onClose={() => setBookingOpen(false)} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scroll indicator */}
|
{/* Scroll indicator */}
|
||||||
|
|||||||
Reference in New Issue
Block a user