feat: redesign landing sections and interactions

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

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

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

After

Width:  |  Height:  |  Size: 1.2 MiB

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