# Netrunner VPN — Landing

Промо-сайт и мини-платформа VPN-сервиса «Netrunner»: Next.js 16 (App Router) фронтенд
с двумя языками (en/ru), PocketBase в роли headless CMS и два самописных Rust-сервиса —
WebAssembly-движок анимированного фона «матрицы» и WebSocket/HTTP-шлюз с AI-модерацией
и speedtest-эндпоинтами.

## Стек

- **Next.js 16** (App Router, React 19, Server Components) + **TailwindCSS 4**
- **PocketBase** — headless CMS/БД для блога, FAQ, ссылок на загрузки и гостевой книги
- **Rust → WebAssembly** (`matrix-engine/`) — физика фоновой анимации, рендер через WebGL2 в Web Worker
- **Rust / Axum** (`proxy-ws/`) — прокси-шлюз: ONNX-модель токсичности для фильтрации сообщений + генератор нагрузки для speedtest
- **Docker Compose** — три контейнера в проде: `landing`, `netrunner-cms` (PocketBase), `ws-proxy`

## Структура репозитория

```
app/[lang]/          Страницы Next.js (App Router), маршруты локализованы через /en/* и /ru/*
components/sections/ Крупные блоки страниц (Hero, Pricing, Terminal-гостевая книга и т.д.)
components/ui/       低-уровневые UI-примитивы (shadcn/radix обвязка)
lib/                 Общие утилиты: клиент PocketBase, загрузчик словарей, типы
dictionaries/        JSON со строками локализации (en.json / ru.json)
matrix-engine/        Rust/WASM-движок фонового "дождя из матрицы" + его README
proxy-ws/              Rust/Axum-шлюз: AI-модерация комментариев и speedtest + его README
public/wasm-matrix/   Скомпилированный вывод matrix-engine, который реально грузит браузер
public/games/doom.zip  Пасхалка — DOOM в браузере на /doom
proxy.ts               Next.js "proxy" (аналог middleware) — определение и редирект локали
```

Подробности по Rust-подсистемам — в их собственных README:
[matrix-engine/README.md](matrix-engine/README.md) и [proxy-ws/README.md](proxy-ws/README.md).
Подробности по фронтенду — в [app/README.md](app/README.md), [components/README.md](components/README.md)
и [lib/README.md](lib/README.md).

## Как это работает вместе

1. Next.js middleware (`proxy.ts`) на каждый запрос без `/en` или `/ru` в пути определяет
   язык браузера (`Negotiator` + `@formatjs/intl-localematcher`) и редиректит на `/{locale}/...`.
   `/doom`, `/doom.html` и `/games/*` из этой логики специально исключены.
2. Серверные страницы в `app/[lang]/...` подгружают словарь (`lib/dictionaries.ts`) и контент
   из PocketBase (`lib/pb.ts`) — блог, FAQ, ссылки на загрузки и т.п.
3. Фоновая WebGL-анимация (`matrix-engine`) инициализируется клиентским компонентом
   `NetrunnerMatrix` — она работает в отдельном Web Worker поверх `OffscreenCanvas`,
   не блокируя основной поток.
4. Гостевая книга (`components/sections/TerminalGuestbook.tsx`) отправляет сообщения не
   напрямую в PocketBase, а через `/api/proxy/*` (rewrite в `next.config.ts` на `ws-proxy:3001`).
   Rust-шлюз прогоняет никнейм и текст через ONNX-классификатор токсичности и только потом
   пишет запись в PocketBase.
5. Спидтест (`components/sections/SpeedtestClient.tsx`, страница `/speedtest`) бьёт напрямую
   в `ws-proxy` за `/api/speedtest/ping|download|upload`.

## Разработка

```bash
pnpm install

# WASM-движок собирается отдельно (нужен Rust + wasm-pack), результат кладётся в public/wasm-matrix
./build-wasm.sh

pnpm dev
```

