feat: redesign landing sections and interactions

Refresh visual design across hero, map, features, FAQ, and performance sections with tighter spacing, richer animations, updated branding assets, and localization/content tweaks.
This commit is contained in:
Gekon Dev
2026-05-06 19:02:07 +03:00
parent c1c139de32
commit 80f98282e7
30 changed files with 15091 additions and 1620 deletions
+10031
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

+19 -7
View File
@@ -23,23 +23,35 @@ export function FAQSection() {
const { t } = useI18n();
return (
<section id="faq" className="relative px-4 py-24 sm:py-32">
<section id="faq" className="relative px-4 pb-16 pt-5 sm:pb-20 sm:pt-5">
<div className="mx-auto max-w-3xl" ref={ref}>
<h2 className={`mb-12 text-center text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
<div className="pointer-events-none absolute left-1/2 top-16 h-64 w-64 -translate-x-1/2 rounded-full bg-gekon-green/10 blur-3xl" />
<h2 className={`mb-8 text-center text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
{t("faq_title_1")} <span className="gradient-text">{t("faq_title_2")}</span>
</h2>
<Accordion type="single" collapsible className={`space-y-3 ${isVisible ? "animate-fade-up-delay-1" : "opacity-0"}`}>
<Accordion type="single" collapsible className="space-y-3.5">
{faqs.map((faq, i) => (
<AccordionItem
key={i}
value={`faq-${i}`}
className="glass-card rounded-xl border-none px-6"
className={`group relative overflow-hidden !border-none rounded-2xl border border-gekon-green/20 bg-gradient-to-br from-background/92 via-background/82 to-background/92 px-5 shadow-[0_0_0_1px_rgba(16,185,129,0.06)] transition-all duration-300 hover:-translate-y-0.5 hover:border-gekon-green/35 hover:shadow-[0_12px_34px_rgba(16,185,129,0.14)] data-[state=open]:border-gekon-green/45 data-[state=open]:shadow-[0_14px_38px_rgba(16,185,129,0.2)] sm:px-6 ${
isVisible ? "animate-fade-up" : "opacity-0"
}`}
style={{ animationDelay: `${0.08 + i * 0.08}s` }}
>
<AccordionTrigger className="text-left text-sm font-semibold hover:no-underline sm:text-base">
{t(faq.qKey)}
<div className="pointer-events-none absolute -right-12 -top-12 h-28 w-28 rounded-full bg-gekon-green/15 blur-2xl opacity-60 transition-opacity duration-300 group-hover:opacity-100 group-data-[state=open]:opacity-100" />
<AccordionTrigger className="gap-4 py-4 text-left text-base font-semibold text-foreground transition-colors hover:no-underline sm:text-lg [&>svg]:text-gekon-green/80 [&>svg]:transition-colors group-hover:[&>svg]:text-gekon-green data-[state=open]:text-gekon-green">
<span className="flex items-center gap-3">
<span className="inline-flex h-7 min-w-7 items-center justify-center rounded-full border border-gekon-green/35 bg-gekon-green/12 px-1.5 text-[11px] font-mono tracking-wide text-gekon-green">
{(i + 1).toString().padStart(2, "0")}
</span>
<span>{t(faq.qKey)}</span>
</span>
</AccordionTrigger>
<AccordionContent className="text-sm leading-relaxed text-muted-foreground">
<AccordionContent className="pb-5 pl-10 pr-1 text-sm leading-relaxed text-muted-foreground sm:text-[15px]">
{t(faq.aKey)}
</AccordionContent>
</AccordionItem>
+9 -9
View File
@@ -17,10 +17,10 @@ export function FeaturesSection() {
const { t } = useI18n();
return (
<section id="technology" className="relative px-4 py-24 sm:py-32">
<section id="technology" className="relative px-4 pb-0 pt-5 sm:pb-0 sm:pt-5">
<div className="mx-auto max-w-7xl" ref={ref}>
<div className="mb-16 text-center">
<h2 className={`mb-4 text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
<div className="mb-7 text-center">
<h2 className={`mb-3 text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
{t("features_title_1")} <span className="gradient-text">{t("features_title_2")}</span>
</h2>
<p className={`mx-auto max-w-2xl text-muted-foreground ${isVisible ? "animate-fade-up-delay-1" : "opacity-0"}`}>
@@ -28,20 +28,20 @@ export function FeaturesSection() {
</p>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature, i) => (
<div
key={feature.titleKey}
className={`glass-card group rounded-xl p-6 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-gekon-green/5 ${
className={`glass-card group rounded-lg p-4 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-gekon-green/5 ${
isVisible ? "animate-fade-up" : "opacity-0"
}`}
style={{ animationDelay: `${i * 0.1}s` }}
>
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-gekon-green/20 to-gekon-cyan/20 transition-all group-hover:from-gekon-green/30 group-hover:to-gekon-cyan/30">
<feature.icon size={24} className="text-gekon-green" />
<div className="mb-3 flex h-9 w-9 items-center justify-center rounded-md bg-gradient-to-br from-gekon-green/20 to-gekon-cyan/20 transition-all group-hover:from-gekon-green/30 group-hover:to-gekon-cyan/30">
<feature.icon size={19} className="text-gekon-green" />
</div>
<h3 className="mb-2 text-lg font-semibold text-foreground">{t(feature.titleKey)}</h3>
<p className="text-sm leading-relaxed text-muted-foreground">{t(feature.descKey)}</p>
<h3 className="mb-1.5 text-base font-semibold text-foreground">{t(feature.titleKey)}</h3>
<p className="text-sm leading-snug text-muted-foreground">{t(feature.descKey)}</p>
</div>
))}
</div>
+1 -1
View File
@@ -8,7 +8,7 @@ export function FinalCTASection() {
const { t } = useI18n();
return (
<section className="relative overflow-hidden px-4 py-24 sm:py-32">
<section className="relative overflow-hidden px-4 py-16 sm:py-20">
<div className="pointer-events-none absolute inset-0 bg-radial-glow" />
<div className="pointer-events-none absolute inset-0 bg-grid-pattern opacity-50" />
+7 -4
View File
@@ -28,10 +28,13 @@ export function Footer() {
<div className="mx-auto max-w-7xl">
<div className="grid gap-8 sm:grid-cols-2 md:grid-cols-5">
<div className="md:col-span-1">
<div className="flex items-center gap-2 mb-4">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-gekon-green to-gekon-cyan">
<span className="text-sm font-bold text-primary-foreground">G</span>
</div>
<div className="mb-4 flex items-center gap-2">
<img
src="/gekon-logo.png"
alt="Gekon Fast logo"
className="h-8 w-8 rounded-md object-contain"
draggable={false}
/>
<span className="text-lg font-bold">Gekon</span>
</div>
<p className="text-sm text-muted-foreground leading-relaxed">
+92 -49
View File
@@ -1,31 +1,70 @@
import { Button } from "@/components/ui/button";
import { Zap, ArrowRight } from "lucide-react";
import { ArrowRight, Github, Instagram, MessageCircle, Send, Youtube, Zap } from "lucide-react";
import { useI18n } from "@/i18n/context";
export function HeroSection() {
const { t } = useI18n();
const motionClasses = [
"tag-cloud-motion-a",
"tag-cloud-motion-b",
"tag-cloud-motion-c",
"tag-cloud-motion-d",
];
const cloudTags = [
{ label: "YouTube", x: 14, y: 18, style: "service" },
{ label: "Discord", x: 44, y: 12, style: "service" },
{ label: "Telegram", x: 70, y: 20, style: "service" },
{ label: "ChatGPT", x: 24, y: 34, style: "service" },
{ label: "Spotify", x: 56, y: 30, style: "service" },
{ label: "TikTok", x: 80, y: 34, style: "service" },
{ label: "Instagram", x: 12, y: 50, style: "service" },
{ label: "X (Twitter)", x: 40, y: 48, style: "service" },
{ label: "Facebook", x: 68, y: 48, style: "service" },
{ label: "Threads", x: 20, y: 64, style: "service" },
{ label: "Twitch", x: 52, y: 66, style: "service" },
{ label: "Reddit", x: 82, y: 62, style: "service" },
{ label: "Windows", x: 8, y: 78, style: "platform" },
{ label: "macOS", x: 30, y: 78, style: "platform" },
{ label: "iOS", x: 48, y: 80, style: "platform" },
{ label: "Android", x: 66, y: 78, style: "platform" },
{ label: "Linux", x: 86, y: 78, style: "platform" },
{ label: "WiFi", x: 18, y: 90, style: "platform" },
{ label: "Smart TV", x: 42, y: 92, style: "platform" },
{ label: "Chrome", x: 66, y: 92, style: "platform" },
{ label: "Firefox", x: 86, y: 90, style: "platform" },
];
const socialLinks = [
{ name: "Telegram", href: "#", icon: Send },
{ name: "Instagram", href: "#", icon: Instagram },
{ name: "ВКонтакте", href: "#", icon: MessageCircle },
{ name: "GitHub", href: "#", icon: Github },
{ name: "YouTube", href: "#", icon: Youtube },
{ name: "Threads", href: "#", icon: MessageCircle },
];
return (
<section className="relative flex min-h-screen items-center justify-center overflow-hidden bg-grid-pattern bg-radial-glow px-4 pt-16">
<section className="relative flex items-start overflow-hidden bg-grid-pattern bg-radial-glow px-4 pb-4 pt-16 sm:pb-5">
<div className="pointer-events-none absolute top-1/4 left-1/4 h-96 w-96 rounded-full bg-gekon-green/5 blur-[128px]" />
<div className="pointer-events-none absolute bottom-1/4 right-1/4 h-96 w-96 rounded-full bg-gekon-cyan/5 blur-[128px]" />
<div className="relative z-10 mx-auto max-w-5xl text-center">
<div className="relative z-10 mx-auto w-full max-w-7xl">
<div className="grid items-start gap-6 lg:grid-cols-[1.1fr_0.9fr] lg:gap-4">
<div className="text-center lg:text-left">
<div className="animate-fade-up mb-6 inline-flex items-center gap-2 rounded-full border border-gekon-green/20 bg-gekon-green/5 px-4 py-1.5 text-sm text-gekon-green">
<Zap size={14} />
<span>{t("hero_badge")}</span>
</div>
<h1 className="animate-fade-up-delay-1 mb-6 text-4xl font-extrabold leading-tight tracking-tight sm:text-5xl md:text-7xl">
<h1 className="animate-fade-up-delay-1 mb-5 text-4xl font-extrabold leading-tight tracking-tight sm:text-5xl md:text-6xl">
{t("hero_title_1")}{" "}
<span className="gradient-text">{t("hero_title_2")}</span>
</h1>
<p className="animate-fade-up-delay-2 mx-auto mb-10 max-w-2xl text-lg text-muted-foreground sm:text-xl">
<p className="animate-fade-up-delay-2 mx-auto mb-8 max-w-2xl text-lg text-muted-foreground sm:text-xl lg:mx-0">
{t("hero_subtitle")}
</p>
<div className="animate-fade-up-delay-3 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<div className="animate-fade-up-delay-3 flex flex-col items-center gap-4 sm:flex-row sm:justify-center lg:justify-start">
<Button variant="glow" size="xl">
{t("hero_cta_primary")}
<ArrowRight size={18} />
@@ -35,54 +74,58 @@ export function HeroSection() {
</Button>
</div>
<div className="animate-fade-up-delay-3 mx-auto mt-16 max-w-3xl">
<div className="glass-card rounded-2xl p-6 sm:p-8">
<div className="mb-4 flex items-center justify-between text-sm text-muted-foreground">
<span>{t("hero_speed_label")}</span>
<span className="font-mono text-gekon-green">{t("hero_speed_optimized")}</span>
<div className="mt-4 w-full max-w-[540px]">
<div className="flex items-center justify-center gap-3 lg:justify-start">
{socialLinks.map((item) => (
<a
key={item.name}
href={item.href}
title={item.name}
aria-label={item.name}
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-gekon-green/60 bg-gekon-green/15 text-gekon-green shadow-[0_0_16px_rgba(16,185,129,0.25)] transition-all hover:-translate-y-0.5 hover:border-gekon-green hover:bg-gekon-green/25 hover:shadow-[0_0_24px_rgba(16,185,129,0.45)]"
>
<item.icon size={19} />
</a>
))}
</div>
<div className="space-y-4">
<SpeedBar label={t("hero_speed_download")} before="50 Mbps" after="150 Mbps" percent={90} />
<SpeedBar label={t("hero_speed_upload")} before="20 Mbps" after="60 Mbps" percent={75} />
<SpeedBar label={t("hero_speed_latency")} before="120ms" after="40ms" percent={95} isReduced />
</div>
</div>
<div className="flex justify-center lg:justify-start">
<div className="flex w-full max-w-[430px] flex-col items-center lg:items-start">
<div className="relative h-[320px] w-[320px] shrink-0 sm:h-[370px] sm:w-[370px] lg:h-[400px] lg:w-[400px]">
{cloudTags.map((tag, index) => (
<div
key={tag.label}
className="absolute -translate-x-1/2 -translate-y-1/2"
style={{ left: `${tag.x}%`, top: `${tag.y}%` }}
>
<div
className={motionClasses[index % motionClasses.length]}
style={{ animationDelay: `${(index % 7) * 0.42}s` }}
>
<button
type="button"
aria-label={tag.label}
className={`inline-flex rounded-full border px-3 py-1.5 text-sm font-semibold backdrop-blur-sm ${
tag.style === "service"
? "tag-cloud-item border-gekon-green/35 bg-background/80 text-gekon-green shadow-lg shadow-gekon-green/10 hover:border-gekon-green/60 hover:shadow-gekon-green/30"
: "tag-cloud-item border-gekon-cyan/35 bg-background/85 text-gekon-cyan shadow-md shadow-gekon-cyan/10 hover:border-gekon-cyan/60 hover:shadow-gekon-cyan/30"
}`}
>
{tag.label}
</button>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</section>
);
}
function SpeedBar({
label,
before,
after,
percent,
isReduced,
}: {
label: string;
before: string;
after: string;
percent: number;
isReduced?: boolean;
}) {
return (
<div>
<div className="mb-1 flex items-center justify-between text-xs">
<span className="text-muted-foreground">{label}</span>
<span className="font-mono">
<span className="text-muted-foreground line-through">{before}</span>
{" → "}
<span className="text-gekon-green font-semibold">{after}</span>
{isReduced && <span className="ml-1 text-gekon-green"></span>}
</span>
</div>
<div className="h-2 overflow-hidden rounded-full bg-secondary">
<div
className="h-full rounded-full bg-gradient-to-r from-gekon-green to-gekon-cyan transition-all duration-1000"
style={{ width: `${percent}%` }}
/>
</div>
</div>
);
}
+2 -2
View File
@@ -14,9 +14,9 @@ export function HowItWorksSection() {
const { t } = useI18n();
return (
<section className="relative px-4 py-24 sm:py-32">
<section className="relative px-4 pb-0 pt-5 sm:pb-0 sm:pt-5">
<div className="mx-auto max-w-5xl" ref={ref}>
<div className="mb-16 text-center">
<div className="mb-10 text-center">
<h2 className={`mb-4 text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
{t("how_title_1")} <span className="gradient-text">{t("how_title_2")}</span>
</h2>
+7 -4
View File
@@ -34,10 +34,13 @@ export function Navbar() {
>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
<a href="#" className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-gekon-green to-gekon-cyan">
<span className="text-sm font-bold text-primary-foreground">G</span>
</div>
<a href="#" className="flex items-center gap-3">
<img
src="/gekon-logo.png"
alt="Gekon Fast logo"
className="h-9 w-9 rounded-md object-contain"
draggable={false}
/>
<span className="text-xl font-bold tracking-tight text-foreground">
Gekon
</span>
+81 -11
View File
@@ -1,22 +1,86 @@
import { useEffect, useState } from "react";
import { useScrollAnimation } from "@/hooks/useScrollAnimation";
import { useI18n } from "@/i18n/context";
import type { TranslationKeys } from "@/i18n/translations";
const metrics: { labelKey: TranslationKeys; before: string; after: string; improvement: string }[] = [
{ labelKey: "perf_download", before: "50 Mbps", after: "150 Mbps", improvement: "3x" },
{ labelKey: "perf_latency", before: "120ms", after: "40ms", improvement: "67% ↓" },
{ labelKey: "perf_streaming", before: "720p", after: "4K", improvement: "UHD" },
{ labelKey: "perf_gaming", before: "80ms", after: "25ms", improvement: "69% ↓" },
type AnimatedValue = {
from: number;
to: number;
unit: string;
spaceBeforeUnit?: boolean;
};
const metrics: {
labelKey: TranslationKeys;
before: AnimatedValue;
after: AnimatedValue;
improvement: string;
}[] = [
{
labelKey: "perf_download",
before: { from: 0, to: 50, unit: "Mbps", spaceBeforeUnit: true },
after: { from: 0, to: 150, unit: "Mbps", spaceBeforeUnit: true },
improvement: "3x",
},
{
labelKey: "perf_latency",
before: { from: 0, to: 120, unit: "ms" },
after: { from: 0, to: 40, unit: "ms" },
improvement: "67% ↓",
},
{
labelKey: "perf_streaming",
before: { from: 0, to: 720, unit: "p" },
after: { from: 0, to: 4, unit: "K" },
improvement: "UHD",
},
{
labelKey: "perf_gaming",
before: { from: 0, to: 80, unit: "ms" },
after: { from: 0, to: 25, unit: "ms" },
improvement: "69% ↓",
},
];
export function PerformanceSection() {
const { ref, isVisible } = useScrollAnimation();
const { t } = useI18n();
const [progress, setProgress] = useState(0);
useEffect(() => {
if (!isVisible) {
setProgress(0);
return;
}
let animationFrame = 0;
let startTime: number | null = null;
const duration = 1500;
const animate = (timestamp: number) => {
if (startTime === null) startTime = timestamp;
const linearProgress = Math.min((timestamp - startTime) / duration, 1);
const easedProgress = 1 - Math.pow(1 - linearProgress, 3);
setProgress(easedProgress);
if (linearProgress < 1) {
animationFrame = window.requestAnimationFrame(animate);
}
};
animationFrame = window.requestAnimationFrame(animate);
return () => window.cancelAnimationFrame(animationFrame);
}, [isVisible]);
const formatAnimated = (value: AnimatedValue) => {
const current = Math.round(value.from + (value.to - value.from) * progress);
return `${current}${value.spaceBeforeUnit ? " " : ""}${value.unit}`;
};
return (
<section id="performance" className="relative px-4 py-24 sm:py-32">
<section id="performance" className="relative px-4 pb-0 pt-5 sm:pb-0 sm:pt-5">
<div className="mx-auto max-w-5xl" ref={ref}>
<div className="mb-16 text-center">
<div className="mb-10 text-center">
<h2 className={`mb-4 text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
{t("perf_title_1")} <span className="gradient-text">{t("perf_title_2")}</span>
</h2>
@@ -37,14 +101,20 @@ export function PerformanceSection() {
<div className="flex items-center justify-between">
<div className="text-center">
<div className="text-sm text-muted-foreground mb-1">{t("perf_before")}</div>
<div className="font-mono text-xl text-muted-foreground">{m.before}</div>
<div className={`font-mono text-xl text-muted-foreground ${isVisible ? "animate-counter" : ""}`}>
{formatAnimated(m.before)}
</div>
</div>
<div className="mx-4 text-2xl text-gekon-green/90 transition-transform duration-500 group-hover:translate-x-0.5">
</div>
<div className="mx-4 text-gekon-green text-2xl"></div>
<div className="text-center">
<div className="text-sm text-gekon-green mb-1">{t("perf_after")}</div>
<div className="font-mono text-xl text-foreground font-bold">{m.after}</div>
<div className={`font-mono text-xl font-bold text-foreground ${isVisible ? "animate-counter" : ""}`}>
{formatAnimated(m.after)}
</div>
<div className="ml-4 rounded-full bg-gekon-green/10 px-3 py-1 text-sm font-bold text-gekon-green">
</div>
<div className="ml-4 rounded-full border border-gekon-green/20 bg-gekon-green/10 px-3 py-1 text-sm font-bold text-gekon-green transition-all duration-300 group-hover:bg-gekon-green/15 group-hover:shadow-[0_0_18px_rgba(16,185,129,0.2)]">
{m.improvement}
</div>
</div>
+1 -1
View File
@@ -59,7 +59,7 @@ export function PricingSection() {
const { t } = useI18n();
return (
<section id="pricing" className="relative px-4 py-24 sm:py-32">
<section id="pricing" className="relative px-4 pb-0 pt-5 sm:pb-0 sm:pt-5">
<div className="mx-auto max-w-6xl" ref={ref}>
<div className="mb-12 text-center">
<h2 className={`mb-4 text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
+129 -89
View File
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useScrollAnimation } from "@/hooks/useScrollAnimation";
import { useI18n } from "@/i18n/context";
import { Server, Zap, Users } from "lucide-react";
import worldMapOutline from "@/world-states.svg?url";
interface ServerLocation {
id: string;
@@ -16,44 +17,72 @@ interface ServerLocation {
const servers: ServerLocation[] = [
// North America
{ id: "us-west", country: "USA", city: "Los Angeles", x: 15, y: 35, users: 1250, latency: 12, status: "online" },
{ id: "us-east", country: "USA", city: "New York", x: 22, y: 32, users: 1840, latency: 8, status: "online" },
{ id: "canada", country: "Canada", city: "Toronto", x: 21, y: 28, users: 680, latency: 15, status: "online" },
{ id: "us-west", country: "USA", city: "Los Angeles", x: 13, y: 35, users: 1250, latency: 12, status: "online" },
{ id: "us-east", country: "USA", city: "New York", x: 20, y: 31, users: 1840, latency: 8, status: "online" },
{ id: "canada", country: "Canada", city: "Toronto", x: 20, y: 27, users: 680, latency: 15, status: "online" },
// South America
{ id: "brazil", country: "Brazil", city: "São Paulo", x: 30, y: 65, users: 520, latency: 45, status: "online" },
{ id: "argentina", country: "Argentina", city: "Buenos Aires", x: 28, y: 72, users: 280, latency: 52, status: "online" },
// Europe
{ id: "uk", country: "UK", city: "London", x: 48, y: 28, users: 2100, latency: 5, status: "online" },
{ id: "germany", country: "Germany", city: "Frankfurt", x: 51, y: 30, users: 1650, latency: 7, status: "online" },
{ id: "france", country: "France", city: "Paris", x: 49, y: 32, users: 980, latency: 9, status: "online" },
{ id: "netherlands", country: "Netherlands", city: "Amsterdam", x: 50, y: 29, users: 1420, latency: 6, status: "online" },
{ id: "sweden", country: "Sweden", city: "Stockholm", x: 53, y: 24, users: 540, latency: 12, status: "online" },
{ id: "poland", country: "Poland", city: "Warsaw", x: 54, y: 30, users: 720, latency: 14, status: "online" },
{ id: "spain", country: "Spain", city: "Madrid", x: 47, y: 35, users: 650, latency: 18, status: "online" },
{ id: "italy", country: "Italy", city: "Milan", x: 51, y: 34, users: 580, latency: 16, status: "online" },
{ id: "uk", country: "UK", city: "London", x: 46.5, y: 27, users: 2100, latency: 5, status: "online" },
{ id: "germany", country: "Germany", city: "Frankfurt", x: 49, y: 29, users: 1650, latency: 7, status: "online" },
{ id: "france", country: "France", city: "Paris", x: 47.8, y: 30, users: 980, latency: 9, status: "online" },
{ id: "netherlands", country: "Netherlands", city: "Amsterdam", x: 48.2, y: 28, users: 1420, latency: 6, status: "online" },
{ id: "sweden", country: "Sweden", city: "Stockholm", x: 51, y: 23, users: 540, latency: 12, status: "online" },
{ id: "poland", country: "Poland", city: "Warsaw", x: 51.5, y: 28.5, users: 720, latency: 14, status: "online" },
{ id: "spain", country: "Spain", city: "Madrid", x: 46, y: 33, users: 650, latency: 18, status: "online" },
{ id: "italy", country: "Italy", city: "Milan", x: 49.5, y: 31.5, users: 580, latency: 16, status: "online" },
// Asia
{ id: "japan", country: "Japan", city: "Tokyo", x: 82, y: 35, users: 1950, latency: 8, status: "online" },
{ id: "singapore", country: "Singapore", city: "Singapore", x: 75, y: 52, users: 1680, latency: 10, status: "online" },
{ id: "hong-kong", country: "Hong Kong", city: "Hong Kong", x: 77, y: 42, users: 1420, latency: 12, status: "online" },
{ id: "south-korea", country: "South Korea", city: "Seoul", x: 80, y: 34, users: 1280, latency: 9, status: "online" },
{ id: "india", country: "India", city: "Mumbai", x: 68, y: 45, users: 890, latency: 25, status: "online" },
{ id: "uae", country: "UAE", city: "Dubai", x: 60, y: 42, users: 740, latency: 22, status: "online" },
{ id: "thailand", country: "Thailand", city: "Bangkok", x: 74, y: 48, users: 620, latency: 28, status: "online" },
{ id: "vietnam", country: "Vietnam", city: "Ho Chi Minh", x: 76, y: 50, users: 480, latency: 32, status: "online" },
{ id: "japan", country: "Japan", city: "Tokyo", x: 80.5, y: 35, users: 1950, latency: 8, status: "online" },
{ id: "singapore", country: "Singapore", city: "Singapore", x: 73, y: 52, users: 1680, latency: 10, status: "online" },
{ id: "hong-kong", country: "Hong Kong", city: "Hong Kong", x: 76, y: 43, users: 1420, latency: 12, status: "online" },
{ id: "south-korea", country: "South Korea", city: "Seoul", x: 79.5, y: 33, users: 1280, latency: 9, status: "online" },
{ id: "india", country: "India", city: "Mumbai", x: 67, y: 45.5, users: 890, latency: 25, status: "online" },
{ id: "uae", country: "UAE", city: "Dubai", x: 59.5, y: 39.5, users: 740, latency: 22, status: "online" },
{ id: "thailand", country: "Thailand", city: "Bangkok", x: 74, y: 48.5, users: 620, latency: 28, status: "online" },
{ id: "vietnam", country: "Vietnam", city: "Ho Chi Minh", x: 75.5, y: 50.5, users: 480, latency: 32, status: "online" },
// Oceania
{ id: "australia", country: "Australia", city: "Sydney", x: 85, y: 72, users: 920, latency: 18, status: "online" },
{ id: "new-zealand", country: "New Zealand", city: "Auckland", x: 90, y: 75, users: 340, latency: 24, status: "online" },
{ id: "australia", country: "Australia", city: "Sydney", x: 84, y: 73, users: 920, latency: 18, status: "online" },
{ id: "new-zealand", country: "New Zealand", city: "Auckland", x: 89, y: 76, users: 340, latency: 24, status: "online" },
// Africa
{ id: "south-africa", country: "South Africa", city: "Cape Town", x: 52, y: 72, users: 380, latency: 48, status: "online" },
{ id: "south-africa", country: "South Africa", city: "Cape Town", x: 51, y: 74, users: 380, latency: 48, status: "online" },
// Middle East
{ id: "turkey", country: "Turkey", city: "Istanbul", x: 56, y: 35, users: 680, latency: 20, status: "online" },
{ id: "israel", country: "Israel", city: "Tel Aviv", x: 57, y: 38, users: 420, latency: 26, status: "online" },
{ id: "turkey", country: "Turkey", city: "Istanbul", x: 54.5, y: 33, users: 680, latency: 20, status: "online" },
{ id: "israel", country: "Israel", city: "Tel Aviv", x: 56, y: 36, users: 420, latency: 26, status: "online" },
];
const networkLinks: Array<[string, string]> = [
["us-west", "us-east"],
["us-east", "canada"],
["us-east", "uk"],
["us-east", "brazil"],
["brazil", "argentina"],
["uk", "france"],
["france", "spain"],
["france", "germany"],
["germany", "netherlands"],
["germany", "poland"],
["germany", "italy"],
["poland", "sweden"],
["italy", "turkey"],
["turkey", "israel"],
["turkey", "uae"],
["uae", "india"],
["india", "thailand"],
["thailand", "vietnam"],
["vietnam", "hong-kong"],
["hong-kong", "singapore"],
["hong-kong", "south-korea"],
["south-korea", "japan"],
["vietnam", "australia"],
["australia", "new-zealand"],
["uae", "south-africa"],
];
export function ServerMap() {
@@ -63,11 +92,12 @@ export function ServerMap() {
const totalUsers = servers.reduce((sum, s) => sum + s.users, 0);
const avgLatency = Math.round(servers.reduce((sum, s) => sum + s.latency, 0) / servers.length);
const serverById = new Map(servers.map((server) => [server.id, server]));
return (
<section id="technology" className="relative px-4 py-24 sm:py-32 overflow-hidden">
<section id="technology" className="relative overflow-hidden px-4 pb-0 pt-5 sm:pb-0 sm:pt-5">
<div className="mx-auto max-w-7xl" ref={ref}>
<div className="mb-16 text-center">
<div className="mb-10 text-center">
<h2 className={`mb-4 text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
{t("tech_title_1")} <span className="gradient-text">{t("tech_title_2")}</span>
</h2>
@@ -77,67 +107,63 @@ export function ServerMap() {
</div>
{/* Stats */}
<div className={`mb-12 grid gap-6 sm:grid-cols-3 ${isVisible ? "animate-fade-up-delay-2" : "opacity-0"}`}>
<div className="glass-card rounded-xl p-6 text-center">
<Server className="mx-auto mb-3 text-gekon-green" size={32} />
<div className="text-3xl font-bold gradient-text">{servers.length}+</div>
<div className="text-sm text-muted-foreground">Global Servers</div>
<div className={`mb-10 grid gap-4 sm:grid-cols-3 ${isVisible ? "animate-fade-up-delay-2" : "opacity-0"}`}>
<div className="glass-card rounded-xl p-4 text-center">
<Server className="mx-auto mb-2 text-gekon-green" size={26} />
<div className="text-2xl font-bold gradient-text">{servers.length}+</div>
<div className="text-xs text-muted-foreground">Global Servers</div>
</div>
<div className="glass-card rounded-xl p-6 text-center">
<Users className="mx-auto mb-3 text-gekon-cyan" size={32} />
<div className="text-3xl font-bold gradient-text">{totalUsers}+</div>
<div className="text-sm text-muted-foreground">Active Users</div>
<div className="glass-card rounded-xl p-4 text-center">
<Users className="mx-auto mb-2 text-gekon-cyan" size={26} />
<div className="text-2xl font-bold gradient-text">{totalUsers}+</div>
<div className="text-xs text-muted-foreground">Active Users</div>
</div>
<div className="glass-card rounded-xl p-6 text-center">
<Zap className="mx-auto mb-3 text-gekon-green" size={32} />
<div className="text-3xl font-bold gradient-text">{avgLatency}ms</div>
<div className="text-sm text-muted-foreground">Avg Latency</div>
<div className="glass-card rounded-xl p-4 text-center">
<Zap className="mx-auto mb-2 text-gekon-green" size={26} />
<div className="text-2xl font-bold gradient-text">{avgLatency}ms</div>
<div className="text-xs text-muted-foreground">Avg Latency</div>
</div>
</div>
{/* Interactive Map */}
<div className={`glass-card relative overflow-hidden rounded-2xl p-8 ${isVisible ? "animate-fade-up-delay-3" : "opacity-0"}`}>
<div className={`relative ${isVisible ? "animate-fade-up-delay-3" : "opacity-0"}`}>
{/* World Map SVG Background */}
<div className="relative aspect-[2/1] w-full">
{/* Simplified world map outline */}
<svg
viewBox="0 0 1000 500"
className="absolute inset-0 h-full w-full opacity-10"
xmlns="http://www.w3.org/2000/svg"
<div className="relative aspect-[2/1] w-full overflow-hidden">
<div
className="absolute inset-0"
style={{
WebkitMaskImage:
"radial-gradient(120% 85% at 50% 50%, black 62%, rgba(0,0,0,0.72) 78%, transparent 100%)",
maskImage:
"radial-gradient(120% 85% at 50% 50%, black 62%, rgba(0,0,0,0.72) 78%, transparent 100%)",
}}
>
{/* Continents outlines (simplified) */}
<path
d="M 150 150 Q 200 120 250 150 L 280 180 L 250 220 L 200 200 Z"
fill="currentColor"
className="text-gekon-green"
<div className="absolute inset-0 bg-[#020617]" />
<img
src={worldMapOutline}
alt="World contour map"
className="h-full w-full object-contain opacity-82"
draggable={false}
/>
<path
d="M 450 120 Q 550 100 650 140 L 680 180 L 650 220 L 550 200 L 450 180 Z"
fill="currentColor"
className="text-gekon-cyan"
/>
<path
d="M 700 150 Q 800 130 900 180 L 920 250 L 880 300 L 750 280 L 700 220 Z"
fill="currentColor"
className="text-gekon-green"
/>
</svg>
<div className="absolute inset-0 bg-gradient-to-b from-background/28 via-transparent to-background/34" />
</div>
{/* Connection lines */}
<svg className="absolute inset-0 h-full w-full pointer-events-none" viewBox="0 0 100 100" preserveAspectRatio="none">
{servers.map((server, i) => {
if (i === 0) return null;
const prev = servers[i - 1];
{networkLinks.map(([fromId, toId], i) => {
const from = serverById.get(fromId);
const to = serverById.get(toId);
if (!from || !to) return null;
return (
<line
key={`line-${server.id}`}
x1={prev.x}
y1={prev.y}
x2={server.x}
y2={server.y}
key={`line-${fromId}-${toId}`}
x1={from.x}
y1={from.y}
x2={to.x}
y2={to.y}
stroke="url(#gradient)"
strokeWidth="0.1"
opacity="0.2"
strokeWidth="0.14"
opacity="0.3"
className="animate-pulse"
style={{ animationDelay: `${i * 0.1}s` }}
/>
@@ -155,49 +181,63 @@ export function ServerMap() {
{servers.map((server, i) => (
<div
key={server.id}
className="absolute -translate-x-1/2 -translate-y-1/2 cursor-pointer transition-transform hover:scale-150"
className="absolute -translate-x-1/2 -translate-y-1/2 pointer-events-none"
style={{
left: `${server.x}%`,
top: `${server.y}%`,
animationDelay: `${i * 0.05}s`,
}}
>
{/* Pulse ring */}
<div className="absolute inset-0 -m-1 animate-ping rounded-full bg-gekon-green/30 pointer-events-none" />
{/* Node dot */}
<button
type="button"
aria-label={`${server.city}, ${server.country}`}
className="pointer-events-auto relative block h-2.5 w-2.5 rounded-full transition-transform duration-150 hover:scale-125 focus-visible:outline-none"
onMouseEnter={() => setHoveredServer(server)}
onMouseLeave={() => setHoveredServer(null)}
>
{/* Pulse ring */}
<div className="absolute inset-0 -m-2 animate-ping rounded-full bg-gekon-green/30" />
{/* Node dot */}
<div
className={`relative h-3 w-3 rounded-full transition-all ${
<span
className={`absolute inset-0 rounded-full transition-all ${
server.status === "online"
? "bg-gekon-green shadow-lg shadow-gekon-green/50"
: "bg-yellow-500 shadow-lg shadow-yellow-500/50"
}`}
/>
</button>
{/* Tooltip */}
{hoveredServer?.id === server.id && (
<div className="absolute left-1/2 top-full z-10 mt-2 -translate-x-1/2 whitespace-nowrap rounded-lg bg-background/95 px-3 py-2 text-xs shadow-xl backdrop-blur-sm border border-gekon-green/20">
<div className="font-semibold text-foreground">{server.city}, {server.country}</div>
</div>
))}
{/* Global tooltip layer (always above points) */}
{hoveredServer && (
<div
className="pointer-events-none absolute z-40 -translate-x-1/2 whitespace-nowrap rounded-lg border border-gekon-green/25 bg-background/98 px-3 py-2 text-xs shadow-2xl shadow-black/50 backdrop-blur-md"
style={{
left: `${hoveredServer.x}%`,
top: `calc(${hoveredServer.y}% + 14px)`,
}}
>
<div className="font-semibold text-foreground">
{hoveredServer.city}, {hoveredServer.country}
</div>
<div className="mt-1 flex items-center gap-3 text-muted-foreground">
<span className="flex items-center gap-1">
<Users size={10} />
{server.users}
{hoveredServer.users}
</span>
<span className="flex items-center gap-1">
<Zap size={10} />
{server.latency}ms
{hoveredServer.latency}ms
</span>
</div>
</div>
)}
</div>
))}
</div>
{/* Legend */}
<div className="mt-6 flex flex-wrap items-center justify-center gap-6 text-xs text-muted-foreground">
<div className="mt-5 flex flex-wrap items-center justify-center gap-6 text-xs text-muted-foreground/90">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-gekon-green shadow-lg shadow-gekon-green/50" />
<span>Online</span>
@@ -0,0 +1,43 @@
import { Headset, Languages, Settings } from "lucide-react";
const cards = [
{
title: "Отвечаем 24/7",
desc: "Мы всегда готовы прийти на помощь",
icon: Headset,
},
{
title: "Говорим понятным языком",
desc: "Делаем сложные вещи простыми — никаких технических терминов",
icon: Languages,
},
{
title: "Помогаем настроить сервис",
desc: "От смартфонов до телевизоров — позаботимся обо всем!",
icon: Settings,
},
];
export function SupportHighlightsSection() {
return (
<section className="px-4 pb-0 pt-5 sm:pb-0">
<div className="mx-auto max-w-7xl">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{cards.map((card) => (
<div
key={card.title}
className="group relative overflow-hidden rounded-2xl border border-gekon-green/20 bg-gradient-to-br from-background/85 via-background/70 to-background/85 p-5 shadow-[0_0_0_1px_rgba(16,185,129,0.08)] transition-all hover:-translate-y-1 hover:border-gekon-green/45 hover:shadow-[0_8px_30px_rgba(16,185,129,0.18)]"
>
<div className="pointer-events-none absolute -right-10 -top-10 h-28 w-28 rounded-full bg-gekon-green/15 blur-2xl transition-opacity group-hover:opacity-100" />
<div className="mb-3 inline-flex h-10 w-10 items-center justify-center rounded-xl border border-gekon-green/30 bg-gekon-green/10 text-gekon-green">
<card.icon size={18} />
</div>
<h3 className="mb-1.5 text-base font-semibold text-foreground">{card.title}</h3>
<p className="text-sm leading-relaxed text-muted-foreground">{card.desc}</p>
</div>
))}
</div>
</div>
</section>
);
}
+15 -6
View File
@@ -16,9 +16,9 @@ export function UseCasesSection() {
const { t } = useI18n();
return (
<section className="relative px-4 py-24 sm:py-32">
<section className="relative px-4 pb-0 pt-5 sm:pb-0 sm:pt-5">
<div className="mx-auto max-w-5xl" ref={ref}>
<h2 className={`mb-12 text-center text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
<h2 className={`mb-8 text-center text-3xl font-bold sm:text-4xl ${isVisible ? "animate-fade-up" : "opacity-0"}`}>
{t("cases_title_1")} <span className="gradient-text">{t("cases_title_2")}</span>
</h2>
@@ -26,14 +26,23 @@ export function UseCasesSection() {
{cases.map((c, i) => (
<div
key={c.titleKey}
className={`glass-card flex items-center gap-3 rounded-full px-6 py-3 transition-all hover:scale-105 hover:shadow-lg hover:shadow-gekon-green/5 ${
className={`glass-card group flex items-center gap-3 rounded-full px-6 py-3 transition-all duration-500 hover:scale-[1.03] hover:shadow-lg hover:shadow-gekon-green/10 ${
isVisible ? "animate-fade-up" : "opacity-0"
}`}
style={{ animationDelay: `${i * 0.08}s` }}
style={{
animationName: isVisible ? "fade-up, float" : undefined,
animationDuration: isVisible ? "0.6s, 5.4s" : undefined,
animationTimingFunction: isVisible ? "ease-out, ease-in-out" : undefined,
animationIterationCount: isVisible ? "1, infinite" : undefined,
animationFillMode: isVisible ? "both, both" : undefined,
animationDelay: isVisible ? `${i * 0.08}s, ${0.2 + i * 0.16}s` : `${i * 0.08}s`,
}}
>
<c.icon size={20} className="text-gekon-green" />
<div className="rounded-full bg-gekon-green/10 p-1.5 transition-all duration-300 group-hover:bg-gekon-green/20 group-hover:shadow-[0_0_18px_rgba(16,185,129,0.25)]">
<c.icon size={19} className="text-gekon-green transition-transform duration-300 group-hover:scale-110" />
</div>
<div>
<span className="font-semibold text-foreground">{t(c.titleKey)}</span>
<span className="font-semibold text-foreground transition-colors group-hover:text-gekon-green">{t(c.titleKey)}</span>
<span className="ml-2 text-sm text-muted-foreground">{t(c.descKey)}</span>
</div>
</div>
+3 -3
View File
@@ -179,8 +179,8 @@ const ru: typeof en = {
hero_title_1: "Ускорь свой интернет.",
hero_title_2: "Раскрой настоящую скорость.",
hero_subtitle: "Передовая технология оптимизации сети. Используйте интернет на полную мощность благодаря интеллектуальной маршрутизации и оптимизации трафика.",
hero_cta_primary: "Попробовать бесплатно",
hero_cta_secondary: "Смотреть результаты",
hero_cta_primary: "Оформить подписку",
hero_cta_secondary: "Попробовать бесплатно",
hero_speed_label: "Скорость соединения",
hero_speed_optimized: "Оптимизировано",
hero_speed_download: "Загрузка",
@@ -297,7 +297,7 @@ const ru: typeof en = {
cta_title_2: "настоящую скорость интернета?",
cta_subtitle: "Присоединяйтесь к 10 000+ пользователям, которые ускорили свои соединения.",
cta_button: "Попробовать бесплатно",
cta_note: "Без кредитной карты · 7-дневный пробный период",
cta_note: "Бесплатный тест 3 дня, за суточный репост - 24 часа сервиса в подарок!",
footer_desc: "Передовая технология оптимизации сети для более быстрого и надёжного интернета.",
footer_product: "Продукт",
+9
View File
@@ -44,6 +44,15 @@ export const Route = createRootRoute({
rel: "stylesheet",
href: appCss,
},
{
rel: "icon",
type: "image/png",
href: "/gekon-logo.png",
},
{
rel: "apple-touch-icon",
href: "/gekon-logo.png",
},
],
}),
shellComponent: RootShell,
+3 -1
View File
@@ -4,6 +4,7 @@ import { HeroSection } from "@/components/HeroSection";
import { SocialProofBar } from "@/components/SocialProofBar";
import { FeaturesSection } from "@/components/FeaturesSection";
import { ServerMap } from "@/components/ServerMap";
import { SupportHighlightsSection } from "@/components/SupportHighlightsSection";
import { HowItWorksSection } from "@/components/HowItWorksSection";
import { PricingSection } from "@/components/PricingSection";
import { PerformanceSection } from "@/components/PerformanceSection";
@@ -42,8 +43,9 @@ function Index() {
<Navbar />
<HeroSection />
<SocialProofBar />
<FeaturesSection />
<ServerMap />
<SupportHighlightsSection />
<FeaturesSection />
<HowItWorksSection />
<PerformanceSection />
<UseCasesSection />
+53
View File
@@ -150,6 +150,59 @@
}
@layer utilities {
@keyframes tag-cloud-path-a {
0% { transform: translate3d(0, 0, 0); }
20% { transform: translate3d(8px, -10px, 0); }
45% { transform: translate3d(16px, 2px, 0); }
70% { transform: translate3d(6px, 12px, 0); }
100% { transform: translate3d(0, 0, 0); }
}
@keyframes tag-cloud-path-b {
0% { transform: translate3d(0, 0, 0); }
25% { transform: translate3d(-10px, -6px, 0); }
50% { transform: translate3d(-16px, 8px, 0); }
75% { transform: translate3d(-4px, 14px, 0); }
100% { transform: translate3d(0, 0, 0); }
}
@keyframes tag-cloud-path-c {
0% { transform: translate3d(0, 0, 0); }
25% { transform: translate3d(12px, 6px, 0); }
50% { transform: translate3d(4px, -14px, 0); }
75% { transform: translate3d(-10px, -8px, 0); }
100% { transform: translate3d(0, 0, 0); }
}
@keyframes tag-cloud-path-d {
0% { transform: translate3d(0, 0, 0); }
20% { transform: translate3d(-6px, 10px, 0); }
45% { transform: translate3d(10px, 16px, 0); }
70% { transform: translate3d(14px, -4px, 0); }
100% { transform: translate3d(0, 0, 0); }
}
.tag-cloud-motion-a { animation: tag-cloud-path-a 11s cubic-bezier(0.4, 0, 0.2, 1) infinite; }
.tag-cloud-motion-b { animation: tag-cloud-path-b 13s cubic-bezier(0.4, 0, 0.2, 1) infinite; }
.tag-cloud-motion-c { animation: tag-cloud-path-c 12s cubic-bezier(0.4, 0, 0.2, 1) infinite; }
.tag-cloud-motion-d { animation: tag-cloud-path-d 14s cubic-bezier(0.4, 0, 0.2, 1) infinite; }
.tag-cloud-item {
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, filter 0.2s ease;
cursor: pointer;
will-change: transform, filter;
}
.tag-cloud-item:hover {
transform: scale(1.08);
filter: brightness(1.1);
}
.tag-cloud-item:focus-visible {
outline: 2px solid var(--gekon-green);
outline-offset: 2px;
}
.glass-card {
background: var(--glass);
backdrop-filter: blur(16px);
+3153
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB