> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# White-label оформление

> Палитра, скругления, отступы, режимы и изображения, под которыми отрисовывается установка, что отклоняет сохранение и почему список токенов короткий

`/admin/settings/brand` содержит две вещи. Раздел **branding** называет сервис и
его публичные ссылки. Ниже — **Оформление**, место, где установка перестаёт
выглядеть как Omniflow и начинает выглядеть как она сама: палитра для светлой и
тёмной темы, радиус скругления, плотность отступов, набор доступных режимов и
три изображения.

> [!NOTE]
>
> Оформление — единственная настройка установки, которая вступает в силу сразу.
> Любой другой раздел экрана настроек — версионированная запись намерения,
> которую работающие процессы не перечитывают, см.
> [Настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md). Этот же раздел
> читается обеими панелями при каждой отрисовке страницы, за минутным кэшем, так
> что нажать «Сохранить» и перезагрузить — это и есть вся процедура развёртывания.

Всё здесь необязательно. То, что вы не задали, остаётся штатным дизайном, и для
него ничего не отдаётся: установка, ничего не настроившая, вообще не отдаёт
дополнительную таблицу стилей, а не копию дизайна, объявленную заново.

## Что оператор может изменить

| Контрол | Значения | Что делает |
| - | - | - |
| Палитра | Hex-цвет на токен и на режим | Переопределяет один токен дизайна. Пустое поле возвращает токену его собственное значение. |
| Углы | `square`, `compact`, `default`, `rounded` | Умножает все шесть радиусов на 0, 0,6, 1 или 1,6. |
| Отступы | `compact`, `default`, `comfortable` | Умножает базу отступов на 0,88, 1 или 1,14. Все паддинги, зазоры и вставки следуют за ней. |
| Доступные режимы | Светлый, тёмный или оба | Единственный режим форсируется, а переключатель темы исчезает с обеих поверхностей. |
| Режим по умолчанию | `light`, `dark` или как на устройстве | Что получает посетитель без сохранённого предпочтения. |
| Изображения | Логотип (светлый), логотип (тёмный), значок вкладки | PNG, JPEG или WebP, до 256 КБ каждое. |

Углы и отступы — **масштабы, а не значения**. В дизайне шесть связанных
радиусов: 5 px у чипов, 8–9 px у контролов, 10 px у строк, 12 px у карточек, — и
оператор, задающий шесть чисел, выбирал бы соотношение, которого не видит.
Масштабирование сохраняет соотношение и меняет характер.

Диапазон отступов узкий по обратной причине: одна переменная достигает и
паддинга ячейки таблицы, и вставки шторки, и зазора между двумя кнопками сразу,
поэтому значение, выглядевшее разумным по отдельности, ломается там, куда вы не
смотрели.

## Список токенов и то, чего в нём намеренно нет

Настраиваются двадцать три токена: поверхности (`background`, `card`, `popover`,
`chrome`), их передние планы, тона бренда (`primary`, `secondary`, `accent`,
`ring`), тихие тона текста (`muted-foreground`, `subtle-foreground`), линии
(`border`, `input`, `chrome-border`) и три серии графиков.

Панель рисует список, публикуемый API, а не собственный, поэтому она не может
предложить слот, который эта сборка не поддерживает.

**Статусные тона не настраиваются.** `destructive`, `success`, `warning` и
`info` остаются такими, как в дизайне. Их смысл и есть то, что они сообщают, а
установка, у которой «destructive» зелёный, не брендирована — она сломана, ровно
там, где ошибка стоит человеку необратимого действия. Эта граница ценнее полного
списка.

## Что отклоняет сохранение

Контраст считается на сервере, против той палитры, которую посетитель
действительно видит, — переопределения оператора поверх собственных значений
дизайна. Тот, кто задал только `card`, изменил контраст каждого переднего плана,
нарисованного на карточке, и проверка, смотрящая только на изменённое, не
сообщила бы ничего.

В каждом доступном режиме проверяются одиннадцать пар, и исход один из трёх:

| Отношение | Исход |
| - | - |
| 4,5:1 и лучше | Проходит WCAG 2.2 AA для основного текста. Ничего не сообщается. |
| От 3:1 до 4,5:1 | Сохраняется с предупреждением, называющим пару и отношение. |
| Меньше 3:1 | **Отклоняется.** Сообщение называет худшую пару. |

Граница между двумя последними и есть суть. Тон бренда, не дотягивающий до AA, —
решение, которое оператор вправе принять и о котором вправе быть предупреждён.
Ниже 3:1 в WCAG 2.2 нет порога, при котором пара пригодна хоть кому-то, так что
это не решение — это экран, который никто не может прочитать, включая тот экран,
на котором это пришлось бы исправлять.

