feat: landing page (hero, features, pricing, how-to, FAQ)

This commit is contained in:
2026-05-20 12:22:58 +03:00
parent 51f459c230
commit 0be6b30713
6 changed files with 234 additions and 1 deletions
+32
View File
@@ -0,0 +1,32 @@
import { useState } from "react";
import { ChevronDown } from "lucide-react";
const faqs = [
{ q: "На каких устройствах работает?", a: "iOS, Android, Windows, macOS, Linux — на всех. Одна подписка до 5 устройств." },
{ q: "Какие протоколы используются?", a: "WireGuard, AmneziaWG и VLESS. Обходят любые блокировки, включая DPI." },
{ q: "Есть ли ограничения по трафику?", a: "Нет. Все тарифы — неограниченный трафик на весь срок подписки." },
{ q: "Как быстро приходит доступ?", a: "Мгновенно после оплаты. QR-код для подключения доступен сразу в кабинете." },
{ q: "Как продлить подписку?", a: "В личном кабинете раздел «Мои ключи» → кнопка продлить, или через Telegram-бот." },
{ q: "Возможен возврат средств?", a: "Да, в течение 24 часов после покупки если VPN не заработал. Пишите в поддержку." },
];
export function FAQSection() {
const [open, setOpen] = useState<number | null>(null);
return (
<section id="faq" className="py-24 max-w-3xl mx-auto px-4">
<h2 className="text-4xl font-bold text-center mb-12">Частые вопросы</h2>
<div className="space-y-2">
{faqs.map(({ q, a }, i) => (
<div key={i} className="rounded-xl border border-white/[0.09] bg-white/[0.04] overflow-hidden">
<button onClick={() => setOpen(open === i ? null : i)}
className="w-full flex justify-between items-center px-5 py-4 text-left font-medium hover:bg-white/5 transition-colors">
{q}
<ChevronDown size={18} className={`text-white/40 transition-transform ${open === i ? "rotate-180" : ""}`} />
</button>
{open === i && <div className="px-5 pb-4 text-sm text-white/50">{a}</div>}
</div>
))}
</div>
</section>
);
}
@@ -0,0 +1,31 @@
import { GlassCard } from "../ui/GlassCard";
import { Zap, Lock, Smartphone, Globe, Headphones, Shield } from "lucide-react";
const features = [
{ icon: Zap, title: "До 10 Гбит/с", desc: "Высокоскоростные серверы без троттлинга" },
{ icon: Lock, title: "Шифрование", desc: "AES-256 + ChaCha20. Никаких логов." },
{ icon: Smartphone, title: "До 5 устройств", desc: "Один аккаунт на все ваши гаджеты" },
{ icon: Globe, title: "WireGuard + VLESS", desc: "Современные протоколы, работают везде" },
{ icon: Headphones, title: "Поддержка 24/7", desc: "Telegram-бот отвечает мгновенно" },
{ icon: Shield, title: "AmneziaWG", desc: "Обходит DPI и блокировки провайдеров" },
];
export function FeaturesSection() {
return (
<section className="py-24 max-w-6xl mx-auto px-4">
<h2 className="text-4xl font-bold text-center mb-4">Почему Aura?</h2>
<p className="text-white/40 text-center mb-12">Всё что нужно для безопасного интернета</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
{features.map(({ icon: Icon, title, desc }) => (
<GlassCard key={title}>
<div className="w-11 h-11 rounded-xl bg-accent/15 border border-accent/20 flex items-center justify-center mb-4">
<Icon size={22} className="text-accent" />
</div>
<h3 className="font-semibold mb-1">{title}</h3>
<p className="text-sm text-white/45">{desc}</p>
</GlassCard>
))}
</div>
</section>
);
}
+58
View File
@@ -0,0 +1,58 @@
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
import { Button } from "../ui/Button";
import { Shield, Zap, Lock } from "lucide-react";
export function HeroSection() {
return (
<section className="relative min-h-screen flex items-center justify-center overflow-hidden">
{/* Background glow */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-1/3 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full bg-accent/10 blur-[120px]" />
<div className="absolute top-2/3 left-1/4 w-[300px] h-[300px] rounded-full bg-indigo-500/10 blur-[80px]" />
</div>
{/* Floating icons */}
<motion.div animate={{ y: [0, -12, 0] }} transition={{ duration: 4, repeat: Infinity }}
className="absolute top-32 right-[15%] text-accent/30">
<Shield size={48} />
</motion.div>
<motion.div animate={{ y: [0, 10, 0] }} transition={{ duration: 3.5, repeat: Infinity }}
className="absolute bottom-40 left-[10%] text-indigo-400/20">
<Lock size={36} />
</motion.div>
{/* Content */}
<div className="relative z-10 max-w-3xl mx-auto px-4 text-center">
<motion.div initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6 }}>
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-accent/30 bg-accent/10 text-accent text-sm font-medium mb-6">
<Zap size={14} /> Быстро. Безопасно. Анонимно.
</div>
<h1 className="text-5xl md:text-7xl font-black mb-6 leading-tight">
<span className="bg-gradient-to-r from-white via-purple-200 to-indigo-300 bg-clip-text text-transparent">
Свобода
</span>
<br />
<span className="text-white/80">в сети</span>
</h1>
<p className="text-xl text-white/50 mb-10 max-w-xl mx-auto">
VPN-сервис нового поколения. WireGuard и VLESS протоколы, мгновенное подключение через QR-код.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link to="/register">
<Button className="text-base px-8 py-3.5 w-full sm:w-auto">
Подключиться сейчас
</Button>
</Link>
<Link to="/login">
<Button variant="secondary" className="text-base px-8 py-3.5 w-full sm:w-auto">
Войти в кабинет
</Button>
</Link>
</div>
<p className="text-sm text-white/30 mt-6">Также доступно в <a href="https://t.me/cricket_vpn_bot" className="text-accent/70 hover:text-accent">Telegram боте</a></p>
</motion.div>
</div>
</section>
);
}
+36
View File
@@ -0,0 +1,36 @@
import { ShoppingCart, Download, QrCode, Wifi } from "lucide-react";
const steps = [
{ icon: ShoppingCart, title: "Купить подписку", desc: "Выбери тариф и оплати через СБП или карту" },
{ icon: Download, title: "Скачай приложение", desc: "Hiddify, V2rayTun или AmneziaVPN — для любого устройства" },
{ icon: QrCode, title: "Отсканируй QR", desc: "Открой личный кабинет и отсканируй QR прямо в приложении" },
{ icon: Wifi, title: "Работает!", desc: "VPN подключён. Безопасный и быстрый интернет" },
];
export function HowToSection() {
return (
<section className="py-24 max-w-5xl mx-auto px-4">
<h2 className="text-4xl font-bold text-center mb-4">Как подключиться</h2>
<p className="text-white/40 text-center mb-16">4 простых шага</p>
<div className="relative">
<div className="hidden md:block absolute top-8 left-[12%] right-[12%] h-px bg-gradient-to-r from-transparent via-accent/30 to-transparent" />
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
{steps.map(({ icon: Icon, title, desc }, i) => (
<div key={title} className="flex flex-col items-center text-center">
<div className="relative mb-5">
<div className="w-16 h-16 rounded-2xl bg-accent/15 border border-accent/25 flex items-center justify-center">
<Icon size={28} className="text-accent" />
</div>
<span className="absolute -top-2 -right-2 w-6 h-6 rounded-full bg-accent text-xs font-black flex items-center justify-center">
{i + 1}
</span>
</div>
<h3 className="font-semibold mb-2">{title}</h3>
<p className="text-sm text-white/40">{desc}</p>
</div>
))}
</div>
</div>
</section>
);
}
+54
View File
@@ -0,0 +1,54 @@
import { useEffect, useState } from "react";
import { api } from "../../api/client";
import { GlassCard } from "../ui/GlassCard";
import { Button } from "../ui/Button";
import { Check } from "lucide-react";
import { Link } from "react-router-dom";
interface Tariff { id: number; name: string; duration_days: number; price_rub: number; }
export function PricingSection() {
const [tariffs, setTariffs] = useState<Tariff[]>([]);
useEffect(() => {
api.action("tariffs").then((r) => { if (r.ok) setTariffs(r.tariffs); });
}, []);
const perks = ["Неограниченный трафик", "До 5 устройств", "WireGuard + VLESS", "Поддержка 24/7"];
return (
<section id="pricing" className="py-24 max-w-6xl mx-auto px-4">
<h2 className="text-4xl font-bold text-center mb-4">Тарифы</h2>
<p className="text-white/40 text-center mb-12">Без скрытых платежей</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 justify-items-center">
{tariffs.map((t, i) => {
const popular = i === 1;
return (
<GlassCard key={t.id} className={`w-full max-w-xs relative ${popular ? "border-accent/40 bg-accent/5" : ""}`}>
{popular && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-accent text-xs font-bold">
Популярный
</div>
)}
<div className="text-white/50 text-sm mb-2">{t.name}</div>
<div className="text-4xl font-black mb-1">{t.price_rub} <span className="text-xl text-white/40"></span></div>
<div className="text-sm text-white/30 mb-5">{t.duration_days} дней</div>
<ul className="space-y-2 mb-6">
{perks.map((p) => (
<li key={p} className="flex items-center gap-2 text-sm text-white/70">
<Check size={15} className="text-green-400 shrink-0" /> {p}
</li>
))}
</ul>
<Link to="/register">
<Button className="w-full" variant={popular ? "primary" : "secondary"}>
Выбрать
</Button>
</Link>
</GlassCard>
);
})}
</div>
</section>
);
}
+23 -1
View File
@@ -1 +1,23 @@
export default function Landing() { return <div>Landing</div>; }
import { Navbar } from "../components/layout/Navbar";
import { Footer } from "../components/layout/Footer";
import { HeroSection } from "../components/landing/HeroSection";
import { FeaturesSection } from "../components/landing/FeaturesSection";
import { PricingSection } from "../components/landing/PricingSection";
import { HowToSection } from "../components/landing/HowToSection";
import { FAQSection } from "../components/landing/FAQSection";
export default function Landing() {
return (
<div className="min-h-screen">
<Navbar />
<main className="pt-16">
<HeroSection />
<FeaturesSection />
<PricingSection />
<HowToSection />
<FAQSection />
</main>
<Footer />
</div>
);
}