diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..46be5b5
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+dist-ssr
+.git
+docs
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7a74a91
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+# Static build served by nginx. VITE_BITRIX_ORIGIN must be provided at build
+# time (build arg) — it gets baked into the JS bundle, see docs/bitrix-integration.md §7.
+FROM node:22-alpine AS build
+WORKDIR /app
+COPY package.json package-lock.json ./
+RUN npm ci
+COPY . .
+ARG VITE_BITRIX_ORIGIN
+ENV VITE_BITRIX_ORIGIN=$VITE_BITRIX_ORIGIN
+RUN npm run build
+
+FROM nginx:alpine
+COPY --from=build /app/dist /usr/share/nginx/html
+EXPOSE 80
diff --git a/docs/bitrix-integration.md b/docs/bitrix-integration.md
new file mode 100644
index 0000000..6f6ef70
--- /dev/null
+++ b/docs/bitrix-integration.md
@@ -0,0 +1,261 @@
+# Интеграция с Bitrix — спецификация для backend-разработчика
+
+Это документ для человека, который не участвовал в разработке самого React-приложения (`portal-chart-builder`) и не обязан знать TypeScript — здесь только то, что нужно на стороне PHP/Bitrix.
+
+## 1. Что это
+
+Конструктор диаграмм — отдельное React-приложение (не часть Bitrix24, продукт — «1С-Битрикс: Управление сайтом»). Встраивается как **пользовательский тип свойства инфоблока**: в админке, на странице редактирования элемента, вместо обычного поля отображается iframe с редактором диаграммы. Значение свойства — JSON-строка со схемой диаграммы, хранится как обычное строковое свойство (`PROPERTY_TYPE => 'S'`).
+
+Никакого REST-модуля, вебхуков или JS SDK Bitrix24 не требуется — это нативный механизм модуля `iblock`.
+
+## 2. Регистрация типа свойства
+
+В `local/php_interface/init.php`:
+
+```php
+AddEventHandler("iblock", "OnIBlockPropertyBuildList", ["DiagramProperty", "GetUserTypeDescription"]);
+```
+
+После регистрации новый тип появляется в **Контент → Инфоблоки → [нужный] → Свойства → Тип свойства**. Свойство должно быть **одиночным** (`MULTIPLE = N`) — одна диаграмма на элемент.
+
+## 3. Скелет PHP-класса
+
+Ниже — иллюстративный каркас, не готовый к продакшену код. Реализация валидации, экранирования и т.д. — на усмотрение разработчика, но с обязательным соблюдением требований из раздела 5.
+
+```php
+class DiagramProperty
+{
+ public static function GetUserTypeDescription(): array
+ {
+ return [
+ 'PROPERTY_TYPE' => 'S',
+ 'USER_TYPE' => 'DiagramEditor',
+ 'DESCRIPTION' => 'Конструктор диаграмм процессов',
+ 'GetPropertyFieldHtml' => [__CLASS__, 'GetPropertyFieldHtml'],
+ 'ConvertToDB' => [__CLASS__, 'ConvertToDB'],
+ 'ConvertFromDB' => [__CLASS__, 'ConvertFromDB'],
+ ];
+ }
+
+ public static function GetPropertyFieldHtml($arProperty, $value, $strHTMLControlName): string
+ {
+ $inputName = htmlspecialcharsbx($strHTMLControlName['VALUE']);
+ $rawValue = $value['VALUE'] ?? '';
+ // JSON_HEX_* флаги обязательны — см. раздел 5.
+ $jsValue = json_encode($rawValue, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT);
+
+ // Вариант A — рекомендуемый, приложение лежит внутри дерева портала (см. раздел 7):
+ // appOrigin — это просто origin текущего запроса, appUrl — конкретный файл.
+ $appOrigin = (CMain::IsHTTPS() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'];
+ $appUrl = $appOrigin . '/local/diagram-app/index.html';
+ // Вариант B — если приложение развёрнуто отдельным контейнером (см. раздел 7),
+ // appUrl/appOrigin вместо этого указывают на его отдельный адрес, например:
+ // $appOrigin = 'http://localhost:5178';
+ // $appUrl = $appOrigin;
+
+ ob_start();
+ ?>
+
+
+
+ }
+5. Пользователь редактирует → с debounce ~400мс:
+ Приложение → мост: { type: 'diagram:change', payload: '' }
+6. Мост при каждом diagram:change пишет payload в value hidden input
+7. Пользователь жмёт штатное «Сохранить» → браузер сабмитит форму → ConvertToDB → сохранение
+```
+
+Важно: `payload` в `diagram:change` — **уже сериализованная JSON-строка** (`JSON.stringify` со стороны приложения), не объект. Мост должен положить её в `input.value` как есть, без повторной обработки.
+
+`diagram:init` с `payload: null` (или пустой строкой) — валидный случай: новый элемент без диаграммы, приложение стартует с чистого холста.
+
+## 5. Требования безопасности (обязательны, не опциональны)
+
+- При встраивании `initialValue` в инлайн-скрипт — **обязательно** `JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT` (см. пример в разделе 3). Без этого — риск XSS через выход из `