Режим, который вы не предлагаете, не проверяется. Установку только со светлой
темой не держат под требованиями тёмной палитры, которую она никогда не рисует.

Предупреждения пересчитываются при каждом чтении, а не хранятся, поэтому они не
могут устареть относительно палитры, отредактированной откуда-то ещё.

## Изображения

Три слота, по одному изображению. Загрузка заменяет, удаление возвращает
установку к штатному знаку.

**SVG не принимается, и это не упущение.** SVG-документ может нести элемент
`<script>` и внешние ссылки, а эти файлы отдаются с того же источника, что и обе
панели, — источника, где живёт сессионный cookie. Принять его значило бы вручить
хранимый межсайтовый скриптинг любому, кто дотягивается до экрана настроек. PNG,
JPEG и WebP — форматы, которые браузер рисует, ничего не исполняя.

Объявленный content type сверяется с собственным «магическим числом» байтов, так
что ничего нельзя сохранить под типом изображения и получить обратно с этим
заголовком.

Каждое изображение публикуется по адресу `/v1/branding/assets/{kind}?v=<checksum>`,
где checksum — SHA-256 байтов. Запрос, называющий текущую контрольную сумму,
отвечается как неизменяемый, поэтому браузер держит логотип, пока вы его не
замените, — и тогда адрес меняется, а новый появляется немедленно.

## Как это доходит до страницы

`GET /v1/branding` публичен и не требует аутентификации, и иначе быть не может:
экран входа — первая страница, которую видит кто угодно, и именно та, на которой
установка больше всего хочет нести свой знак. Он отвечает названием сервиса,
палитрой, уже отрисованной как CSS-объявления, настройками формы и адресами
изображений — и ничем больше, потому что всё в нём читается любым, кто
дотягивается до установки.

Веб-приложение читает его на сервере и встраивает объявления в head документа,
поэтому палитра применяется на первой отрисовке, а не после гидрации. Палитра,
применённая после гидрации, — это видимая вспышка чужого бренда при каждой
холодной загрузке.

> [!NOTE]
>
> У установки без панели оператора нет темы для публикации, и маршрут
> отсутствует. В этом случае — и в любом случае, когда API недоступен, —
> веб-приложение рисует штатный дизайн: цвет, который не удалось прочитать,
> никогда не должен быть причиной, по которой страница не отрисовалась.

## Почему значения можно безопасно встраивать

Строка, попадающая в элемент `<style>`, порождается в Go, из списка разрешённого,
парсером, который принимает hex-цвета и ничего больше:

* **Имя токена выбирается, а не задаётся.** Панель отправляет имя из
  опубликованного списка; всё прочее отклоняется. Имя свойства — это то место,
  через которое прошёл бы произвольный CSS.
* **Значение — `#rgb` или `#rrggbb`**, нормализуемое к шести строчным цифрам. Не
  ключевое слово, не `rgb()`, не `var()`, не `url()`.
* Поэтому алфавит вывода не может содержать ни фигурной скобки, ни точки с
  запятой, ни последовательности, закрывающей элемент.

Второй реализации ничего из этого в браузере нет — включая формулу WCAG. Две
такие реализации рано или поздно разошлись бы в том, какие палитры допустимы, и
права была бы только та, что отклоняет сохранения. Что панель считает локально —
это предпросмотр: она задаёт текущие значения формы как CSS-переменные на одном
элементе-обёртке и позволяет браузеру показать оператору, как эти цвета выглядят
вместе.

## Права и журнал

Чтение требует `settings.read`, любое изменение — `settings.write`. Сохранение
палитры проходит тот же контроль версии, что и любой другой раздел настроек,
поэтому две вкладки панели конфликтуют, а не одна тихо отбрасывает работу другой.

Загрузки и удаления попадают в журнал как `branding.asset.saved` и
`branding.asset.removed` в категории `configuration`. Метаданные записи содержат
content type, размер и контрольную сумму — никогда сам файл. Фиксируемый факт в
том, что кто-то поменял логотип во вторник.

## Смежные страницы

> **[Настройки установки](https://omniflow-vpn.mintlify.site/ru/operations/installation-settings.md)**
>
> Остальные разделы, контроль версии и то, какие значения реально меняют поведение.

> **[Панель администратора](https://omniflow-vpn.mintlify.site/ru/operations/admin-panel.md)**
>
> Роли, права и журнал аудита за этим экраном.

> **[Кабинет клиента](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md)**
>
> Поверхность, на которой клиент видит эти цвета.

> **[Публичный API](https://omniflow-vpn.mintlify.site/ru/integrations/api.md)**
>
> Какие поверхности несут обещание версии, а какие нет.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.