bac46aeb34
Design direction: "Dark Luxury Gold" — more breathing room, bolder headings, frosted glass cards, and richer gold accents. - Section padding: py-20→py-24, sm:py-32→sm:py-36, px-6→sm:px-10 - SectionHeading: text-6xl→7xl on lg, tracking-wide→wider, longer gold gradient underline with via-gold/40 - Section glow: larger radius (800px), positioned higher (-40px), elliptical shape for dark mode - Glass card: backdrop-blur-md→lg in dark, border white/8%, hover with subtle gold glow shadow - Section divider: wider gradient spread with 5% transparent edges - About/FAQ/Pricing/DayCard: frosted glass in dark mode with backdrop-blur-md, subtle gold glow on hover
210 lines
9.3 KiB
TypeScript
210 lines
9.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useRef } from "react";
|
|
import { CreditCard, Building2, ScrollText, Crown, Sparkles } from "lucide-react";
|
|
import { SectionHeading } from "@/components/ui/SectionHeading";
|
|
import { Reveal } from "@/components/ui/Reveal";
|
|
import type { SiteContent } from "@/types/content";
|
|
|
|
type Tab = "prices" | "rental" | "rules";
|
|
|
|
interface PricingProps {
|
|
data: SiteContent["pricing"];
|
|
}
|
|
|
|
export function Pricing({ data: pricing }: PricingProps) {
|
|
if (!pricing?.items) return null;
|
|
const [activeTab, setActiveTab] = useState<Tab>("prices");
|
|
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
const tabs: { id: Tab; label: string; icon: React.ReactNode }[] = [
|
|
{ id: "prices", label: "Абонементы", icon: <CreditCard size={16} /> },
|
|
{ id: "rental", label: "Аренда зала", icon: <Building2 size={16} /> },
|
|
{ id: "rules", label: "Правила", icon: <ScrollText size={16} /> },
|
|
];
|
|
|
|
function handleTabKeyDown(e: React.KeyboardEvent, index: number) {
|
|
let nextIndex: number | null = null;
|
|
if (e.key === "ArrowRight") {
|
|
nextIndex = (index + 1) % tabs.length;
|
|
} else if (e.key === "ArrowLeft") {
|
|
nextIndex = (index - 1 + tabs.length) % tabs.length;
|
|
}
|
|
if (nextIndex !== null) {
|
|
e.preventDefault();
|
|
setActiveTab(tabs[nextIndex].id);
|
|
tabRefs.current[nextIndex]?.focus();
|
|
}
|
|
}
|
|
|
|
// Split items: featured (big card) vs regular
|
|
const featuredItem = pricing.items.find((item) => item.featured);
|
|
const regularItems = pricing.items.filter((item) => !item.featured);
|
|
|
|
return (
|
|
<section id="pricing" className="section-glow relative section-padding bg-neutral-50 dark:bg-neutral-950">
|
|
<div className="section-divider absolute top-0 left-0 right-0" />
|
|
<div className="section-container">
|
|
<Reveal>
|
|
<SectionHeading centered>{pricing.title}</SectionHeading>
|
|
</Reveal>
|
|
|
|
{/* Tabs */}
|
|
<Reveal>
|
|
<div role="tablist" aria-label="Разделы цен" className="mt-12 flex flex-wrap justify-center gap-2">
|
|
{tabs.map((tab, index) => (
|
|
<button
|
|
key={tab.id}
|
|
ref={(el) => { tabRefs.current[index] = el; }}
|
|
role="tab"
|
|
aria-selected={activeTab === tab.id}
|
|
aria-controls={`tabpanel-${tab.id}`}
|
|
id={`tab-${tab.id}`}
|
|
tabIndex={activeTab === tab.id ? 0 : -1}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
onKeyDown={(e) => handleTabKeyDown(e, index)}
|
|
className={`inline-flex items-center gap-2 rounded-full px-6 py-2.5 text-sm font-medium transition-all duration-300 cursor-pointer ${
|
|
activeTab === tab.id
|
|
? "bg-gold text-black shadow-lg shadow-gold/25"
|
|
: "bg-white border border-neutral-300 text-neutral-600 hover:bg-neutral-50 hover:border-neutral-400 dark:border-transparent dark:bg-white/[0.06] dark:text-neutral-300 dark:hover:bg-white/[0.1]"
|
|
}`}
|
|
>
|
|
{tab.icon}
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</Reveal>
|
|
|
|
{/* Prices tab */}
|
|
<div id="tabpanel-prices" role="tabpanel" aria-labelledby="tab-prices" className={activeTab === "prices" ? "block" : "hidden"}>
|
|
<div className="mx-auto mt-10 max-w-4xl">
|
|
<p className="mb-8 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
|
{pricing.subtitle}
|
|
</p>
|
|
|
|
{/* Cards grid */}
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
{regularItems.map((item, i) => {
|
|
const isPopular = item.popular ?? false;
|
|
return (
|
|
<div
|
|
key={i}
|
|
className={`group relative rounded-2xl border p-5 transition-all duration-300 ${
|
|
isPopular
|
|
? "border-gold/40 bg-gradient-to-br from-gold/10 via-transparent to-gold/5 dark:from-gold/[0.07] dark:to-gold/[0.02] shadow-lg shadow-gold/10"
|
|
: "border-neutral-200 bg-white shadow-sm shadow-gold/[0.04] hover:shadow-md hover:shadow-gold/[0.08] dark:border-white/[0.08] dark:bg-white/[0.03] dark:backdrop-blur-md dark:shadow-none dark:hover:border-white/[0.15] dark:hover:shadow-[0_0_20px_rgba(201,169,110,0.05)]"
|
|
}`}
|
|
>
|
|
{/* Popular badge */}
|
|
{isPopular && (
|
|
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
|
<span className="inline-flex items-center gap-1 rounded-full bg-gold px-3 py-1 text-[10px] font-bold uppercase tracking-wider text-black shadow-md shadow-gold/30">
|
|
<Sparkles size={10} />
|
|
Популярный
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className={isPopular ? "mt-1" : ""}>
|
|
{/* Name */}
|
|
<p className={`text-sm font-medium ${isPopular ? "text-gold-dark dark:text-gold-light" : "text-neutral-700 dark:text-neutral-300"}`}>
|
|
{item.name}
|
|
</p>
|
|
|
|
{/* Note */}
|
|
{item.note && (
|
|
<p className="mt-1 text-xs text-neutral-500 dark:text-neutral-500">
|
|
{item.note}
|
|
</p>
|
|
)}
|
|
|
|
{/* Price */}
|
|
<p className={`mt-3 font-display text-xl sm:text-2xl font-bold ${isPopular ? "text-gold" : "text-neutral-900 dark:text-white"}`}>
|
|
{item.price}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Featured — big card */}
|
|
{featuredItem && (
|
|
<div className="mt-6 w-full team-card-glitter rounded-2xl border border-gold/30 bg-gradient-to-r from-gold/10 via-gold/5 to-gold/10 dark:from-gold/[0.06] dark:via-transparent dark:to-gold/[0.06] p-6 sm:p-8">
|
|
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-between">
|
|
<div className="text-center sm:text-left">
|
|
<div className="flex items-center justify-center gap-2 sm:justify-start">
|
|
<Crown size={18} className="text-gold" />
|
|
<p className="text-lg font-bold text-neutral-900 dark:text-white">
|
|
{featuredItem.name}
|
|
</p>
|
|
</div>
|
|
{featuredItem.note && (
|
|
<p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
|
{featuredItem.note}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<p className="shrink-0 font-display text-2xl sm:text-3xl font-bold text-gold">
|
|
{featuredItem.price}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Rental tab */}
|
|
<div id="tabpanel-rental" role="tabpanel" aria-labelledby="tab-rental" className={activeTab === "rental" ? "block" : "hidden"}>
|
|
{pricing.rentalSubtitle && (
|
|
<p className="mx-auto mt-6 max-w-2xl text-center text-sm text-neutral-500 dark:text-neutral-400">
|
|
{pricing.rentalSubtitle}
|
|
</p>
|
|
)}
|
|
<div className="mx-auto mt-10 max-w-2xl space-y-3">
|
|
{pricing.rentalItems.map((item, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center justify-between gap-4 rounded-2xl border border-neutral-200 bg-white px-6 py-5 dark:border-white/[0.06] dark:bg-neutral-950"
|
|
>
|
|
<div>
|
|
<p className="font-medium text-neutral-900 dark:text-white">
|
|
{item.name}
|
|
</p>
|
|
{item.note && (
|
|
<p className="mt-0.5 text-sm text-neutral-500 dark:text-neutral-400">
|
|
{item.note}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<span className="shrink-0 font-display text-xl font-bold text-gold-dark dark:text-gold-light">
|
|
{item.price}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Rules tab */}
|
|
<div id="tabpanel-rules" role="tabpanel" aria-labelledby="tab-rules" className={activeTab === "rules" ? "block" : "hidden"}>
|
|
<div className="mx-auto mt-10 max-w-2xl space-y-3">
|
|
{pricing.rules.map((rule, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex gap-4 rounded-2xl border border-neutral-200 bg-white px-5 py-4 dark:border-white/[0.06] dark:bg-neutral-950"
|
|
>
|
|
<span className="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-gold/10 text-xs font-bold text-gold-dark dark:bg-gold/10 dark:text-gold-light">
|
|
{i + 1}
|
|
</span>
|
|
<p className="text-sm leading-relaxed text-neutral-700 dark:text-neutral-300">
|
|
{rule}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|