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:
+9
-9
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Generated
+10031
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 |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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
@@ -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: "Wi‑Fi", 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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: "Продукт",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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);
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.2 MiB |
+9
-9
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user