feat: landing page (hero, features, pricing, how-to, FAQ)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user