Открыть [http://localhost:3000](http://localhost:3000) — редиректнёт на `/en` или `/ru` по языку браузера.

Полезные переменные окружения (`.env.local`):

| Переменная | Назначение |
|---|---|
| `NEXT_PUBLIC_PB_URL` | Публичный URL PocketBase (используется в браузере) |
| `PB_INTERNAL_URL` | Внутренний URL PocketBase для SSR (обычно `http://netrunner-cms:8090` в докере) |
| `NEXT_PUBLIC_PROXY_URL` | Публичный URL Rust-шлюза (для клиентских fetch, минуя rewrite) |
| `NEXT_PUBLIC_API_URL` | База API бэкенда (`netrunner-backend`) для auth-эндпоинтов — `/users/me`, `/auth/telegram`. По умолчанию `${NEXT_PUBLIC_ACCOUNT_ORIGIN}/api/v1` |
| `NEXT_PUBLIC_ACCOUNT_ORIGIN` | Origin ЛК (`netrunner-backend`, сабдомен `account.*`) — куда редиректить после успешного логина |
| `NEXT_PUBLIC_BOT_USERNAME` | Юзернейм Telegram-бота для виджета логина |
| `PB_ADMIN_EMAIL` / `PB_ADMIN_PASS` | Только для одноразового скрипта `upload.mjs` (сид данных в PocketBase) |

## Сборка и деплой

- `Dockerfile` — многостадийная сборка: сначала компилирует `matrix-engine` в WASM
  (`wasm-pack build --target web --release`), кладёт результат в `public/wasm-matrix`,
  затем собирает Next.js и упаковывает в минимальный `node:20-alpine` рантайм.
- `proxy-ws/Dockerfile` — отдельный образ Rust-шлюза (кэширует зависимости через
  BuildKit cache mounts, чтобы не пересобирать crates на каждый билд).
- `docker-compose.yml` — прод: поднимает три сервиса: `landing`, `netrunner-cms`
  (PocketBase) и `ws-proxy`, все в одной bridge-сети `netrunner-network`.
- `docker-compose.dev-remote.yml` — dev: тот же набор сервисов на отдельном dev VPS
  (по аналогии с `netrunner-backend`, тот же хост — `45.76.161.137` / DNS
  `dev.netrunner-vpn.com`), сам лендинг без reverse-proxy (порт `3000` открыт
  напрямую, обычный HTTP), но смотрит на dev-бэкенд уже через Caddy на
  `https://dev.netrunner-vpn.com` (порт 443, не голый `:8080`) — у
  `netrunner-backend` там настоящий TLS, нужный и для Telegram Web Apps (кнопки
  бота) и Secure-cookie, и для Telegram Login Widget (требует в браузере именно
  порт 443).
  Домен, а не IP: Telegram Login Widget требует настоящий домен, зарегистрированный
  в BotFather под отдельным dev-ботом (`BOT_USERNAME` для этого стека —
  обязательная переменная, без дефолта на прод-бота, см. комментарии в файле).
- `.github/workflows/build.yml` — на пуш в `main` (и вручную) собирает и пушит оба
  образа (`netrunner-landing`, `netrunner-landing-proxy`) в GHCR.
- `.github/workflows/deploy.yml` — две джобы: `deploy-dev` срабатывает автоматически
  сразу после `build.yml` и обновляет dev VPS (`docker-compose.dev-remote.yml`);
  `deploy-prod` — только вручную (Actions → Run workflow), обновляет прод
  (`docker-compose.yml`). Секреты: `VPS_IP`/`SSH_PRIVATE_KEY` (прод),
  `DEV_VPS_IP`/`DEV_SSH_PRIVATE_KEY` (dev) — заводятся в Settings → Secrets репозитория.
- `deploy.sh` — тот же процесс вручную для прода, для локального запуска без ожидания CI.
- `ecosystem.config.js` — конфиг PM2, альтернативный способ запускать `next start`
  без Docker (используется, если сервис поднимается процесс-менеджером на самом VPS).

Разовые dev-утилиты **не являются частью приложения** и не запускаются в проде:
- `collect_code.js` — сбрасывает все `.ts/.tsx` файлы из `app/`, `components/`, `lib/`
  в один текстовый файл (удобно для передачи контекста в LLM).
- `upload.mjs` — одноразовый скрипт сидирования коллекций `downloads_en`/`downloads_ru`
  в PocketBase тестовыми данными.
