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
+9 -9
View File
@@ -1,9 +1,9 @@
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.DS_Store
dist
.cache
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.DS_Store
dist
.cache
+187 -187
View File
@@ -1,187 +1,187 @@
# 🐛 Отладка Gekon
## Проблема: Не видно частицы
### Возможные причины:
1. **Canvas не поддерживается браузером**
- Откройте консоль браузера (F12)
- Проверьте ошибки JavaScript
2. **Частицы за другими элементами**
- Проверьте z-index
- Убедитесь, что ParticlesBackground рендерится
3. **Opacity слишком низкая**
- Частицы имеют opacity: 0.4
- На светлом фоне могут быть не видны
### Быстрая проверка:
1. Откройте http://localhost:5173 (или 8080)
2. Нажмите F12 (DevTools)
3. Перейдите в Console
4. Проверьте ошибки
### Типичные ошибки:
#### Ошибка: "Cannot read property 'getContext' of null"
**Решение:** Canvas ref не инициализирован
```typescript
// Проверьте, что canvas монтируется
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) {
console.error('Canvas not found!');
return;
}
// ...
}, []);
```
#### Ошибка: "ResizeObserver loop limit exceeded"
**Решение:** Это предупреждение, можно игнорировать
#### Частицы не видны
**Решение 1:** Увеличьте opacity
```typescript
// В ParticlesBackground.tsx, строка ~60
ctx.fillStyle = `rgba(16, 185, 129, ${particle.opacity * 2})`; // Увеличили в 2 раза
```
**Решение 2:** Увеличьте размер частиц
```typescript
// В ParticlesBackground.tsx, строка ~40
size: Math.random() * 4 + 2, // Было: * 2 + 1
```
**Решение 3:** Увеличьте количество
```typescript
// В ParticlesBackground.tsx, строка ~35
const particleCount = Math.min(200, ...); // Было: 100
```
### Проверка рендеринга:
Добавьте console.log в ParticlesBackground:
```typescript
useEffect(() => {
console.log('ParticlesBackground mounted!');
const canvas = canvasRef.current;
if (!canvas) {
console.error('Canvas not found!');
return;
}
console.log('Canvas found:', canvas.width, 'x', canvas.height);
console.log('Particles count:', particlesRef.current.length);
// ...
}, []);
```
### Проверка в браузере:
1. Откройте DevTools (F12)
2. Elements tab
3. Найдите `<canvas>` элемент
4. Проверьте стили:
- `position: absolute`
- `inset: 0`
- `opacity: 0.4`
- `z-index` не перекрыт
### Временное решение (для теста):
Сделайте частицы очень заметными:
```typescript
// В ParticlesBackground.tsx
// Строка ~60 - увеличьте opacity
ctx.fillStyle = `rgba(16, 185, 129, 1)`; // Полная непрозрачность
// Строка ~40 - увеличьте размер
size: Math.random() * 10 + 5, // Большие частицы
// Строка ~35 - больше частиц
const particleCount = 200;
```
### Проверка карты серверов:
Если карта не работает:
1. Откройте консоль (F12)
2. Проверьте ошибки в ServerMap
3. Убедитесь, что hover работает
**Типичная проблема:** Tooltips не показываются
```typescript
// Проверьте z-index в ServerMap.tsx
// Tooltip должен иметь z-10
className="... z-10 ..."
```
### Перезапуск с чистого листа:
```bash
# Остановите Docker
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose down"
# Удалите volumes
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose down -v"
# Пересоберите
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose up --build"
```
### Проверка портов:
Текущий порт может быть 8080 вместо 5173:
- Попробуйте http://localhost:8080
- Или http://localhost:5173
### Логи Docker:
```bash
# Смотрите логи в реальном времени
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose logs -f"
```
### Если ничего не помогает:
1. Откройте http://localhost:8080 (или 5173)
2. Нажмите F12
3. Скопируйте все ошибки из Console
4. Проверьте Network tab - все ли файлы загружаются
---
## Быстрый тест частиц:
Добавьте в консоль браузера:
```javascript
// Проверка Canvas API
const canvas = document.querySelector('canvas');
if (canvas) {
console.log('Canvas found!', canvas.width, canvas.height);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 200, 200); // Красный квадрат для теста
} else {
console.error('Canvas NOT found!');
}
```
Если увидите красный квадрат - Canvas работает, проблема в коде частиц.
Если нет - Canvas не рендерится.
---
**Текущий статус:**
- ✅ Docker запущен
- ✅ Vite работает
- ❓ Частицы - нужна проверка
- ❓ Карта - нужна проверка
**Порт:** http://localhost:8080 или http://localhost:5173
# 🐛 Отладка Gekon
## Проблема: Не видно частицы
### Возможные причины:
1. **Canvas не поддерживается браузером**
- Откройте консоль браузера (F12)
- Проверьте ошибки JavaScript
2. **Частицы за другими элементами**
- Проверьте z-index
- Убедитесь, что ParticlesBackground рендерится
3. **Opacity слишком низкая**
- Частицы имеют opacity: 0.4
- На светлом фоне могут быть не видны
### Быстрая проверка:
1. Откройте http://localhost:5173 (или 8080)
2. Нажмите F12 (DevTools)
3. Перейдите в Console
4. Проверьте ошибки
### Типичные ошибки:
#### Ошибка: "Cannot read property 'getContext' of null"
**Решение:** Canvas ref не инициализирован
```typescript
// Проверьте, что canvas монтируется
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) {
console.error('Canvas not found!');
return;
}
// ...
}, []);
```
#### Ошибка: "ResizeObserver loop limit exceeded"
**Решение:** Это предупреждение, можно игнорировать
#### Частицы не видны
**Решение 1:** Увеличьте opacity
```typescript
// В ParticlesBackground.tsx, строка ~60
ctx.fillStyle = `rgba(16, 185, 129, ${particle.opacity * 2})`; // Увеличили в 2 раза
```
**Решение 2:** Увеличьте размер частиц
```typescript
// В ParticlesBackground.tsx, строка ~40
size: Math.random() * 4 + 2, // Было: * 2 + 1
```
**Решение 3:** Увеличьте количество
```typescript
// В ParticlesBackground.tsx, строка ~35
const particleCount = Math.min(200, ...); // Было: 100
```
### Проверка рендеринга:
Добавьте console.log в ParticlesBackground:
```typescript
useEffect(() => {
console.log('ParticlesBackground mounted!');
const canvas = canvasRef.current;
if (!canvas) {
console.error('Canvas not found!');
return;
}
console.log('Canvas found:', canvas.width, 'x', canvas.height);
console.log('Particles count:', particlesRef.current.length);
// ...
}, []);
```
### Проверка в браузере:
1. Откройте DevTools (F12)
2. Elements tab
3. Найдите `<canvas>` элемент
4. Проверьте стили:
- `position: absolute`
- `inset: 0`
- `opacity: 0.4`
- `z-index` не перекрыт
### Временное решение (для теста):
Сделайте частицы очень заметными:
```typescript
// В ParticlesBackground.tsx
// Строка ~60 - увеличьте opacity
ctx.fillStyle = `rgba(16, 185, 129, 1)`; // Полная непрозрачность
// Строка ~40 - увеличьте размер
size: Math.random() * 10 + 5, // Большие частицы
// Строка ~35 - больше частиц
const particleCount = 200;
```
### Проверка карты серверов:
Если карта не работает:
1. Откройте консоль (F12)
2. Проверьте ошибки в ServerMap
3. Убедитесь, что hover работает
**Типичная проблема:** Tooltips не показываются
```typescript
// Проверьте z-index в ServerMap.tsx
// Tooltip должен иметь z-10
className="... z-10 ..."
```
### Перезапуск с чистого листа:
```bash
# Остановите Docker
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose down"
# Удалите volumes
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose down -v"
# Пересоберите
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose up --build"
```
### Проверка портов:
Текущий порт может быть 8080 вместо 5173:
- Попробуйте http://localhost:8080
- Или http://localhost:5173
### Логи Docker:
```bash
# Смотрите логи в реальном времени
wsl bash -c "cd gekon-speed-boost-3ba17197-main && docker compose logs -f"
```
### Если ничего не помогает:
1. Откройте http://localhost:8080 (или 5173)
2. Нажмите F12
3. Скопируйте все ошибки из Console
4. Проверьте Network tab - все ли файлы загружаются
---
## Быстрый тест частиц:
Добавьте в консоль браузера:
```javascript
// Проверка Canvas API
const canvas = document.querySelector('canvas');
if (canvas) {
console.log('Canvas found!', canvas.width, canvas.height);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 200, 200); // Красный квадрат для теста
} else {
console.error('Canvas NOT found!');
}
```
Если увидите красный квадрат - Canvas работает, проблема в коде частиц.
Если нет - Canvas не рендерится.
---
**Текущий статус:**
- ✅ Docker запущен
- ✅ Vite работает
- ❓ Частицы - нужна проверка
- ❓ Карта - нужна проверка
**Порт:** http://localhost:8080 или http://localhost:5173
+18 -18
View File
@@ -1,18 +1,18 @@
FROM node:22-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy project files
COPY . .
# Expose port
EXPOSE 5173
# Start dev server
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
FROM node:22-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy project files
COPY . .
# Expose port
EXPOSE 5173
# Start dev server
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
+305 -305
View File
@@ -1,305 +1,305 @@
# ✨ Gekon - Новые возможности
## 🎨 1. Анимированный фон с частицами
### Описание
Динамический фон с анимированными частицами, создающий эффект сетевых соединений.
### Технические детали
- **Технология:** Canvas API
- **Производительность:** Оптимизирован для 60 FPS
- **Адаптивность:** Автоматическое масштабирование под размер экрана
- **Цвета:** Gekon green (#10b981) и cyan (#06b6d4)
### Возможности
✅ Плавная анимация частиц
✅ Динамические связи между близкими частицами
✅ Wrap-around эффект (частицы появляются с другой стороны)
✅ Автоматическая очистка при размонтировании
✅ Blend mode для лучшей интеграции с фоном
### Настройка
#### Количество частиц
```typescript
// src/components/ParticlesBackground.tsx, строка ~35
const particleCount = Math.min(100, Math.floor((canvas.width * canvas.height) / 15000));
```
- Увеличьте делитель (15000 → 20000) для меньшего количества
- Уменьшите (15000 → 10000) для большего количества
#### Скорость движения
```typescript
// src/components/ParticlesBackground.tsx, строка ~40
vx: (Math.random() - 0.5) * 0.5, // Измените 0.5 на нужную скорость
vy: (Math.random() - 0.5) * 0.5,
```
#### Дистанция связей
```typescript
// src/components/ParticlesBackground.tsx, строка ~70
if (distance < 150) { // Измените 150 на нужную дистанцию
```
#### Цвета
```typescript
// Частицы (строка ~60)
ctx.fillStyle = `rgba(16, 185, 129, ${particle.opacity})`;
// Связи (строка ~75)
ctx.strokeStyle = `rgba(6, 182, 212, ${opacity})`;
```
### Производительность
- **Desktop:** ~100 частиц, 60 FPS
- **Tablet:** ~60 частиц, 60 FPS
- **Mobile:** ~40 частиц, 60 FPS
---
## 🗺️ 2. Интерактивная карта серверов
### Описание
Интерактивная карта с 25+ локациями серверов по всему миру, показывающая статистику в реальном времени.
### Технические детали
- **Технология:** SVG + React
- **Интерактивность:** Hover tooltips
- **Анимация:** Пульсирующие точки, анимированные линии
- **Данные:** Статические (можно подключить API)
### Возможности
✅ 25+ локаций серверов
✅ Hover tooltips с деталями
✅ Статистика (пользователи, задержка)
✅ Индикаторы статуса (online/maintenance)
✅ Анимированные пульсации
✅ Линии соединений между серверами
✅ Адаптивный дизайн
### Локации серверов
#### Северная Америка (3)
- 🇺🇸 Los Angeles - 1,250 users, 12ms
- 🇺🇸 New York - 1,840 users, 8ms
- 🇨🇦 Toronto - 680 users, 15ms
#### Южная Америка (2)
- 🇧🇷 São Paulo - 520 users, 45ms
- 🇦🇷 Buenos Aires - 280 users, 52ms
#### Европа (10)
- 🇬🇧 London - 2,100 users, 5ms
- 🇩🇪 Frankfurt - 1,650 users, 7ms
- 🇫🇷 Paris - 980 users, 9ms
- 🇳🇱 Amsterdam - 1,420 users, 6ms
- 🇸🇪 Stockholm - 540 users, 12ms
- 🇵🇱 Warsaw - 720 users, 14ms
- 🇪🇸 Madrid - 650 users, 18ms
- 🇮🇹 Milan - 580 users, 16ms
- 🇹🇷 Istanbul - 680 users, 20ms
- 🇮🇱 Tel Aviv - 420 users, 26ms
#### Азия (8)
- 🇯🇵 Tokyo - 1,950 users, 8ms
- 🇸🇬 Singapore - 1,680 users, 10ms
- 🇭🇰 Hong Kong - 1,420 users, 12ms
- 🇰🇷 Seoul - 1,280 users, 9ms
- 🇮🇳 Mumbai - 890 users, 25ms
- 🇦🇪 Dubai - 740 users, 22ms
- 🇹🇭 Bangkok - 620 users, 28ms
- 🇻🇳 Ho Chi Minh - 480 users, 32ms
#### Океания (2)
- 🇦🇺 Sydney - 920 users, 18ms
- 🇳🇿 Auckland - 340 users, 24ms
#### Африка (1)
- 🇿🇦 Cape Town - 380 users, 48ms
**Всего:** 25 серверов, 23,000+ активных пользователей
### Добавление нового сервера
```typescript
// src/components/ServerMap.tsx, добавьте в массив servers:
{
id: "moscow", // Уникальный ID
country: "Russia", // Страна
city: "Moscow", // Город
x: 55, // Позиция X (% от левого края, 0-100)
y: 30, // Позиция Y (% от верхнего края, 0-100)
users: 2500, // Количество пользователей
latency: 5, // Задержка в миллисекундах
status: "online" // Статус: "online" или "maintenance"
}
```
### Позиционирование серверов
Координаты X и Y в процентах (0-100):
```
Примерные координаты регионов:
- Западное побережье США: x: 15, y: 35
- Восточное побережье США: x: 22, y: 32
- Западная Европа: x: 48-52, y: 28-35
- Восточная Европа: x: 54-58, y: 30-35
- Ближний Восток: x: 56-60, y: 35-42
- Южная Азия: x: 68-72, y: 42-48
- Восточная Азия: x: 76-82, y: 34-42
- Юго-Восточная Азия: x: 74-78, y: 48-52
- Австралия: x: 85, y: 72
```
### Настройка внешнего вида
#### Цвет точек
```typescript
// src/components/ServerMap.tsx, строка ~180
className={`... ${
server.status === "online"
? "bg-gekon-green shadow-lg shadow-gekon-green/50" // Зелёный для online
: "bg-yellow-500 shadow-lg shadow-yellow-500/50" // Жёлтый для maintenance
}`}
```
#### Размер точек
```typescript
// src/components/ServerMap.tsx, строка ~182
<div className="relative h-3 w-3 rounded-full ...">
// Измените h-3 w-3 на h-4 w-4 для больших точек
```
#### Скорость пульсации
```css
/* src/styles.css */
.animate-ping {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
/* Измените 1s на 2s для медленной пульсации */
```
### Интеграция с API
Для подключения реальных данных:
```typescript
// src/hooks/useServerStats.ts (создайте этот файл)
import { useState, useEffect } from 'react';
export function useServerStats() {
const [servers, setServers] = useState<ServerLocation[]>([]);
useEffect(() => {
const fetchStats = async () => {
const response = await fetch('https://your-api.com/servers');
const data = await response.json();
setServers(data);
};
fetchStats();
const interval = setInterval(fetchStats, 60000); // Обновление каждую минуту
return () => clearInterval(interval);
}, []);
return servers;
}
// В ServerMap.tsx:
const servers = useServerStats(); // Вместо статического массива
```
---
## 🎯 Статистика
### Общая статистика карты
- **Всего серверов:** 25+
- **Активных пользователей:** 23,000+
- **Средняя задержка:** 18ms
- **Покрытие:** 50+ стран
### Производительность
- **Рендеринг:** < 16ms (60 FPS)
- **Размер компонента:** ~8KB (gzipped)
- **Зависимости:** Только React + Lucide icons
---
## 🌍 Интернационализация
Добавлены переводы для новых секций:
### Английский (en)
```typescript
tech_title_1: "Global Network"
tech_title_2: "Infrastructure"
tech_subtitle: "Our worldwide network of optimization nodes..."
```
### Русский (ru)
```typescript
tech_title_1: "Глобальная сетевая"
tech_title_2: "инфраструктура"
tech_subtitle: "Наша всемирная сеть узлов оптимизации..."
```
### Китайский (zh)
```typescript
tech_title_1: "全球网络"
tech_title_2: "基础设施"
tech_subtitle: "我们的全球优化节点网络..."
```
---
## 📊 Сравнение до/после
### До добавления новых возможностей
- ❌ Статичный фон
- ❌ Нет визуализации серверов
- ❌ Только текстовое описание инфраструктуры
### После добавления
- ✅ Динамический анимированный фон
- ✅ Интерактивная карта с 25+ серверами
- ✅ Визуализация глобальной сети
- ✅ Статистика в реальном времени
- ✅ Улучшенный UX и визуальная привлекательность
---
## 🚀 Следующие улучшения
### Возможные дополнения:
1. **Real-time данные**
- Подключение к API для актуальной статистики
- WebSocket для обновлений в реальном времени
2. **Фильтры на карте**
- Фильтр по регионам
- Фильтр по задержке
- Поиск по городу/стране
3. **Расширенная аналитика**
- График загрузки серверов
- История задержек
- Прогноз доступности
4. **3D визуализация**
- Three.js для 3D глобуса
- Анимированные траектории данных
5. **Дополнительные эффекты**
- Particle trails при движении мыши
- Звуковые эффекты при hover
- Тематические цветовые схемы
---
**Версия:** 1.0.0
**Дата:** 23.01.2026
**Статус:** ✅ Готово к использованию
# ✨ Gekon - Новые возможности
## 🎨 1. Анимированный фон с частицами
### Описание
Динамический фон с анимированными частицами, создающий эффект сетевых соединений.
### Технические детали
- **Технология:** Canvas API
- **Производительность:** Оптимизирован для 60 FPS
- **Адаптивность:** Автоматическое масштабирование под размер экрана
- **Цвета:** Gekon green (#10b981) и cyan (#06b6d4)
### Возможности
✅ Плавная анимация частиц
✅ Динамические связи между близкими частицами
✅ Wrap-around эффект (частицы появляются с другой стороны)
✅ Автоматическая очистка при размонтировании
✅ Blend mode для лучшей интеграции с фоном
### Настройка
#### Количество частиц
```typescript
// src/components/ParticlesBackground.tsx, строка ~35
const particleCount = Math.min(100, Math.floor((canvas.width * canvas.height) / 15000));
```
- Увеличьте делитель (15000 → 20000) для меньшего количества
- Уменьшите (15000 → 10000) для большего количества
#### Скорость движения
```typescript
// src/components/ParticlesBackground.tsx, строка ~40
vx: (Math.random() - 0.5) * 0.5, // Измените 0.5 на нужную скорость
vy: (Math.random() - 0.5) * 0.5,
```
#### Дистанция связей
```typescript
// src/components/ParticlesBackground.tsx, строка ~70
if (distance < 150) { // Измените 150 на нужную дистанцию
```
#### Цвета
```typescript
// Частицы (строка ~60)
ctx.fillStyle = `rgba(16, 185, 129, ${particle.opacity})`;
// Связи (строка ~75)
ctx.strokeStyle = `rgba(6, 182, 212, ${opacity})`;
```
### Производительность
- **Desktop:** ~100 частиц, 60 FPS
- **Tablet:** ~60 частиц, 60 FPS
- **Mobile:** ~40 частиц, 60 FPS
---
## 🗺️ 2. Интерактивная карта серверов
### Описание
Интерактивная карта с 25+ локациями серверов по всему миру, показывающая статистику в реальном времени.
### Технические детали
- **Технология:** SVG + React
- **Интерактивность:** Hover tooltips
- **Анимация:** Пульсирующие точки, анимированные линии
- **Данные:** Статические (можно подключить API)
### Возможности
✅ 25+ локаций серверов
✅ Hover tooltips с деталями
✅ Статистика (пользователи, задержка)
✅ Индикаторы статуса (online/maintenance)
✅ Анимированные пульсации
✅ Линии соединений между серверами
✅ Адаптивный дизайн
### Локации серверов
#### Северная Америка (3)
- 🇺🇸 Los Angeles - 1,250 users, 12ms
- 🇺🇸 New York - 1,840 users, 8ms
- 🇨🇦 Toronto - 680 users, 15ms
#### Южная Америка (2)
- 🇧🇷 São Paulo - 520 users, 45ms
- 🇦🇷 Buenos Aires - 280 users, 52ms
#### Европа (10)
- 🇬🇧 London - 2,100 users, 5ms
- 🇩🇪 Frankfurt - 1,650 users, 7ms
- 🇫🇷 Paris - 980 users, 9ms
- 🇳🇱 Amsterdam - 1,420 users, 6ms
- 🇸🇪 Stockholm - 540 users, 12ms
- 🇵🇱 Warsaw - 720 users, 14ms
- 🇪🇸 Madrid - 650 users, 18ms
- 🇮🇹 Milan - 580 users, 16ms
- 🇹🇷 Istanbul - 680 users, 20ms
- 🇮🇱 Tel Aviv - 420 users, 26ms
#### Азия (8)
- 🇯🇵 Tokyo - 1,950 users, 8ms
- 🇸🇬 Singapore - 1,680 users, 10ms
- 🇭🇰 Hong Kong - 1,420 users, 12ms
- 🇰🇷 Seoul - 1,280 users, 9ms
- 🇮🇳 Mumbai - 890 users, 25ms
- 🇦🇪 Dubai - 740 users, 22ms
- 🇹🇭 Bangkok - 620 users, 28ms
- 🇻🇳 Ho Chi Minh - 480 users, 32ms
#### Океания (2)
- 🇦🇺 Sydney - 920 users, 18ms
- 🇳🇿 Auckland - 340 users, 24ms
#### Африка (1)
- 🇿🇦 Cape Town - 380 users, 48ms
**Всего:** 25 серверов, 23,000+ активных пользователей
### Добавление нового сервера
```typescript
// src/components/ServerMap.tsx, добавьте в массив servers:
{
id: "moscow", // Уникальный ID
country: "Russia", // Страна
city: "Moscow", // Город
x: 55, // Позиция X (% от левого края, 0-100)
y: 30, // Позиция Y (% от верхнего края, 0-100)
users: 2500, // Количество пользователей
latency: 5, // Задержка в миллисекундах
status: "online" // Статус: "online" или "maintenance"
}
```
### Позиционирование серверов
Координаты X и Y в процентах (0-100):
```
Примерные координаты регионов:
- Западное побережье США: x: 15, y: 35
- Восточное побережье США: x: 22, y: 32
- Западная Европа: x: 48-52, y: 28-35
- Восточная Европа: x: 54-58, y: 30-35
- Ближний Восток: x: 56-60, y: 35-42
- Южная Азия: x: 68-72, y: 42-48
- Восточная Азия: x: 76-82, y: 34-42
- Юго-Восточная Азия: x: 74-78, y: 48-52
- Австралия: x: 85, y: 72
```
### Настройка внешнего вида
#### Цвет точек
```typescript
// src/components/ServerMap.tsx, строка ~180
className={`... ${
server.status === "online"
? "bg-gekon-green shadow-lg shadow-gekon-green/50" // Зелёный для online
: "bg-yellow-500 shadow-lg shadow-yellow-500/50" // Жёлтый для maintenance
}`}
```
#### Размер точек
```typescript
// src/components/ServerMap.tsx, строка ~182
<div className="relative h-3 w-3 rounded-full ...">
// Измените h-3 w-3 на h-4 w-4 для больших точек
```
#### Скорость пульсации
```css
/* src/styles.css */
.animate-ping {
animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
/* Измените 1s на 2s для медленной пульсации */
```
### Интеграция с API
Для подключения реальных данных:
```typescript
// src/hooks/useServerStats.ts (создайте этот файл)
import { useState, useEffect } from 'react';
export function useServerStats() {
const [servers, setServers] = useState<ServerLocation[]>([]);
useEffect(() => {
const fetchStats = async () => {
const response = await fetch('https://your-api.com/servers');
const data = await response.json();
setServers(data);
};
fetchStats();
const interval = setInterval(fetchStats, 60000); // Обновление каждую минуту
return () => clearInterval(interval);
}, []);
return servers;
}
// В ServerMap.tsx:
const servers = useServerStats(); // Вместо статического массива
```
---
## 🎯 Статистика
### Общая статистика карты
- **Всего серверов:** 25+
- **Активных пользователей:** 23,000+
- **Средняя задержка:** 18ms
- **Покрытие:** 50+ стран
### Производительность
- **Рендеринг:** < 16ms (60 FPS)
- **Размер компонента:** ~8KB (gzipped)
- **Зависимости:** Только React + Lucide icons
---
## 🌍 Интернационализация
Добавлены переводы для новых секций:
### Английский (en)
```typescript
tech_title_1: "Global Network"
tech_title_2: "Infrastructure"
tech_subtitle: "Our worldwide network of optimization nodes..."
```
### Русский (ru)
```typescript
tech_title_1: "Глобальная сетевая"
tech_title_2: "инфраструктура"
tech_subtitle: "Наша всемирная сеть узлов оптимизации..."
```
### Китайский (zh)
```typescript
tech_title_1: "全球网络"
tech_title_2: "基础设施"
tech_subtitle: "我们的全球优化节点网络..."
```
---
## 📊 Сравнение до/после
### До добавления новых возможностей
- ❌ Статичный фон
- ❌ Нет визуализации серверов
- ❌ Только текстовое описание инфраструктуры
### После добавления
- ✅ Динамический анимированный фон
- ✅ Интерактивная карта с 25+ серверами
- ✅ Визуализация глобальной сети
- ✅ Статистика в реальном времени
- ✅ Улучшенный UX и визуальная привлекательность
---
## 🚀 Следующие улучшения
### Возможные дополнения:
1. **Real-time данные**
- Подключение к API для актуальной статистики
- WebSocket для обновлений в реальном времени
2. **Фильтры на карте**
- Фильтр по регионам
- Фильтр по задержке
- Поиск по городу/стране
3. **Расширенная аналитика**
- График загрузки серверов
- История задержек
- Прогноз доступности
4. **3D визуализация**
- Three.js для 3D глобуса
- Анимированные траектории данных
5. **Дополнительные эффекты**
- Particle trails при движении мыши
- Звуковые эффекты при hover
- Тематические цветовые схемы
---
**Версия:** 1.0.0
**Дата:** 23.01.2026
**Статус:** ✅ Готово к использованию
+111 -111
View File
@@ -1,111 +1,111 @@
# 🚀 Gekon - Быстрый старт
## За 3 минуты до запуска
### 1. Установка (1 мин)
```bash
cd gekon-speed-boost-3ba17197-main
npm install
```
### 2. Запуск (30 сек)
```bash
npm run dev
```
### 3. Открыть (30 сек)
```
http://localhost:5173
```
## ✅ Что добавлено
### Анимированный фон с частицами
- Файл: `src/components/ParticlesBackground.tsx`
- Плавная анимация на Canvas
- Цвета бренда Gekon
### Интерактивная карта серверов
- Файл: `src/components/ServerMap.tsx`
- 25+ локаций по всему миру
- Hover для деталей сервера
- Статистика в реальном времени
## 🎨 Быстрая настройка
### Добавить свой сервер на карту
```typescript
// src/components/ServerMap.tsx, строка ~15
{
id: "moscow",
country: "Russia",
city: "Moscow",
x: 55, // % слева (0-100)
y: 30, // % сверху (0-100)
users: 2500, // количество пользователей
latency: 5, // задержка в мс
status: "online"
}
```
### Изменить количество частиц
```typescript
// src/components/ParticlesBackground.tsx, строка ~35
const particleCount = Math.min(100, ...);
// Измените 100 на нужное число
```
### Подключить к Sub-Bridge
```typescript
// Создайте: src/config/subscription.ts
export const SUBSCRIPTION_CONFIG = {
baseUrl: 'https://subb.ydns.eu',
supportUrl: 'https://subb.ydns.eu/',
};
// В компонентах замените:
<Button onClick={() => window.location.href = SUBSCRIPTION_CONFIG.baseUrl}>
```
## 🌍 Языки
Переключатель в навигации (правый верхний угол):
- 🇬🇧 English
- 🇷🇺 Русский
- 🇨🇳 中文
## 📦 Сборка
```bash
npm run build
npm run preview
```
## 🎯 Что дальше?
1. Добавьте реальные ссылки на подписку
2. Интегрируйте Telegram бот
3. Настройте аналитику
4. Оптимизируйте SEO
5. Добавьте Cookie Consent
## 📚 Документация
- `SETUP.md` - Полная инструкция
- `README.ru.md` - Подробное описание на русском
- Исходный код хорошо документирован
## 🐛 Проблемы?
```bash
# Переустановите зависимости
rm -rf node_modules package-lock.json
npm install
# Очистите кэш
npm run build -- --force
```
---
**Готово!** Лендинг запущен и готов к кастомизации 🎉
# 🚀 Gekon - Быстрый старт
## За 3 минуты до запуска
### 1. Установка (1 мин)
```bash
cd gekon-speed-boost-3ba17197-main
npm install
```
### 2. Запуск (30 сек)
```bash
npm run dev
```
### 3. Открыть (30 сек)
```
http://localhost:5173
```
## ✅ Что добавлено
### Анимированный фон с частицами
- Файл: `src/components/ParticlesBackground.tsx`
- Плавная анимация на Canvas
- Цвета бренда Gekon
### Интерактивная карта серверов
- Файл: `src/components/ServerMap.tsx`
- 25+ локаций по всему миру
- Hover для деталей сервера
- Статистика в реальном времени
## 🎨 Быстрая настройка
### Добавить свой сервер на карту
```typescript
// src/components/ServerMap.tsx, строка ~15
{
id: "moscow",
country: "Russia",
city: "Moscow",
x: 55, // % слева (0-100)
y: 30, // % сверху (0-100)
users: 2500, // количество пользователей
latency: 5, // задержка в мс
status: "online"
}
```
### Изменить количество частиц
```typescript
// src/components/ParticlesBackground.tsx, строка ~35
const particleCount = Math.min(100, ...);
// Измените 100 на нужное число
```
### Подключить к Sub-Bridge
```typescript
// Создайте: src/config/subscription.ts
export const SUBSCRIPTION_CONFIG = {
baseUrl: 'https://subb.ydns.eu',
supportUrl: 'https://subb.ydns.eu/',
};
// В компонентах замените:
<Button onClick={() => window.location.href = SUBSCRIPTION_CONFIG.baseUrl}>
```
## 🌍 Языки
Переключатель в навигации (правый верхний угол):
- 🇬🇧 English
- 🇷🇺 Русский
- 🇨🇳 中文
## 📦 Сборка
```bash
npm run build
npm run preview
```
## 🎯 Что дальше?
1. Добавьте реальные ссылки на подписку
2. Интегрируйте Telegram бот
3. Настройте аналитику
4. Оптимизируйте SEO
5. Добавьте Cookie Consent
## 📚 Документация
- `SETUP.md` - Полная инструкция
- `README.ru.md` - Подробное описание на русском
- Исходный код хорошо документирован
## 🐛 Проблемы?
```bash
# Переустановите зависимости
rm -rf node_modules package-lock.json
npm install
# Очистите кэш
npm run build -- --force
```
---
**Готово!** Лендинг запущен и готов к кастомизации 🎉
+278 -278
View File
@@ -1,278 +1,278 @@
# Gekon - Лендинг для сервиса ускорения интернета
## 🎯 Описание
Современный одностраничный лендинг для сервиса **Gekon** - технологичного решения для оптимизации и ускорения интернет-соединений.
## ✨ Новые возможности
### 1. Анимированный фон с частицами ✅
- Плавная анимация частиц на Canvas
- Динамические связи между частицами
- Цвета бренда Gekon (зелёный/циан)
- Оптимизирован для производительности
- Адаптивный под размер экрана
### 2. Интерактивная карта серверов ✅
- 25+ локаций серверов по всему миру
- Всплывающие подсказки при наведении
- Статистика в реальном времени (пользователи, задержка)
- Анимированные пульсирующие эффекты
- Линии соединений между серверами
- Индикаторы статуса (онлайн/обслуживание)
## 🚀 Быстрый старт
### Установка зависимостей
```bash
npm install
# или
bun install
```
### Запуск dev-сервера
```bash
npm run dev
# или
bun run dev
```
Откройте http://localhost:5173 в браузере.
### Сборка для продакшена
```bash
npm run build
npm run preview
```
## 🌍 Поддержка языков
Лендинг поддерживает 3 языка:
- 🇬🇧 Английский (English)
- 🇷🇺 Русский
- 🇨🇳 Китайский (中文)
Переключатель языков находится в навигации (правый верхний угол).
## 📊 Структура проекта
```
src/
├── components/
│ ├── ParticlesBackground.tsx ← Анимированные частицы
│ ├── ServerMap.tsx ← Интерактивная карта
│ ├── HeroSection.tsx ← Главный экран
│ ├── Navbar.tsx ← Навигация
│ ├── FeaturesSection.tsx ← Возможности
│ ├── PricingSection.tsx ← Тарифы
│ └── ... другие компоненты
├── i18n/
│ ├── translations.ts ← Переводы
│ └── context.tsx
└── routes/
└── index.tsx ← Главная страница
```
## 🎨 Кастомизация
### Изменить локации серверов
Отредактируйте `src/components/ServerMap.tsx`:
```typescript
const servers: ServerLocation[] = [
{
id: "moscow",
country: "Russia",
city: "Moscow",
x: 55, // % слева
y: 30, // % сверху
users: 2500,
latency: 5,
status: "online"
},
// ... больше серверов
];
```
### Настроить количество частиц
Отредактируйте `src/components/ParticlesBackground.tsx`:
```typescript
// Строка ~35
const particleCount = Math.min(100, Math.floor((canvas.width * canvas.height) / 15000));
// Увеличьте делитель (15000) для меньшего количества частиц
// Уменьшите для большего количества
```
### Изменить цвета бренда
Отредактируйте `src/styles.css`:
```css
--gekon-green: oklch(0.72 0.19 160); /* Основной зелёный */
--gekon-cyan: oklch(0.7 0.14 200); /* Циан */
--gekon-purple: oklch(0.6 0.25 300); /* Фиолетовый */
--gekon-neon: oklch(0.75 0.2 150); /* Неоновый зелёный */
```
## 🔗 Интеграция с Sub-Bridge
Чтобы подключить к вашему реальному VPN-сервису:
### 1. Создайте конфиг
```typescript
// src/config/subscription.ts
export const SUBSCRIPTION_CONFIG = {
baseUrl: 'https://subb.ydns.eu',
supportUrl: 'https://subb.ydns.eu/',
logoUrl: 'https://raw.githubusercontent.com/arpicme/Proxy-App-Icon-set/refs/heads/main/white_background/Karing.svg',
announcement: 'Спасибо за подписку! Ваша безопасность - наш приоритет.',
};
```
### 2. Обновите CTA кнопки
```typescript
// В компонентах (HeroSection, PricingSection и т.д.)
import { SUBSCRIPTION_CONFIG } from '@/config/subscription';
<Button onClick={() => window.location.href = SUBSCRIPTION_CONFIG.baseUrl}>
Начать
</Button>
```
### 3. Добавьте Telegram виджет
```typescript
// src/components/TelegramWidget.tsx
export function TelegramWidget() {
return (
<a
href="https://t.me/your_support_bot"
className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-gradient-to-br from-gekon-green to-gekon-cyan shadow-lg hover:scale-110 transition-transform"
>
<MessageCircle size={24} className="text-white" />
</a>
);
}
```
## 🌐 Локации серверов
### Северная Америка
- 🇺🇸 США (Лос-Анджелес, Нью-Йорк)
- 🇨🇦 Канада (Торонто)
### Южная Америка
- 🇧🇷 Бразилия (Сан-Паулу)
- 🇦🇷 Аргентина (Буэнос-Айрес)
### Европа
- 🇬🇧 Великобритания (Лондон)
- 🇩🇪 Германия (Франкфурт)
- 🇫🇷 Франция (Париж)
- 🇳🇱 Нидерланды (Амстердам)
- 🇸🇪 Швеция (Стокгольм)
- 🇵🇱 Польша (Варшава)
- 🇪🇸 Испания (Мадрид)
- 🇮🇹 Италия (Милан)
- 🇹🇷 Турция (Стамбул)
- 🇮🇱 Израиль (Тель-Авив)
### Азия
- 🇯🇵 Япония (Токио)
- 🇸🇬 Сингапур
- 🇭🇰 Гонконг
- 🇰🇷 Южная Корея (Сеул)
- 🇮🇳 Индия (Мумбаи)
- 🇦🇪 ОАЭ (Дубай)
- 🇹🇭 Таиланд (Бангкок)
- 🇻🇳 Вьетнам (Хошимин)
### Океания
- 🇦🇺 Австралия (Сидней)
- 🇳🇿 Новая Зеландия (Окленд)
### Африка
- 🇿🇦 ЮАР (Кейптаун)
## 🛠️ Технологии
- **React 19** + TypeScript
- **TanStack Router** - маршрутизация
- **Tailwind CSS 4.2** - стилизация
- **Radix UI** - UI компоненты
- **Lucide React** - иконки
- **Vite** - сборка
- **Canvas API** - анимация частиц
## 📦 Деплой
### Cloudflare Pages
```bash
npm run build
# Загрузите папку dist/ на Cloudflare Pages
```
### Vercel
```bash
vercel deploy
```
### Netlify
```bash
netlify deploy --prod
```
## 🎯 Следующие шаги
1. ✅ Добавлены анимированные частицы
2. ✅ Создана интерактивная карта серверов
3. ⏳ Добавить реальные ссылки на подписку
4. ⏳ Интегрировать Telegram бот
5. ⏳ Добавить аналитику (Google Analytics)
6. ⏳ Оптимизировать SEO
7. ⏳ Добавить Cookie Consent (GDPR)
## 📝 Дополнительно
### Производительность
- Частицы автоматически масштабируются под размер экрана
- Lazy loading для изображений
- Оптимизированная сборка с code splitting
### Доступность
- ARIA метки
- Навигация с клавиатуры
- Поддержка screen readers
### SEO
- Мета-теги для всех страниц
- Open Graph теги
- Семантическая разметка HTML
## 🐛 Решение проблем
### Частицы не отображаются
- Проверьте консоль браузера на ошибки
- Убедитесь, что Canvas поддерживается
- Попробуйте уменьшить количество частиц
### Карта не работает
- Проверьте z-index конфликты
- Убедитесь, что hover события не блокируются
- Протестируйте в разных браузерах
### Ошибки сборки
```bash
# Очистите кэш и переустановите
rm -rf node_modules package-lock.json
npm install
```
## 📞 Поддержка
Для вопросов и проблем:
- Проверьте документацию в `SETUP.md`
- Изучите исходный код компонентов
- Протестируйте в разных браузерах
---
**Версия:** 1.0.0
**Обновлено:** 23.01.2026
**Создано с помощью:** React + TypeScript + Tailwind CSS
# Gekon - Лендинг для сервиса ускорения интернета
## 🎯 Описание
Современный одностраничный лендинг для сервиса **Gekon** - технологичного решения для оптимизации и ускорения интернет-соединений.
## ✨ Новые возможности
### 1. Анимированный фон с частицами ✅
- Плавная анимация частиц на Canvas
- Динамические связи между частицами
- Цвета бренда Gekon (зелёный/циан)
- Оптимизирован для производительности
- Адаптивный под размер экрана
### 2. Интерактивная карта серверов ✅
- 25+ локаций серверов по всему миру
- Всплывающие подсказки при наведении
- Статистика в реальном времени (пользователи, задержка)
- Анимированные пульсирующие эффекты
- Линии соединений между серверами
- Индикаторы статуса (онлайн/обслуживание)
## 🚀 Быстрый старт
### Установка зависимостей
```bash
npm install
# или
bun install
```
### Запуск dev-сервера
```bash
npm run dev
# или
bun run dev
```
Откройте http://localhost:5173 в браузере.
### Сборка для продакшена
```bash
npm run build
npm run preview
```
## 🌍 Поддержка языков
Лендинг поддерживает 3 языка:
- 🇬🇧 Английский (English)
- 🇷🇺 Русский
- 🇨🇳 Китайский (中文)
Переключатель языков находится в навигации (правый верхний угол).
## 📊 Структура проекта
```
src/
├── components/
│ ├── ParticlesBackground.tsx ← Анимированные частицы
│ ├── ServerMap.tsx ← Интерактивная карта
│ ├── HeroSection.tsx ← Главный экран
│ ├── Navbar.tsx ← Навигация
│ ├── FeaturesSection.tsx ← Возможности
│ ├── PricingSection.tsx ← Тарифы
│ └── ... другие компоненты
├── i18n/
│ ├── translations.ts ← Переводы
│ └── context.tsx
└── routes/
└── index.tsx ← Главная страница
```
## 🎨 Кастомизация
### Изменить локации серверов
Отредактируйте `src/components/ServerMap.tsx`:
```typescript
const servers: ServerLocation[] = [
{
id: "moscow",
country: "Russia",
city: "Moscow",
x: 55, // % слева
y: 30, // % сверху
users: 2500,
latency: 5,
status: "online"
},
// ... больше серверов
];
```
### Настроить количество частиц
Отредактируйте `src/components/ParticlesBackground.tsx`:
```typescript
// Строка ~35
const particleCount = Math.min(100, Math.floor((canvas.width * canvas.height) / 15000));
// Увеличьте делитель (15000) для меньшего количества частиц
// Уменьшите для большего количества
```
### Изменить цвета бренда
Отредактируйте `src/styles.css`:
```css
--gekon-green: oklch(0.72 0.19 160); /* Основной зелёный */
--gekon-cyan: oklch(0.7 0.14 200); /* Циан */
--gekon-purple: oklch(0.6 0.25 300); /* Фиолетовый */
--gekon-neon: oklch(0.75 0.2 150); /* Неоновый зелёный */
```
## 🔗 Интеграция с Sub-Bridge
Чтобы подключить к вашему реальному VPN-сервису:
### 1. Создайте конфиг
```typescript
// src/config/subscription.ts
export const SUBSCRIPTION_CONFIG = {
baseUrl: 'https://subb.ydns.eu',
supportUrl: 'https://subb.ydns.eu/',
logoUrl: 'https://raw.githubusercontent.com/arpicme/Proxy-App-Icon-set/refs/heads/main/white_background/Karing.svg',
announcement: 'Спасибо за подписку! Ваша безопасность - наш приоритет.',
};
```
### 2. Обновите CTA кнопки
```typescript
// В компонентах (HeroSection, PricingSection и т.д.)
import { SUBSCRIPTION_CONFIG } from '@/config/subscription';
<Button onClick={() => window.location.href = SUBSCRIPTION_CONFIG.baseUrl}>
Начать
</Button>
```
### 3. Добавьте Telegram виджет
```typescript
// src/components/TelegramWidget.tsx
export function TelegramWidget() {
return (
<a
href="https://t.me/your_support_bot"
className="fixed bottom-6 right-6 z-50 flex h-14 w-14 items-center justify-center rounded-full bg-gradient-to-br from-gekon-green to-gekon-cyan shadow-lg hover:scale-110 transition-transform"
>
<MessageCircle size={24} className="text-white" />
</a>
);
}
```
## 🌐 Локации серверов
### Северная Америка
- 🇺🇸 США (Лос-Анджелес, Нью-Йорк)
- 🇨🇦 Канада (Торонто)
### Южная Америка
- 🇧🇷 Бразилия (Сан-Паулу)
- 🇦🇷 Аргентина (Буэнос-Айрес)
### Европа
- 🇬🇧 Великобритания (Лондон)
- 🇩🇪 Германия (Франкфурт)
- 🇫🇷 Франция (Париж)
- 🇳🇱 Нидерланды (Амстердам)
- 🇸🇪 Швеция (Стокгольм)
- 🇵🇱 Польша (Варшава)
- 🇪🇸 Испания (Мадрид)
- 🇮🇹 Италия (Милан)
- 🇹🇷 Турция (Стамбул)
- 🇮🇱 Израиль (Тель-Авив)
### Азия
- 🇯🇵 Япония (Токио)
- 🇸🇬 Сингапур
- 🇭🇰 Гонконг
- 🇰🇷 Южная Корея (Сеул)
- 🇮🇳 Индия (Мумбаи)
- 🇦🇪 ОАЭ (Дубай)
- 🇹🇭 Таиланд (Бангкок)
- 🇻🇳 Вьетнам (Хошимин)
### Океания
- 🇦🇺 Австралия (Сидней)
- 🇳🇿 Новая Зеландия (Окленд)
### Африка
- 🇿🇦 ЮАР (Кейптаун)
## 🛠️ Технологии
- **React 19** + TypeScript
- **TanStack Router** - маршрутизация
- **Tailwind CSS 4.2** - стилизация
- **Radix UI** - UI компоненты
- **Lucide React** - иконки
- **Vite** - сборка
- **Canvas API** - анимация частиц
## 📦 Деплой
### Cloudflare Pages
```bash
npm run build
# Загрузите папку dist/ на Cloudflare Pages
```
### Vercel
```bash
vercel deploy
```
### Netlify
```bash
netlify deploy --prod
```
## 🎯 Следующие шаги
1. ✅ Добавлены анимированные частицы
2. ✅ Создана интерактивная карта серверов
3. ⏳ Добавить реальные ссылки на подписку
4. ⏳ Интегрировать Telegram бот
5. ⏳ Добавить аналитику (Google Analytics)
6. ⏳ Оптимизировать SEO
7. ⏳ Добавить Cookie Consent (GDPR)
## 📝 Дополнительно
### Производительность
- Частицы автоматически масштабируются под размер экрана
- Lazy loading для изображений
- Оптимизированная сборка с code splitting
### Доступность
- ARIA метки
- Навигация с клавиатуры
- Поддержка screen readers
### SEO
- Мета-теги для всех страниц
- Open Graph теги
- Семантическая разметка HTML
## 🐛 Решение проблем
### Частицы не отображаются
- Проверьте консоль браузера на ошибки
- Убедитесь, что Canvas поддерживается
- Попробуйте уменьшить количество частиц
### Карта не работает
- Проверьте z-index конфликты
- Убедитесь, что hover события не блокируются
- Протестируйте в разных браузерах
### Ошибки сборки
```bash
# Очистите кэш и переустановите
rm -rf node_modules package-lock.json
npm install
```
## 📞 Поддержка
Для вопросов и проблем:
- Проверьте документацию в `SETUP.md`
- Изучите исходный код компонентов
- Протестируйте в разных браузерах
---
**Версия:** 1.0.0
**Обновлено:** 23.01.2026
**Создано с помощью:** React + TypeScript + Tailwind CSS
+243 -243
View File
@@ -1,243 +1,243 @@
# Gekon Landing Page - Setup Guide
## 🚀 Quick Start
### Prerequisites
- Node.js 18+ or Bun runtime
- Git
### Installation
1. **Install dependencies:**
```bash
# Using npm
npm install
# Or using bun (faster)
bun install
```
2. **Run development server:**
```bash
# Using npm
npm run dev
# Or using bun
bun run dev
```
3. **Open in browser:**
```
http://localhost:5173
```
## 📦 Build for Production
```bash
# Build
npm run build
# Preview production build
npm run preview
```
## 🎨 New Features Added
### 1. Animated Particles Background
- **File:** `src/components/ParticlesBackground.tsx`
- **Features:**
- Canvas-based particle system
- Animated connections between particles
- Gekon brand colors (green/cyan)
- Performance optimized
- Responsive to screen size
### 2. Interactive Server Map
- **File:** `src/components/ServerMap.tsx`
- **Features:**
- 25+ server locations worldwide
- Hover tooltips with server details
- Real-time stats (users, latency)
- Animated pulse effects
- Connection lines between servers
- Status indicators (online/maintenance)
### Server Locations Included:
- **North America:** USA (West/East), Canada
- **South America:** Brazil, Argentina
- **Europe:** UK, Germany, France, Netherlands, Sweden, Poland, Spain, Italy, Turkey, Israel
- **Asia:** Japan, Singapore, Hong Kong, South Korea, India, UAE, Thailand, Vietnam
- **Oceania:** Australia, New Zealand
- **Africa:** South Africa
## 🌍 Internationalization
The site supports 3 languages:
- **English** (en)
- **Russian** (ru) - Русский
- **Chinese** (zh) - 中文
Language switcher is in the navbar (top-right).
## 🎨 Customization
### Update Server Locations
Edit `src/components/ServerMap.tsx`:
```typescript
const servers: ServerLocation[] = [
{
id: "your-server",
country: "Country",
city: "City",
x: 50, // % from left
y: 50, // % from top
users: 1000,
latency: 10,
status: "online"
},
// ... more servers
];
```
### Adjust Particle Count
Edit `src/components/ParticlesBackground.tsx`:
```typescript
// Line ~35
const particleCount = Math.min(100, Math.floor((canvas.width * canvas.height) / 15000));
// Increase divisor (15000) for fewer particles
// Decrease for more particles
```
### Change Colors
Edit `src/styles.css`:
```css
--gekon-green: oklch(0.72 0.19 160);
--gekon-cyan: oklch(0.7 0.14 200);
--gekon-purple: oklch(0.6 0.25 300);
--gekon-neon: oklch(0.75 0.2 150);
```
## 📁 Project Structure
```
gekon-speed-boost-3ba17197-main/
├── src/
│ ├── components/
│ │ ├── ParticlesBackground.tsx ← NEW: Animated particles
│ │ ├── ServerMap.tsx ← NEW: Interactive map
│ │ ├── HeroSection.tsx
│ │ ├── Navbar.tsx
│ │ ├── FeaturesSection.tsx
│ │ ├── PricingSection.tsx
│ │ └── ... other components
│ ├── i18n/
│ │ ├── translations.ts ← Updated with new keys
│ │ └── context.tsx
│ ├── routes/
│ │ └── index.tsx ← Updated with new components
│ └── styles.css
├── package.json
└── vite.config.ts
```
## 🔧 Tech Stack
- **Framework:** React 19 + TypeScript
- **Router:** TanStack Router
- **Styling:** Tailwind CSS 4.2
- **UI Components:** Radix UI
- **Icons:** Lucide React
- **Build Tool:** Vite
- **Runtime:** Node.js / Bun
## 🚀 Deployment
### Cloudflare Pages (Recommended)
```bash
npm run build
# Upload dist/ folder to Cloudflare Pages
```
### Vercel
```bash
vercel deploy
```
### Netlify
```bash
netlify deploy --prod
```
## 📊 Performance Tips
1. **Reduce particles on mobile:**
- Particles automatically scale based on screen size
- Adjust in `ParticlesBackground.tsx`
2. **Lazy load images:**
- Add `loading="lazy"` to img tags
3. **Optimize bundle:**
- Run `npm run build` to see bundle analysis
- Consider code splitting for large components
## 🐛 Troubleshooting
### Particles not showing
- Check browser console for errors
- Ensure canvas is supported
- Try reducing particle count
### Map tooltips not working
- Check z-index conflicts
- Ensure hover events are not blocked
- Test on different browsers
### Build errors
```bash
# Clear cache and reinstall
rm -rf node_modules package-lock.json
npm install
```
## 📝 Next Steps
1. **Add real subscription links** from your Sub-Bridge config
2. **Integrate Telegram bot** for support
3. **Add analytics** (Google Analytics, Plausible)
4. **Optimize SEO** with meta tags
5. **Add cookie consent** for GDPR
## 🎯 Integration with Sub-Bridge
To connect with your real VPN service:
1. **Update subscription URL:**
```typescript
// src/config/subscription.ts (create this file)
export const SUBSCRIPTION_CONFIG = {
baseUrl: 'https://subb.ydns.eu',
supportUrl: 'https://subb.ydns.eu/',
};
```
2. **Update CTA buttons:**
```typescript
// In components (HeroSection, PricingSection, etc.)
<Button onClick={() => window.location.href = SUBSCRIPTION_CONFIG.baseUrl}>
Get Started
</Button>
```
## 📞 Support
For issues or questions:
- Check documentation in `/docs`
- Review component source code
- Test in different browsers
---
**Version:** 1.0.0
**Last Updated:** 2026-01-23
**Built with:** React + TypeScript + Tailwind CSS
# Gekon Landing Page - Setup Guide
## 🚀 Quick Start
### Prerequisites
- Node.js 18+ or Bun runtime
- Git
### Installation
1. **Install dependencies:**
```bash
# Using npm
npm install
# Or using bun (faster)
bun install
```
2. **Run development server:**
```bash
# Using npm
npm run dev
# Or using bun
bun run dev
```
3. **Open in browser:**
```
http://localhost:5173
```
## 📦 Build for Production
```bash
# Build
npm run build
# Preview production build
npm run preview
```
## 🎨 New Features Added
### 1. Animated Particles Background
- **File:** `src/components/ParticlesBackground.tsx`
- **Features:**
- Canvas-based particle system
- Animated connections between particles
- Gekon brand colors (green/cyan)
- Performance optimized
- Responsive to screen size
### 2. Interactive Server Map
- **File:** `src/components/ServerMap.tsx`
- **Features:**
- 25+ server locations worldwide
- Hover tooltips with server details
- Real-time stats (users, latency)
- Animated pulse effects
- Connection lines between servers
- Status indicators (online/maintenance)
### Server Locations Included:
- **North America:** USA (West/East), Canada
- **South America:** Brazil, Argentina
- **Europe:** UK, Germany, France, Netherlands, Sweden, Poland, Spain, Italy, Turkey, Israel
- **Asia:** Japan, Singapore, Hong Kong, South Korea, India, UAE, Thailand, Vietnam
- **Oceania:** Australia, New Zealand
- **Africa:** South Africa
## 🌍 Internationalization
The site supports 3 languages:
- **English** (en)
- **Russian** (ru) - Русский
- **Chinese** (zh) - 中文
Language switcher is in the navbar (top-right).
## 🎨 Customization
### Update Server Locations
Edit `src/components/ServerMap.tsx`:
```typescript
const servers: ServerLocation[] = [
{
id: "your-server",
country: "Country",
city: "City",
x: 50, // % from left
y: 50, // % from top
users: 1000,
latency: 10,
status: "online"
},
// ... more servers
];
```
### Adjust Particle Count
Edit `src/components/ParticlesBackground.tsx`:
```typescript
// Line ~35
const particleCount = Math.min(100, Math.floor((canvas.width * canvas.height) / 15000));
// Increase divisor (15000) for fewer particles
// Decrease for more particles
```
### Change Colors
Edit `src/styles.css`:
```css
--gekon-green: oklch(0.72 0.19 160);
--gekon-cyan: oklch(0.7 0.14 200);
--gekon-purple: oklch(0.6 0.25 300);
--gekon-neon: oklch(0.75 0.2 150);
```
## 📁 Project Structure
```
gekon-speed-boost-3ba17197-main/
├── src/
│ ├── components/
│ │ ├── ParticlesBackground.tsx ← NEW: Animated particles
│ │ ├── ServerMap.tsx ← NEW: Interactive map
│ │ ├── HeroSection.tsx
│ │ ├── Navbar.tsx
│ │ ├── FeaturesSection.tsx
│ │ ├── PricingSection.tsx
│ │ └── ... other components
│ ├── i18n/
│ │ ├── translations.ts ← Updated with new keys
│ │ └── context.tsx
│ ├── routes/
│ │ └── index.tsx ← Updated with new components
│ └── styles.css
├── package.json
└── vite.config.ts
```
## 🔧 Tech Stack
- **Framework:** React 19 + TypeScript
- **Router:** TanStack Router
- **Styling:** Tailwind CSS 4.2
- **UI Components:** Radix UI
- **Icons:** Lucide React
- **Build Tool:** Vite
- **Runtime:** Node.js / Bun
## 🚀 Deployment
### Cloudflare Pages (Recommended)
```bash
npm run build
# Upload dist/ folder to Cloudflare Pages
```
### Vercel
```bash
vercel deploy
```
### Netlify
```bash
netlify deploy --prod
```
## 📊 Performance Tips
1. **Reduce particles on mobile:**
- Particles automatically scale based on screen size
- Adjust in `ParticlesBackground.tsx`
2. **Lazy load images:**
- Add `loading="lazy"` to img tags
3. **Optimize bundle:**
- Run `npm run build` to see bundle analysis
- Consider code splitting for large components
## 🐛 Troubleshooting
### Particles not showing
- Check browser console for errors
- Ensure canvas is supported
- Try reducing particle count
### Map tooltips not working
- Check z-index conflicts
- Ensure hover events are not blocked
- Test on different browsers
### Build errors
```bash
# Clear cache and reinstall
rm -rf node_modules package-lock.json
npm install
```
## 📝 Next Steps
1. **Add real subscription links** from your Sub-Bridge config
2. **Integrate Telegram bot** for support
3. **Add analytics** (Google Analytics, Plausible)
4. **Optimize SEO** with meta tags
5. **Add cookie consent** for GDPR
## 🎯 Integration with Sub-Bridge
To connect with your real VPN service:
1. **Update subscription URL:**
```typescript
// src/config/subscription.ts (create this file)
export const SUBSCRIPTION_CONFIG = {
baseUrl: 'https://subb.ydns.eu',
supportUrl: 'https://subb.ydns.eu/',
};
```
2. **Update CTA buttons:**
```typescript
// In components (HeroSection, PricingSection, etc.)
<Button onClick={() => window.location.href = SUBSCRIPTION_CONFIG.baseUrl}>
Get Started
</Button>
```
## 📞 Support
For issues or questions:
- Check documentation in `/docs`
- Review component source code
- Test in different browsers
---
**Version:** 1.0.0
**Last Updated:** 2026-01-23
**Built with:** React + TypeScript + Tailwind CSS
+13 -13
View File
@@ -1,13 +1,13 @@
version: '3.8'
services:
gekon-web:
build: .
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run dev -- --host 0.0.0.0 --port 5173
version: '3.8'
services:
gekon-web:
build: .
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run dev -- --host 0.0.0.0 --port 5173
+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">
+113 -70
View File
@@ -1,88 +1,131 @@
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="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">
{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">
{t("hero_subtitle")}
</p>
<div className="animate-fade-up-delay-3 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<Button variant="glow" size="xl">
{t("hero_cta_primary")}
<ArrowRight size={18} />
</Button>
<Button variant="glow-outline" size="xl">
{t("hero_cta_secondary")}
</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="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>
<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 />
<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-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 lg:justify-start">
<Button variant="glow" size="xl">
{t("hero_cta_primary")}
<ArrowRight size={18} />
</Button>
<Button variant="glow-outline" size="xl">
{t("hero_cta_secondary")}
</Button>
</div>
<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>
</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>
+105 -105
View File
@@ -1,105 +1,105 @@
import { useEffect, useRef } from "react";
interface Particle {
x: number;
y: number;
vx: number;
vy: number;
size: number;
opacity: number;
}
export function ParticlesBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const particlesRef = useRef<Particle[]>([]);
const animationFrameRef = useRef<number>();
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Set canvas size
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
// Initialize particles
const particleCount = Math.min(150, Math.floor((canvas.width * canvas.height) / 12000));
particlesRef.current = Array.from({ length: particleCount }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.8,
vy: (Math.random() - 0.5) * 0.8,
size: Math.random() * 3 + 1.5,
opacity: Math.random() * 0.6 + 0.3,
}));
// Animation loop
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw particles
particlesRef.current.forEach((particle) => {
// Update position
particle.x += particle.vx;
particle.y += particle.vy;
// Wrap around edges
if (particle.x < 0) particle.x = canvas.width;
if (particle.x > canvas.width) particle.x = 0;
if (particle.y < 0) particle.y = canvas.height;
if (particle.y > canvas.height) particle.y = 0;
// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(16, 185, 129, ${particle.opacity})`; // gekon-green
ctx.fill();
});
// Draw connections
particlesRef.current.forEach((p1, i) => {
particlesRef.current.slice(i + 1).forEach((p2) => {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
const opacity = (1 - distance / 150) * 0.2;
ctx.strokeStyle = `rgba(6, 182, 212, ${opacity})`; // gekon-cyan
ctx.lineWidth = 0.5;
ctx.stroke();
}
});
});
animationFrameRef.current = requestAnimationFrame(animate);
};
animate();
return () => {
window.removeEventListener("resize", resizeCanvas);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
return (
<canvas
ref={canvasRef}
className="pointer-events-none absolute inset-0 opacity-60"
style={{ mixBlendMode: "screen", zIndex: 1 }}
/>
);
}
import { useEffect, useRef } from "react";
interface Particle {
x: number;
y: number;
vx: number;
vy: number;
size: number;
opacity: number;
}
export function ParticlesBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const particlesRef = useRef<Particle[]>([]);
const animationFrameRef = useRef<number>();
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Set canvas size
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
// Initialize particles
const particleCount = Math.min(150, Math.floor((canvas.width * canvas.height) / 12000));
particlesRef.current = Array.from({ length: particleCount }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.8,
vy: (Math.random() - 0.5) * 0.8,
size: Math.random() * 3 + 1.5,
opacity: Math.random() * 0.6 + 0.3,
}));
// Animation loop
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw particles
particlesRef.current.forEach((particle) => {
// Update position
particle.x += particle.vx;
particle.y += particle.vy;
// Wrap around edges
if (particle.x < 0) particle.x = canvas.width;
if (particle.x > canvas.width) particle.x = 0;
if (particle.y < 0) particle.y = canvas.height;
if (particle.y > canvas.height) particle.y = 0;
// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(16, 185, 129, ${particle.opacity})`; // gekon-green
ctx.fill();
});
// Draw connections
particlesRef.current.forEach((p1, i) => {
particlesRef.current.slice(i + 1).forEach((p2) => {
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
const opacity = (1 - distance / 150) * 0.2;
ctx.strokeStyle = `rgba(6, 182, 212, ${opacity})`; // gekon-cyan
ctx.lineWidth = 0.5;
ctx.stroke();
}
});
});
animationFrameRef.current = requestAnimationFrame(animate);
};
animate();
return () => {
window.removeEventListener("resize", resizeCanvas);
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
return (
<canvas
ref={canvasRef}
className="pointer-events-none absolute inset-0 opacity-60"
style={{ mixBlendMode: "screen", zIndex: 1 }}
/>
);
}
+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>
<div className="ml-4 rounded-full bg-gekon-green/10 px-3 py-1 text-sm font-bold text-gekon-green">
<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"}`}>
+263 -223
View File
@@ -1,223 +1,263 @@
import { useState } from "react";
import { useScrollAnimation } from "@/hooks/useScrollAnimation";
import { useI18n } from "@/i18n/context";
import { Server, Zap, Users } from "lucide-react";
interface ServerLocation {
id: string;
country: string;
city: string;
x: number; // percentage from left
y: number; // percentage from top
users: number;
latency: number;
status: "online" | "maintenance";
}
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" },
// 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" },
// 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" },
// 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" },
// Africa
{ id: "south-africa", country: "South Africa", city: "Cape Town", x: 52, y: 72, 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" },
];
export function ServerMap() {
const [hoveredServer, setHoveredServer] = useState<ServerLocation | null>(null);
const { ref, isVisible } = useScrollAnimation();
const { t } = useI18n();
const totalUsers = servers.reduce((sum, s) => sum + s.users, 0);
const avgLatency = Math.round(servers.reduce((sum, s) => sum + s.latency, 0) / servers.length);
return (
<section id="technology" className="relative px-4 py-24 sm:py-32 overflow-hidden">
<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"}`}>
{t("tech_title_1")} <span className="gradient-text">{t("tech_title_2")}</span>
</h2>
<p className={`mx-auto max-w-2xl text-lg text-muted-foreground ${isVisible ? "animate-fade-up-delay-1" : "opacity-0"}`}>
{t("tech_subtitle")}
</p>
</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>
<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>
<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>
</div>
{/* Interactive Map */}
<div className={`glass-card relative overflow-hidden rounded-2xl p-8 ${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"
>
{/* 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"
/>
<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>
{/* 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];
return (
<line
key={`line-${server.id}`}
x1={prev.x}
y1={prev.y}
x2={server.x}
y2={server.y}
stroke="url(#gradient)"
strokeWidth="0.1"
opacity="0.2"
className="animate-pulse"
style={{ animationDelay: `${i * 0.1}s` }}
/>
);
})}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="rgb(16, 185, 129)" />
<stop offset="100%" stopColor="rgb(6, 182, 212)" />
</linearGradient>
</defs>
</svg>
{/* Server nodes */}
{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"
style={{
left: `${server.x}%`,
top: `${server.y}%`,
animationDelay: `${i * 0.05}s`,
}}
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 ${
server.status === "online"
? "bg-gekon-green shadow-lg shadow-gekon-green/50"
: "bg-yellow-500 shadow-lg shadow-yellow-500/50"
}`}
/>
{/* 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 className="mt-1 flex items-center gap-3 text-muted-foreground">
<span className="flex items-center gap-1">
<Users size={10} />
{server.users}
</span>
<span className="flex items-center gap-1">
<Zap size={10} />
{server.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="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>
</div>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-yellow-500 shadow-lg shadow-yellow-500/50" />
<span>Maintenance</span>
</div>
<div className="flex items-center gap-2">
<div className="h-px w-8 bg-gradient-to-r from-gekon-green to-gekon-cyan" />
<span>Network Connection</span>
</div>
</div>
</div>
{/* Hover instruction */}
<p className="mt-4 text-center text-sm text-muted-foreground">
Hover over nodes to see server details
</p>
</div>
</section>
);
}
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;
country: string;
city: string;
x: number; // percentage from left
y: number; // percentage from top
users: number;
latency: number;
status: "online" | "maintenance";
}
const servers: ServerLocation[] = [
// North America
{ 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: 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: 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: 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: 51, y: 74, users: 380, latency: 48, status: "online" },
// Middle East
{ 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() {
const [hoveredServer, setHoveredServer] = useState<ServerLocation | null>(null);
const { ref, isVisible } = useScrollAnimation();
const { t } = useI18n();
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 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-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>
<p className={`mx-auto max-w-2xl text-lg text-muted-foreground ${isVisible ? "animate-fade-up-delay-1" : "opacity-0"}`}>
{t("tech_subtitle")}
</p>
</div>
{/* Stats */}
<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-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-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={`relative ${isVisible ? "animate-fade-up-delay-3" : "opacity-0"}`}>
{/* World Map SVG Background */}
<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%)",
}}
>
<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}
/>
<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">
{networkLinks.map(([fromId, toId], i) => {
const from = serverById.get(fromId);
const to = serverById.get(toId);
if (!from || !to) return null;
return (
<line
key={`line-${fromId}-${toId}`}
x1={from.x}
y1={from.y}
x2={to.x}
y2={to.y}
stroke="url(#gradient)"
strokeWidth="0.14"
opacity="0.3"
className="animate-pulse"
style={{ animationDelay: `${i * 0.1}s` }}
/>
);
})}
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="rgb(16, 185, 129)" />
<stop offset="100%" stopColor="rgb(6, 182, 212)" />
</linearGradient>
</defs>
</svg>
{/* Server nodes */}
{servers.map((server, i) => (
<div
key={server.id}
className="absolute -translate-x-1/2 -translate-y-1/2 pointer-events-none"
style={{
left: `${server.x}%`,
top: `${server.y}%`,
}}
>
{/* 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)}
>
<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>
</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} />
{hoveredServer.users}
</span>
<span className="flex items-center gap-1">
<Zap size={10} />
{hoveredServer.latency}ms
</span>
</div>
</div>
)}
</div>
{/* Legend */}
<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>
</div>
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-yellow-500 shadow-lg shadow-yellow-500/50" />
<span>Maintenance</span>
</div>
<div className="flex items-center gap-2">
<div className="h-px w-8 bg-gradient-to-r from-gekon-green to-gekon-cyan" />
<span>Network Connection</span>
</div>
</div>
</div>
{/* Hover instruction */}
<p className="mt-4 text-center text-sm text-muted-foreground">
Hover over nodes to see server details
</p>
</div>
</section>
);
}
@@ -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

+9 -9
View File
@@ -1,9 +1,9 @@
// @lovable.dev/vite-tanstack-config already includes the following — do NOT add them manually
// or the app will break with duplicate plugins:
// - tanstackStart, viteReact, tailwindcss, tsConfigPaths, cloudflare (build-only),
// componentTagger (dev-only), VITE_* env injection, @ path alias, React/TanStack dedupe,
// error logger plugins, and sandbox detection (port/host/strictPort).
// You can pass additional config via defineConfig({ vite: { ... } }) if needed.
import { defineConfig } from "@lovable.dev/vite-tanstack-config";
export default defineConfig();
// @lovable.dev/vite-tanstack-config already includes the following — do NOT add them manually
// or the app will break with duplicate plugins:
// - tanstackStart, viteReact, tailwindcss, tsConfigPaths, cloudflare (build-only),
// componentTagger (dev-only), VITE_* env injection, @ path alias, React/TanStack dedupe,
// error logger plugins, and sandbox detection (port/host/strictPort).
// You can pass additional config via defineConfig({ vite: { ... } }) if needed.
import { defineConfig } from "@lovable.dev/vite-tanstack-config";
export default defineConfig();