feat: about stats cards, pricing popular badge, back-to-top button
- About: add 3 stat cards (13 trainers, 6 styles, 2 locations) - Pricing: highlight first plan with gold "Популярный" badge - BackToTop: floating gold button appears after 600px scroll Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { Classes } from "@/components/sections/Classes";
|
||||
import { Pricing } from "@/components/sections/Pricing";
|
||||
import { FAQ } from "@/components/sections/FAQ";
|
||||
import { Contact } from "@/components/sections/Contact";
|
||||
import { BackToTop } from "@/components/ui/BackToTop";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
@@ -16,6 +17,7 @@ export default function HomePage() {
|
||||
<Pricing />
|
||||
<FAQ />
|
||||
<Contact />
|
||||
<BackToTop />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { Users, Layers, MapPin } from "lucide-react";
|
||||
import { siteContent } from "@/data/content";
|
||||
import { SectionHeading } from "@/components/ui/SectionHeading";
|
||||
import { Reveal } from "@/components/ui/Reveal";
|
||||
|
||||
const stats = [
|
||||
{ icon: <Users size={22} />, value: "13", label: "тренеров" },
|
||||
{ icon: <Layers size={22} />, value: "6", label: "направлений" },
|
||||
{ icon: <MapPin size={22} />, value: "2", label: "зала в Минске" },
|
||||
];
|
||||
|
||||
export function About() {
|
||||
const { about } = siteContent;
|
||||
|
||||
@@ -22,6 +29,28 @@ export function About() {
|
||||
</Reveal>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<Reveal>
|
||||
<div className="mx-auto mt-14 grid max-w-3xl grid-cols-3 gap-4 sm:gap-8">
|
||||
{stats.map((stat, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="group flex flex-col items-center gap-3 rounded-2xl border border-neutral-200 bg-white/50 p-6 transition-all duration-300 hover:border-[#c9a96e]/30 sm:p-8 dark:border-white/[0.06] dark:bg-white/[0.02] dark:hover:border-[#c9a96e]/20"
|
||||
>
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-[#c9a96e]/10 text-[#a08050] transition-colors group-hover:bg-[#c9a96e]/20 dark:text-[#d4b87a]">
|
||||
{stat.icon}
|
||||
</div>
|
||||
<span className="font-display text-3xl font-bold text-neutral-900 sm:text-4xl dark:text-white">
|
||||
{stat.value}
|
||||
</span>
|
||||
<span className="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{stat.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -54,28 +54,38 @@ export function Pricing() {
|
||||
{pricing.subtitle}
|
||||
</p>
|
||||
<div className="overflow-hidden rounded-2xl border border-neutral-200 dark:border-white/[0.06]">
|
||||
{pricing.items.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`group flex items-center justify-between gap-4 px-6 py-5 transition-colors hover:bg-neutral-50 dark:hover:bg-white/[0.03] ${
|
||||
i > 0 ? "border-t border-neutral-100 dark:border-white/[0.04]" : ""
|
||||
}`}
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
{pricing.items.map((item, i) => {
|
||||
const isPopular = i === 0;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`group flex items-center justify-between gap-4 px-6 py-5 transition-colors hover:bg-neutral-50 dark:hover:bg-white/[0.03] ${
|
||||
i > 0 ? "border-t border-neutral-100 dark:border-white/[0.04]" : ""
|
||||
} ${isPopular ? "bg-[#c9a96e]/[0.04] dark:bg-[#c9a96e]/[0.03]" : ""}`}
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="font-medium text-neutral-900 dark:text-white">
|
||||
{item.name}
|
||||
</p>
|
||||
{isPopular && (
|
||||
<span className="rounded-full bg-[#c9a96e]/15 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-[#a08050] dark:text-[#d4b87a]">
|
||||
Популярный
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{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-[#a08050] dark:text-[#d4b87a]">
|
||||
{item.price}
|
||||
</span>
|
||||
</div>
|
||||
<span className="shrink-0 font-display text-xl font-bold text-[#a08050] dark:text-[#d4b87a]">
|
||||
{item.price}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
28
src/components/ui/BackToTop.tsx
Normal file
28
src/components/ui/BackToTop.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { ChevronUp } from "lucide-react";
|
||||
|
||||
export function BackToTop() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function handleScroll() {
|
||||
setVisible(window.scrollY > 600);
|
||||
}
|
||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
||||
aria-label="Наверх"
|
||||
className={`fixed bottom-6 right-6 z-40 flex h-10 w-10 items-center justify-center rounded-full border border-[#c9a96e]/30 bg-black/60 text-[#d4b87a] backdrop-blur-sm transition-all duration-300 hover:bg-[#c9a96e]/20 hover:border-[#c9a96e]/50 ${
|
||||
visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0 pointer-events-none"
|
||||
}`}
|
||||
>
|
||||
<ChevronUp size={18} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user