From db856390867fc008431ba1a0af4dfcf90475b63b Mon Sep 17 00:00:00 2001 From: Ivan Vasilev Date: Thu, 3 Sep 2026 03:59:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B3=D0=BD=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20Prettier:=20=D1=87=D0=B8=D0=BD=D0=B8=D1=82=20format:ch?= =?UTF-8?q?eck=20=D0=B2=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 149 ++-------------------------------------- design.md | 199 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 118 insertions(+), 230 deletions(-) diff --git a/README.md b/README.md index d1d4fd4..db36f6c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RKNboost -[github.com/maeneko/rknboost](https://github.com/maeneko/rknboost) +очередная обертка под zapret ## Требования @@ -24,150 +24,9 @@ npm run build:win # NSIS .exe (x64; arm64 не собираем — не npm run build:linux # AppImage + .deb ``` -> Установщики под чужую ОС локально собрать нельзя — для этого есть CI -> (`.github/workflows/ci.yml` собирает все три платформы и кладёт артефакты в Actions). +## Ссылки -> Если после `npm install` команда `npm run dev` падает с `Error: Electron uninstall` — -> бинарник Electron не докачался; исправляется повторным запуском -> `node node_modules/electron/install.js`. - -## Структура - -``` -src/ - main/ # Node-процесс: окна, меню, доступ к ОС - index.ts lifecycle приложения, single instance - window.ts createWindow / createPopupWindow, настройки безопасности - ipc.ts все ipcMain.handle - preload/ # мост между main и renderer (sandbox, contextBridge) - shared/ # ipc-contract.ts — типы каналов, общие для всех процессов - renderer/ # React-приложение (обычный браузерный код, без Node API) -build/ # иконки и entitlements для electron-builder -resources/ # файлы, попадающие в собранное приложение (иконка окна) -out/ # результат electron-vite build -release/ # готовые установщики -``` - -## Как добавить свой IPC-канал - -1. Опишите канал в `src/shared/ipc-contract.ts` (аргументы и результат). -2. Зарегистрируйте обработчик в `src/main/ipc.ts` через хелпер `handle(...)` — типы проверятся. -3. Добавьте метод в объект `api` в `src/preload/index.ts`. -4. Вызывайте из React как `window.api.мойМетод()` — тип подтянется автоматически. - -События из main в renderer описываются в `IpcEvents` и подписываются через `subscribe(...)` -(пример — `onThemeChanged`, возвращает функцию отписки для `useEffect`). - -## Что показывают демо-карточки - -| Карточка | Каналы | Что демонстрирует | -| ---------------- | ----------------------------------------- | ------------------------------------------------------------- | -| Система | `app:info` | версии Electron/Chromium/Node, платформа, путь к userData | -| IPC ping | `ping` | минимальный invoke/handle | -| Нативный диалог | `dialog:open-file` | системный выбор файла + `fs.stat` в main | -| Всплывающее окно | `dialog:message-box`, `window:open-popup` | модальный диалог ОС и второе `BrowserWindow` | -| Тема оформления | `theme:*` | `nativeTheme` как источник правды, реакция на смену темы в ОС | - -Тайтлбар системный на всех платформах — окно создаётся без `frame`/`titleBarStyle`, рамку -и кнопки свернуть/развернуть/закрыть рисует сама ОС. - -## Безопасность - -- `sandbox: true`, `contextIsolation: true`, `nodeIntegration: false`; -- renderer не имеет доступа к `ipcRenderer` — только к методам из `window.api`; -- `setWindowOpenHandler` и `will-navigate` не дают уйти на внешний origin, ссылки открываются - в системном браузере, причём только с протоколами из `ALLOWED_EXTERNAL_PROTOCOLS`; -- в `index.html` задан CSP без внешних источников. - -## Иконки - -`build/icon.png` (1024×1024) — исходник, из него electron-builder делает иконки для всех платформ; -`build/icon.icns` и `build/icon.ico` лежат рядом как готовые варианты. `resources/icon.png` — -иконка окна для Linux. Замените эти файлы своими, имена оставьте прежними. - -## Подпись и нотаризация - -По умолчанию выключены (`notarize: false`), сборки неподписанные — их можно запускать локально, -но macOS покажет предупреждение Gatekeeper при переносе на другую машину. - -Чтобы включить: - -- **macOS**: переменные окружения `CSC_LINK` (base64 сертификата Developer ID) и `CSC_KEY_PASSWORD`, - плюс `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` и `notarize: true` - в `electron-builder.yml`. -- **Windows**: `CSC_LINK` и `CSC_KEY_PASSWORD` с .pfx-сертификатом. - -В CI эти значения передаются секретами GitHub — место уже отмечено комментарием в -`.github/workflows/ci.yml`. - -## Обход блокировок (zapret) - -Движки и данные — из двух сторонних проектов, оба MIT, оба скачиваются `npm run zapret:fetch` -(вызывается автоматически из `npm run build`) в `resources/zapret/` (в git не хранится): - -- **[bol-van/zapret](https://github.com/bol-van/zapret)** — сами движки: `tpws` (macOS, SOCKS5-прокси - без root) и `winws.exe` + драйвер **WinDivert** (Windows, служба с правами администратора). - WinDivert — LGPLv3/GPLv2 на выбор; лицензии обоих проектов кладутся рядом с бинарниками - (`LICENSE.zapret.txt`, `LICENSE.WinDivert.txt`). +- **[bol-van/zapret](https://github.com/bol-van/zapret)** — сам движок zapret, спасибо за столь хороший инструмент - **[Flowseal/zapret-discord-youtube](https://github.com/Flowseal/zapret-discord-youtube)** — - списки сайтов (`resources/zapret/lists/`) и fake-пакеты (`resources/zapret/fakes/`). Версия - зафиксирована по sha коммита в `scripts/fetch-zapret.mjs` (`FLOWSEAL_COMMIT`), лицензия — + списки сайтов (`resources/zapret/lists/`) и fake-пакеты (`resources/zapret/fakes/`). (`FLOWSEAL_COMMIT`), лицензия — `LICENSE.flowseal.txt`. - -Свои сгенерированные стратегии для Windows (`src/main/zapret/strategies.win32.generated.ts`, -22 варианта) — не батники: `scripts/import-flowseal-strategies.mjs` разбирает `general*.bat` -из того же репозитория Flowseal и переносит аргументы `winws` в TypeScript. Файл коммитится; -перегенерировать и свериться через `git diff` нужно вручную при обновлении `FLOWSEAL_COMMIT`. -На macOS `tpws` не умеет ни fake-пакетов, ни UDP — там свои профили -(`src/main/zapret/strategies.ts`, раздел `DARWIN_DEFINITIONS`) поверх тех же списков. - -Списки сайтов пользователь может редактировать полностью (включая встроенные) — рабочая копия -живёт в `userData/zapret-lists` и переживает обновление приложения; `tpws`/`winws` сами -перечитывают список при изменении файла, перезапуск обхода не нужен (`src/main/zapret/lists.ts`). - -### Windows: служба и права - -`winws` требует драйвер WinDivert и права администратора, поэтому на Windows он работает не как -обычный дочерний процесс, а как служба Windows `rknboost-zapret` (`src/main/zapret/service.win32.ts`). -Устанавливает и настраивает её само приложение — не инсталлятор, — потому что аргументы `winws` -зависят от выбранной пользователем стратегии. - -- **Приложение запрашивает права администратора при каждом запуске** - (`win.requestedExecutionLevel: requireAdministrator` в `electron-builder.yml`). Поэтому все - действия со службой выполняются напрямую, без отдельных запросов UAC по ходу работы. - Если приложение почему-то запущено без прав (dev-режим — там манифеста нет), включается - запасной путь: `outer.ps1` через `Start-Process -Verb RunAs` поднимает само приложение - с флагом `--elevated-service-task`, и UAC запрашивается на него, а не на `powershell.exe` — - в окне UAC пользователь должен видеть имя приложения, а не постороннюю программу. - При установке службы обычным пользователям выдаётся право на `start`/`stop` именно этой - службы (`sc sdset`), так что и без прав включение-выключение обхода работает. -- **Автозапуск с системой** включён по умолчанию (`sc config start= auto`) — обход работает и - после перезагрузки, не дожидаясь запуска приложения. Отключается переключателем в настройках - (тоже через UAC). Выключение обхода кнопкой на главном экране действует только до следующей - перезагрузки, если автозапуск остался включён — служба поднимется вместе с системой заново. -- **arm64 не поддерживается**: у WinDivert нет подписанной сборки под arm64 (только x64 через - эмуляцию на Windows 11), поэтому `electron-builder.yml` собирает Windows-инсталлятор только - для x64. -- Установщик — **per-user** (ставится в `%LocalAppData%`, один клик, без UAC на саму установку). - - > ⚠️ Известный компромисс. Каталог установки доступен на запись обычному пользователю, а exe - > при этом требует администратора — значит подмена `RKNboost.exe` даёт злоумышленнику права - > администратора с подтверждения самого пользователя. Это осознанный выбор владельца проекта - > в пользу быстрой установки без UAC. Безопасный вариант — `perMachine: true`, тогда exe лежит - > в защищённом от записи `Program Files`, но установка снова требует UAC и мастера. - - Служба работает от `LocalSystem`, и её `binPath` не должен указывать в каталог, куда обычный - пользователь может писать без прав администратора — поэтому при первом включении обхода - приложение копирует `winws.exe` и WinDivert в защищённый ACL'ом - каталог в `%ProgramData%` (`protectedWinwsPath()` в `src/main/zapret/paths.ts`) и указывает - службе именно на эту копию, а не на оригинал в папке установки. При удалении приложения - деинсталлятор (`build/installer.nsh`) сам останавливает и снимает службу вместе с этой копией. - -- На одну машину служба одна: её конфиг (`%ProgramData%\rknboost\zapret\strategy.cfg`) хранит - стратегию того пользователя, который последним её менял, — с несколькими пользователями на - одной машине это разделяется некорректно. - -## Авто-обновления - -Не подключены. Если понадобятся: `npm i electron-updater`, добавить секцию `publish` -в `electron-builder.yml` и вызвать `autoUpdater.checkForUpdatesAndNotify()` в `src/main/index.ts`. diff --git a/design.md b/design.md index 0bf57fd..c97bc53 100644 --- a/design.md +++ b/design.md @@ -12,16 +12,18 @@ ## 1. Палитра ### Базовые цвета -| Назначение | HEX | Заметка | -|---|---|---| -| Фиолетовый (акцент) | `#a96bff` | HSL 275°, 100%, 71% — светлый, для тёмной темы и акцентов | -| Фиолетовый (основной) | `#6a45b8` | тон ~40 — заливка кнопок, активные элементы | -| Тёмно-фиолетовый (текст) | `#5a2da8` | текст на светло-фиолетовом контейнере | -| Крем (фон) | `#faf6f0` | основная поверхность | -| Крем (исходный) | `#f6f0ea` / `#ece0d1` | более тёплые/тёмные оттенки нейтрала | -| Оранжевый (исходный) | `#ffb76b` | стартовый цвет, ушёл в роль `tertiary` | + +| Назначение | HEX | Заметка | +| ------------------------ | --------------------- | --------------------------------------------------------- | +| Фиолетовый (акцент) | `#a96bff` | HSL 275°, 100%, 71% — светлый, для тёмной темы и акцентов | +| Фиолетовый (основной) | `#6a45b8` | тон ~40 — заливка кнопок, активные элементы | +| Тёмно-фиолетовый (текст) | `#5a2da8` | текст на светло-фиолетовом контейнере | +| Крем (фон) | `#faf6f0` | основная поверхность | +| Крем (исходный) | `#f6f0ea` / `#ece0d1` | более тёплые/тёмные оттенки нейтрала | +| Оранжевый (исходный) | `#ffb76b` | стартовый цвет, ушёл в роль `tertiary` | ### Связь оттенков + Крем `#ece0d1` (тон ~33°) и оранжевый `#ffb76b` (тон ~31°) — родственники по тёплому углу спектра. Фиолетовый — холодный акцент в противовес тёплому нейтралу. Отсюда баланс «тёплая база + прохладный акцент». @@ -29,27 +31,28 @@ ## 2. Роли Material 3 -| Роль MD3 | Светлая тема | Текст на ней (`on-`) | -|---|---|---| -| `primary` | `#6a45b8` | `#ffffff` | -| `primary-container` | `#ebddff` | `#5a2da8` | -| `secondary-container` | `#ebddff` | `#5a2da8` | -| `tertiary-container` | `#ffdcad` | `#7a4a14` | -| `surface` | `#faf6f0` | `#3a2e22` | -| `surface-container` | `#f0e8dd` | `#3a2e22` | -| `surface-container-high` | `#ece2d3` | `#3a2e22` | -| `surface-bright` (рабочая зона) | `#fdfaf6` | `#3a2e22` | -| `outline` | `#d8cdbe` | — | -| `outline-variant` | `#ece2d3` | — | -| `error` | `#ba1a1a` | `#ffffff` | -| `error-container` | `#ffdad6` | `#410002` | +| Роль MD3 | Светлая тема | Текст на ней (`on-`) | +| ------------------------------- | ------------ | -------------------- | +| `primary` | `#6a45b8` | `#ffffff` | +| `primary-container` | `#ebddff` | `#5a2da8` | +| `secondary-container` | `#ebddff` | `#5a2da8` | +| `tertiary-container` | `#ffdcad` | `#7a4a14` | +| `surface` | `#faf6f0` | `#3a2e22` | +| `surface-container` | `#f0e8dd` | `#3a2e22` | +| `surface-container-high` | `#ece2d3` | `#3a2e22` | +| `surface-bright` (рабочая зона) | `#fdfaf6` | `#3a2e22` | +| `outline` | `#d8cdbe` | — | +| `outline-variant` | `#ece2d3` | — | +| `error` | `#ba1a1a` | `#ffffff` | +| `error-container` | `#ffdad6` | `#410002` | ### Статусные цвета (тёплый подтон) -| Статус | Фон чипа | Текст | Точка | -|---|---|---|---| -| Онлайн / успех | `#c0dd97` | `#2c4d0a` | `#639922` | + +| Статус | Фон чипа | Текст | Точка | +| ----------------- | --------- | --------- | --------- | +| Онлайн / успех | `#c0dd97` | `#2c4d0a` | `#639922` | | Офлайн / нейтраль | `#e4dac9` | `#7a6a55` | `#a99a85` | -| Ошибка | `#ffdad6` | `#410002` | `#ba1a1a` | +| Ошибка | `#ffdad6` | `#410002` | `#ba1a1a` | > Принцип: `primary` (тёмный `#6a45b8`) — для заливок и текста по контрасту. > Светлый `#a96bff` держим как акцент/иконки/тёмную тему, не как фон под белый текст. @@ -64,26 +67,27 @@ - Моноширинный шрифт для технических данных: IP-адреса, VPN-ключи, rx/tx. Шкала: -| Элемент | Размер | Вес | -|---|---|---| -| Заголовок раздела | 20px | 500 | + +| Элемент | Размер | Вес | +| -------------------------------- | ------- | --- | +| Заголовок раздела | 20px | 500 | | Подзаголовок / название карточки | 15–16px | 500 | -| Основной текст | 14px | 400 | -| Заголовки таблицы | 13px | 500 | -| Данные таблицы | 13px | 400 | -| Подписи / hint | 12px | 400 | +| Основной текст | 14px | 400 | +| Заголовки таблицы | 13px | 500 | +| Данные таблицы | 13px | 400 | +| Подписи / hint | 12px | 400 | --- ## 4. Скругления (MD3 shape scale) -| Элемент | Радиус | -|---|---| -| Кнопки, чипы, инпуты (pill) | `100px` | -| Карточки / контейнеры | `24px` | -| Внутренние блоки таблицы / метрики | `16px` | -| Иконка-логотип | `8–9px` | -| Аватар, точка статуса | `50%` | +| Элемент | Радиус | +| ---------------------------------- | ------- | +| Кнопки, чипы, инпуты (pill) | `100px` | +| Карточки / контейнеры | `24px` | +| Внутренние блоки таблицы / метрики | `16px` | +| Иконка-логотип | `8–9px` | +| Аватар, точка статуса | `50%` | > Скругления только полные (по всем сторонам). Односторонних бордюров с radius не делаем. @@ -92,6 +96,7 @@ ## 5. Состояния и взаимодействие (MD3 state layers) Поверх элемента — полупрозрачный слой акцентного цвета: + - Hover: ~8% непрозрачности. - Focus / pressed: ~12%. - Для фиолетовых элементов слой фиолетовый, для нейтральных — тёмный нейтрал. @@ -99,10 +104,12 @@ Элевация — через тональные поверхности (светлее/темнее крем), а не через тени. Иерархия строится тональностью: рабочая зона данных светлее крема, крем — для фона и хрома. **Опора на платформу.** + - На десктопе (указатель) hover-слой работает как первичная обратная связь. - На тач-устройствах hover не срабатывает — обратная связь строится на нажатии и выборе, не на наведении. **Инертные vs действенные элементы.** + - Навигационные строки и статичные строки данных — **инертны** (без эффекта вдавливания); активное состояние показывается заливкой-таблеткой (`primary-container`). - Реальные действия (кнопки, переключатель, QR, удаление) могут иметь лёгкий pressed-эффект через state layer акцентного цвета (~12%). @@ -125,30 +132,31 @@ Фиолетовый акцент — общий со светлой темой по смыслу, но осветлён под тёмный фон. -| Роль | Тёмная тема | Текст на ней (`on-`) | -|---|---|---| -| `surface` | `#1a1611` | `#ede2d4` | -| `surface-container` | `#251f18` | `#ede2d4` | -| `surface-container-high` | `#302820` | `#ede2d4` | -| `surface-bright` (рабочая зона) | `#2b251d` | `#ede2d4` | -| `primary` | `#cbb3ff` (светлый фиолетовый) | `#381b6c` | -| `primary-container` | `#52389e` | `#ebddff` | -| `secondary-container` | `#483a55` | `#ebddff` | -| `outline` | `#4d4334` | — | -| `outline-variant` | `#342c20` | — | -| `error` | `#ffb4ab` | `#5c1414` | -| `error-container` | `#5c1414` | `#ffb4ab` | -| текст / `on-surface` | `#ede2d4` | — | -| приглушённый текст | `#b6a994` | — | +| Роль | Тёмная тема | Текст на ней (`on-`) | +| ------------------------------- | ------------------------------ | -------------------- | +| `surface` | `#1a1611` | `#ede2d4` | +| `surface-container` | `#251f18` | `#ede2d4` | +| `surface-container-high` | `#302820` | `#ede2d4` | +| `surface-bright` (рабочая зона) | `#2b251d` | `#ede2d4` | +| `primary` | `#cbb3ff` (светлый фиолетовый) | `#381b6c` | +| `primary-container` | `#52389e` | `#ebddff` | +| `secondary-container` | `#483a55` | `#ebddff` | +| `outline` | `#4d4334` | — | +| `outline-variant` | `#342c20` | — | +| `error` | `#ffb4ab` | `#5c1414` | +| `error-container` | `#5c1414` | `#ffb4ab` | +| текст / `on-surface` | `#ede2d4` | — | +| приглушённый текст | `#b6a994` | — | ### Статусные цвета (тёмная тема) + Фон и текст осветлены/переинвертированы под тёмный фон, подтон тот же тёплый. -| Статус | Фон чипа | Текст | Точка | -|---|---|---|---| -| Онлайн / успех | `#2d4413` | `#c8e3a0` | `#8fc24f` | +| Статус | Фон чипа | Текст | Точка | +| ----------------- | --------- | --------- | --------- | +| Онлайн / успех | `#2d4413` | `#c8e3a0` | `#8fc24f` | | Офлайн / нейтраль | `#34291c` | `#d0c0a4` | `#b0a085` | -| Ошибка | `#5c1414` | `#ffb4ab` | `#ff5449` | +| Ошибка | `#5c1414` | `#ffb4ab` | `#ff5449` | > Значения — рабочая отправная точка. Контраст текста на поверхностях стоит прогнать через проверку (цель ≥ 4,5:1 для мелкого текста) перед фиксацией. > @@ -161,16 +169,19 @@ Эти компоненты используют одни и те же роли на обеих платформах; различается только их расположение в раскладке. ### Кнопки + - **Главное действие** (Создать пользователя): заливка `primary` `#6a45b8`, текст белый (`on-primary`), pill. - **Вторичное** (Обновить): tonal — фон `secondary-container` `#ebddff`, текст `#5a2da8`, pill. - **Опасное** (Выход, удаление): контур `#e6c4bc` / роль `error`, текст `#b34a3a`, без заливки, pill. - Иконка слева от текста, gap 6–8px. ### Поля ввода и поиска + - Pill-инпут, фон `surface-bright` `#fdfaf6`, бордюр `outline` `#d8cdbe`, плейсхолдер приглушённым. - Поле поиска — с иконкой поиска слева. ### Чипы статуса + - Pill, маленькая цветная точка + подпись. Цвета — из таблицы статусов (успех / нейтраль / ошибка), текст соответствующей роли `on-`. --- @@ -178,10 +189,12 @@ # Часть III. Десктоп ### Компоновка + - Многоколоночная сетка. - Заголовок раздела — в шапке контента. ### Боковое меню (постоянная панель) + - Фиксированная ширина ~240px, всегда на экране. - Фон `#f0e8dd` (`surface-container`, на тон темнее основного — мягкое разделение без жёсткой границы). - Логотип сверху, навигация ниже, «Выход» прижат к низу (`margin-top: auto`). @@ -189,6 +202,7 @@ - Неактивные: текст `#5a4a3a`, при наведении — лёгкая фиолетовая подсветка (state layer). ### Таблица пользователей + - Контейнер `surface-bright` `#fdfaf6`, скругление 16px, бордюр `#ece2d3` (`outline-variant`). - Шапка на тоне `#f0e8dd`, текст среднего веса `#5a4a3a`. - Разделители строк — `0.5px solid #ece2d3`. @@ -201,6 +215,7 @@ # Часть IV. Мобильный ## 1. Принципы адаптации + 1. Одна колонка. Контент течёт вертикально на ширине телефона (рабочая ширина ~360px, поля по 16px). 2. Никаких таблиц с горизонтальной прокруткой — табличные данные разворачиваются в карточки. 3. Боковое меню десктопа превращается в модальный navigation drawer (паттерн Material 3). @@ -210,42 +225,51 @@ ## 2. Навигация и drawer ### Модель + Приложение имеет один основной раздел («Пользователи»). На десктопе это постоянная боковая панель ~240px; на мобильном — **модальный drawer**, скрытый по умолчанию. ### Открытие и закрытие (важно по MD3) + - Открытие — **только тапом по иконке-гамбургеру** в верхнем баре. - Закрытие — тап по затемнению (scrim) или свайп панели к её левому краю (срабатывает при сдвиге ~40px+). - Открытие edge-swipe'ом от края экрана **не используется**: жест конфликтует с системным «назад» (predictive back) на Android. ### Поведение + - Drawer выезжает слева, поверх контента, с затемнением под ним. - Затемнение блокирует взаимодействие с экраном, пока панель открыта. - Анимация — горизонтальный сдвиг панели + плавное появление scrim (быстрый, «решительный» easing, ~250–280 мс); затемнение и сдвиг анимируются вместе. ### Содержимое панели + Поверхность панели — `surface-container` (на тон темнее основного экрана), мягкое разделение без жёсткой границы. Скругление контейнера 24px, правые углы. Сверху вниз: + 1. **Шапка** — вордмарк приложения (роль заголовка) + версия (роль приглушённого текста), внутренний отступ. 2. **Тонкий разделитель** (`outline`, 0.5px). 3. **Навигация** — пункты-строки с ведущей иконкой 22px и подписью 15px, gap 16px. - - Активный пункт: pill-контейнер `primary-container` во всю ширину; текст, иконка и счётчик — `on-primary-container`, вес 500. - - Неактивный пункт: подпись и иконка — основной текст (тёмный нейтрал), без подсветки при наведении. - - Строки навигации **инертны при нажатии** — без эффекта вдавливания, выделяет только активное состояние. + - Активный пункт: pill-контейнер `primary-container` во всю ширину; текст, иконка и счётчик — `on-primary-container`, вес 500. + - Неактивный пункт: подпись и иконка — основной текст (тёмный нейтрал), без подсветки при наведении. + - Строки навигации **инертны при нажатии** — без эффекта вдавливания, выделяет только активное состояние. 4. **«Выход»** — прижат к низу (`margin-top: auto`), стиль опасного действия (контур, без заливки), pill, иконка слева. ### Замечание про объём меню + Длинный список разделов (как у Gmail-паттерна) оправдан только при 5+ назначениях. У приложения раздел один, поэтому drawer минимальный: шапка → активный пункт → «Выход» внизу. Доп. пункты добавляются только под реальные экраны. ## 3. Верхний бар (top app bar) + - Слева — иконка-гамбургер (кнопка-таргет ≥ 42px, круглая зона нажатия). - Рядом — заголовок раздела (роль заголовка, 20px / 500). - Бар без заливки и без тени; высота поверхности задаётся фоном экрана (`surface`). ## 4. Экран «Пользователи» + Порядок блоков сверху вниз: верхний бар → карточка сервера → «Добавить сервер» → форма создания → счётчик → список пользователей. ### 4.1. Карточка VPN-сервера + - Поверхность — `surface-bright`, скругление 24px. - **Активная обводка** — рамка 2px роли `primary` (приглушённый светло-фиолетовый вариант): сигнал, что сервер выбран/активен. - Слева — компактный **переключатель** (pill-трек, маленький круглый ползунок). Состояние «вкл» отражается цветом трека из роли статуса «успех» и положением ползунка. @@ -253,20 +277,25 @@ - Справа — нейтральный чип счётчика пиров (роль статуса «нейтраль», pill). ### 4.2. «Добавить сервер» + - Полноширинная кнопка-«призрак»: пунктирный контур (`outline`), без заливки, иконка `+` слева, приглушённый текст. Pill (100px). ### 4.3. Форма создания пользователя + - Поле ввода: pill-инпут на `surface-bright`, бордюр `outline`, плейсхолдер приглушённым. Полная ширина. - Под ним пара кнопок в ряд (50/50): - - **Создать** — главное действие: заливка `primary`, текст `on-primary`, иконка `+`, pill. - - **Обновить** — вторичное: tonal (`secondary-container` / `on-secondary-container`), иконка обновления, pill. + - **Создать** — главное действие: заливка `primary`, текст `on-primary`, иконка `+`, pill. + - **Обновить** — вторичное: tonal (`secondary-container` / `on-secondary-container`), иконка обновления, pill. ### 4.4. Список пользователей + - Над списком — счётчик («N пользователей») приглушённым текстом 12px. - Десктопная таблица (`#`, Статус, Имя, IP, rx, tx, действия) на мобильном **разворачивается в карточки** — по одной на пользователя. ### 4.5. Карточка пользователя + Поверхность `surface-bright`, бордюр `outline-variant`, скругление 24px. Внутри: + 1. **Верхняя строка** — слева порядковый `#N` (приглушённый) + имя (16px / 500); справа чип статуса «онлайн» (роль статуса «успех»: точка + подпись, pill). 2. **Строка IP** — иконка-глобус + адрес моноширинным. 3. **Два блока метрик** (rx / tx) в ряд 50/50: внутренние блоки на `surface-container`, скругление 16px. Сверху подпись со стрелкой (↓ rx / ↑ tx) приглушённым 12px, под ней значение моноширинным 18px / 500. @@ -274,31 +303,31 @@ ## 5. Сводка мобильных компонентов (через роли и токены) -| Компонент | Поверхность / контур | Форма | Текст | -|---|---|---|---| -| Карточка сервера | `surface-bright` + рамка `primary` 2px | 24px | подзаголовок + mono | -| Карточка пользователя | `surface-bright` + `outline-variant` | 24px | подзаголовок + mono | -| Блок метрики (rx/tx) | `surface-container` | 16px | подпись + mono | -| Кнопка «Создать» | заливка `primary` | pill 100px | `on-primary`, 500 | -| Кнопка «Обновить» | tonal `secondary-container` | pill 100px | `on-…`, 500 | -| «Добавить сервер» | пунктир `outline` | pill 100px | приглушённый | -| «Выход» | контур (опасное) | pill 100px | роль опасного | -| Поле ввода | `surface-bright` + `outline` | pill 100px | основной / плейсхолдер | -| Чип статуса | роль статуса (успех/нейтраль/ошибка) | pill 100px | соответствующий `on-` | -| Активный пункт меню | `primary-container` | pill 100px | `on-primary-container`, 500 | +| Компонент | Поверхность / контур | Форма | Текст | +| --------------------- | -------------------------------------- | ---------- | --------------------------- | +| Карточка сервера | `surface-bright` + рамка `primary` 2px | 24px | подзаголовок + mono | +| Карточка пользователя | `surface-bright` + `outline-variant` | 24px | подзаголовок + mono | +| Блок метрики (rx/tx) | `surface-container` | 16px | подпись + mono | +| Кнопка «Создать» | заливка `primary` | pill 100px | `on-primary`, 500 | +| Кнопка «Обновить» | tonal `secondary-container` | pill 100px | `on-…`, 500 | +| «Добавить сервер» | пунктир `outline` | pill 100px | приглушённый | +| «Выход» | контур (опасное) | pill 100px | роль опасного | +| Поле ввода | `surface-bright` + `outline` | pill 100px | основной / плейсхолдер | +| Чип статуса | роль статуса (успех/нейтраль/ошибка) | pill 100px | соответствующий `on-` | +| Активный пункт меню | `primary-container` | pill 100px | `on-primary-container`, 500 | --- # Отличия десктоп / мобильный (кратко) -| Аспект | Десктоп | Мобильный | -|---|---|---| -| Навигация | постоянная боковая панель ~240px | модальный drawer (гамбургер) | -| Список пользователей | таблица с колонками | карточки, по одной на пользователя | -| rx / tx | ячейки таблицы | два блока-метрики внутри карточки | -| Действия в строке | иконки в конце строки | ряд кнопок внизу карточки | -| Ширина | многоколоночная сетка | одна колонка, поля 16px | -| Заголовок раздела | в шапке контента | в верхнем баре рядом с гамбургером | +| Аспект | Десктоп | Мобильный | +| -------------------- | -------------------------------- | ---------------------------------- | +| Навигация | постоянная боковая панель ~240px | модальный drawer (гамбургер) | +| Список пользователей | таблица с колонками | карточки, по одной на пользователя | +| rx / tx | ячейки таблицы | два блока-метрики внутри карточки | +| Действия в строке | иконки в конце строки | ряд кнопок внизу карточки | +| Ширина | многоколоночная сетка | одна колонка, поля 16px | +| Заголовок раздела | в шапке контента | в верхнем баре рядом с гамбургером | --- @@ -310,4 +339,4 @@ 4. Sentence case, два веса шрифта, без капса. Технические данные — моноширинным. 5. Главное действие — залито фиолетовым; вторичное — tonal; опасное — контур. 6. Таблицы → карточки, rx/tx → блоки-метрики на мобильном; одна колонка, крупные тач-таргеты, действия в зоне большого пальца. -7. Drawer открывается гамбургером, закрывается scrim/свайпом; edge-swipe не задействуем. Hover — опора десктопа; на тач-устройствах обратная связь на нажатии/выборе. \ No newline at end of file +7. Drawer открывается гамбургером, закрывается scrim/свайпом; edge-swipe не задействуем. Hover — опора десктопа; на тач-устройствах обратная связь на нажатии/выборе.