# Закрытие модальных окон нажатием снаружи

## Требование и реализация — 2026-10-28

Пользователь попросил закрывать модальное окно нажатием вне его границ. Новый независимый модуль `src/runtime/modal-backdrop.mjs` экспортирует `createModalBackdrop`, `bindModalBackdropEvents` и `unbindModalBackdropEvents`. Зависимостей игрового контекста нет (`requiredPorts=[]`). Интегратор подключает фабрику через `registerFeature` и один раз вызывает `bindModalBackdropEvents()` при привязке интерфейса.

Обработчики установлены через `addEventListener` в фазе capture на документе. Это охватывает все 23 статических `<dialog>` в `src/index.html`, а также позднее созданные `projectEditorDialog` и `unitDesignerDialog`. Общие обработчики `onclick` не заменяются. Меню производства `role=dialog` внутри города является вложенным списком; оно уже обрабатывает штатную отмену города первым, поэтому отдельной имитации модального backdrop для него нет.

Закрытие выполняется только после полного основного жеста pointerdown → pointerup → click: начало, конец и нажатие находятся строго снаружи текущего `getBoundingClientRect`, а целевой элемент — сам native dialog, на который браузер перенаправляет события его backdrop. Рамка, padding, внутреннее содержимое, дочерний элемент с выступающими границами, правая/средняя кнопки и клавиатурный click не считаются наружным нажатием. Перетаскивание из содержимого наружу не закрывает окно. Движение более 8 CSS px, прокрутка, pointercancel, потеря фокуса или закрытие окна сбрасывают незавершённый жест. Небольшое естественное смещение касания допустимо.

Перед `close()` отправляется отменяемое небулькающее событие `cancel`, соответствующее пути Escape. Обработчики черновиков расформирования, модернизации, дипломатической миссии, нарушения эмбарго и генерации новой карты сохраняют свою очистку; редактор завершает мазок, отменяет worker и сохраняет проект. Обработчик не отправляет игровые команды, не нажимает кнопки действий и не меняет `pendingDecisions`. Закрытие governor без применения сохраняет настройки партии; открытие заново берёт их из игрового состояния. Вложенные native dialog закрываются по одному, без переноса клика на карту.

## Обязательные решения и ограничение

Существующие `preventDefault()` продолжают действовать: захват/культурное присоединение города, входящие мир и торговля всегда запрещают отмену; выбор правительства, науки/бесплатной технологии и производства запрещает отмену при соответствующем обязательном решении. У production сначала может закрыться вложенный список, оставив город открытым. Редактор типов юнитов также запрещает отмену во время своей операции `working`.

Наружное нажатие на эти обязательные экраны не выбирает ответ и не скрывает требуемое решение. Это существующее ограничение самой отмены, не новая проверка ядра. Сейчас у захвата/мира/входящей торговли нет отдельного пути повторно открыть временно скрытое решение; принудительное закрытие могло бы оставить игрока без доступного продолжения. Изменение такого поведения требует отдельного согласованного интерфейса возврата к сохранённому решению. Правила партии в этой задаче не ослаблены.

## Проверка

`tests/modal-backdrop.test.mjs` непосредственно импортирует исходный модуль и проверяет отмену/очистку до close, запрет отмены, геометрию, drag, scroll, touch, pointercancel, разные указатели/кнопки, повторную привязку/освобождение обработчиков и минимальный DOM без `defaultView`.

`tests/browser-modal-backdrop.cjs --isolated` использует отдельную HTTP-страницу с исходным модулем и настоящими native dialog Chromium: padding, drag наружу, прокрутка, ПКМ, наружный click/cancel, preventDefault, вложенные окна, отсутствие click-through и мобильное касание. Shared `dist` при этом не читается и не изменяется. Отчёт: `research/original-game/data/browser-modal-backdrop-isolated-verification.json`.

Полный драйвер без `--isolated` дополнительно проверяет интегрированную сборку: параметры, новую партию, обычную науку с неприменённым бюджетом, D/очистку подтверждения, губернатора, последовательную отмену списка производства/города, обычные переговоры, существующие cancel-guards захвата/мира/входящей торговли, динамические редакторы карты/типов юнитов и параметры при ширине 390 px. Guard-проверки открывают существующие DOM-окна напрямую, без поддельных игровых решений; сохранность реальных обязательных решений проверяется также прежними тематическими сценариями. Отчёт полного прогона: `research/original-game/data/browser-modal-backdrop-verification.json`.

Все запуски выполняются через `python3 scripts/run-limited.py -- …` (35% CPU). Сборку, полный браузерный прогон и публикацию общей рабочей копии выполняет интегратор. На момент передачи интеграции полный прогон и публикация ещё не выполнены.

Передача интегратору в 04:57 МСК: 11/11 модульных проверок прошли; изолированный Chromium прошёл все пять групп проверок, включая реальное мобильное касание; ошибок страницы и запросов нет. Desktop и mobile используют отдельные последовательные процессы Chromium из-за известного ограничения `--single-process` при смене browser context. В минимальном DOM допустимо отсутствие `defaultView`. Исходник модуля заморожен для общей сборки.


## Публичная интеграцияv0.10.0

Опубликовано28.10.2026 в05:13 МСК, код`f1d04cdd4cd1b1be`. Сценарий реально выполнен вChromium локально и на основном HTTPS-сайте. [Текущий отчёт](data/browser-modal-backdrop-verification.json). Общая сверка146 файлов кода/данных/ресурсов не нашла расхождений; независимые служебные страницы сохранены. Полный Windows-оригинал этими проверками не объявлен воспроизведённым.
