# Руководство по интерфейсу GetMeBack для iframe-модулей

Руководство описывает только правую контентную часть админки. Левое меню, верхняя панель и подвал хоста не нужно воспроизводить внутри iframe.

Параметры сняты 14 августа 2026 года с экрана «Онлайн-заказы → Настройки → Самовывоз» в демо-админке GetMeBack. Локальные классы исходного приложения намеренно не копируются: они содержат хэши сборки и могут измениться. Вместо них в [`gmb-admin.css`](./gmb-admin.css) зафиксированы стабильные семантические классы.

## 1. Контракт встраивания

Рекомендуемая схема:

```text
правая область GetMeBack
└── внутренний отступ хоста 20 px
    └── iframe 100% ширины, border: 0
        └── .gmb-page без горизонтального отступа
```

Если iframe занимает всю `.right_col`, включая область до внутреннего отступа хоста, добавьте странице класс `.gmb-page--inset`: он создаёт отступ 20 px по горизонтали и 10 px сверху. Если iframe уже стоит внутри существующего отступа, этот модификатор нужно убрать.

Обязательные настройки iframe:

```html
<iframe
  src="https://your-module.example/settings"
  title="Настройки модуля"
  style="display:block;width:100%;min-height:620px;border:0;background:#f7f7f7"
></iframe>
```

- Не задавайте фиксированную ширину контента.
- Не создавайте внутри iframe второе меню или шапку.
- Фон iframe и страницы: `#F7F7F7`.
- Для динамической высоты пример отправляет родителю сообщение `{ type: "gmb:iframe-height", height }`. Родитель должен проверять `event.origin` перед применением высоты.
- Если родитель и iframe находятся на разных доменах, не пытайтесь читать CSS админки через `window.parent.document`: это запрещено браузером и делает модуль хрупким.

Пример обработчика на стороне родителя:

```js
const frame = document.querySelector("#external-module");
const moduleOrigin = "https://your-module.example";

window.addEventListener("message", (event) => {
  if (event.origin !== moduleOrigin || event.source !== frame.contentWindow) return;
  if (event.data?.type !== "gmb:iframe-height") return;

  const height = Math.max(320, Math.min(Number(event.data.height), 4000));
  if (Number.isFinite(height)) frame.style.height = `${height}px`;
});
```

## 2. Основные токены

| Назначение | Значение |
|---|---:|
| Акцент / primary | `#26B99A` |
| Акцент при наведении | `#169F85` |
| Ссылка | `#26B99A` |
| Ссылка при наведении | `#169F85` |
| Основной заголовок | `#3A3A3A` |
| Основной текст | `#2B3134` |
| Вторичный текст | `#999999` |
| Фон страницы | `#F7F7F7` |
| Фон элементов | `#FFFFFF` |
| Граница поля | `#B0BAC3` |
| Граница поля при наведении | `#858C92` |
| Разделитель | `#E8E8E8` |
| Ошибка | `#FF5252` |
| Радиус поля / блока | `8px` |
| Радиус кнопки | `4px` |

Основной шаг отступов — 4 px. На практике чаще используются `4 / 8 / 12 / 16 / 20 / 24 / 32 / 40` px.

## 3. Типографика

GetMeBack использует две группы шрифтов:

- заголовки, подписи контролов и кнопки — `Gilroy, HelveticaNeue, Helvetica, Arial, sans-serif`;
- описания и значения полей — `Arial, HelveticaNeue, Helvetica, sans-serif`.

| Роль | Размер / строка | Вес | Шрифт |
|---|---:|---:|---|
| Заголовок страницы | `34 / 40 px` | `700` | Gilroy |
| Заголовок секции | `18 / normal` | `700` | Gilroy |
| Подпись поля/свитча | `13 / 17 px` | `700` | Gilroy |
| Кнопка | `14 / normal` | `400` | Gilroy |
| Описание | `12 / 14 px` | `400` | Arial |
| Текст поля | `13 / 21 px` | `500` | Arial |

Описание секции и подсказка загрузчика в исходной админке визуально близки, но используют разные варианты: описание секции — `#999999`, weight 400 и нижний отступ 8 px; подсказка загрузчика — `#8C8C8C`, weight 300 и отрицательный верхний отступ 7 px. Не объединяйте их в один класс, если нужна точная совместимость с GetMeBack.

В CSS указан официальный публичный URL шрифта, который использует текущая админка. Для production лучше хранить разрешённые файлы шрифта на своём CDN: так модуль не зависит от доступности чужого хоста. Если это невозможно, каскад корректно перейдёт на Helvetica или Arial.

## 4. Каркас страницы

Порядок элементов типового экрана настроек:

1. Кнопка «Вернуться назад»: высота 28 px, зелёный текст 13 px, снизу 16 px.
2. `h1`: 34 px, строка 40 px, снизу 16 px.
3. Секция настроек: заголовок 18 px и, при необходимости, описание 12 px.
4. Поля формы в колонке шириной до 480 px.
5. Кнопка «Сохранить» высотой 36 px.

Большие независимые части страницы разделяйте линией `1px solid #E8E8E8` и вертикальным интервалом 24 px. Не помещайте обычную форму в белую карточку без необходимости: эталонный экран размещает настройки непосредственно на сером фоне.

## 5. Компоненты

### Текстовое поле

- Подпись располагается сверху, отступ до поля 4 px.
- Высота поля: 36 px.
- Белый фон, граница 1 px `#B0BAC3`, радиус 8 px.
- Горизонтальный внутренний отступ: 12 px.
- Hover: граница `#858C92`.
- Focus: граница `#26B99A`, без стандартной синей обводки.
- Ошибка: граница `#FF5252`, пояснение 12 px под полем.
- Не используйте placeholder вместо постоянной подписи.

```html
<div class="gmb-field">
  <label class="gmb-field__label" for="site-name">Название сайта</label>
  <input class="gmb-input" id="site-name" name="siteName" type="text">
</div>
```

### Выпадающий список с поиском

Для справочников используйте поисковый combobox, повторяющий поле «Категория
товара» в GetMeBack. Нативный `<select>` допустим только как упрощённый fallback:
его меню заметно отличается между браузерами и хуже совпадает с админкой.

- подпись и внешняя геометрия совпадают с текстовым полем: высота 36 px,
  граница `#B0BAC3`, радиус 8 px;
- справа находится стрелка 18 px в зоне шириной 36 px;
- в раскрытом состоянии граница и стрелка становятся `#26B99A`, стрелка
  поворачивается вверх;
- меню открывается на 2 px ниже поля, имеет ту же ширину, белый фон,
  тонкую границу и тень `0 5px 12px rgba(0,0,0,.22)`;
- максимальная высота списка — 224 px, дальше прокручивается только меню;
- строка варианта — не ниже 40 px, внутренний отступ 12 px, Arial 13/20 px;
- при открытии показывайте полный список и выделяйте текст текущего значения —
  пользователь может сразу напечатать запрос;
- ввод фильтрует варианты без изменения сохранённого значения; значение меняется
  только после выбора варианта;
- если совпадений нет, внутри меню выводится «Ничего не найдено»;
- клик снаружи, Tab и Escape закрывают меню; Escape возвращает прежнее значение;
- Arrow Up/Down перемещают активный вариант, Enter выбирает его.

```html
<div class="gmb-field">
  <label class="gmb-field__label" for="channel-search">Канал</label>
  <div class="gmb-combobox" data-combobox>
    <div class="gmb-combobox__control">
      <input
        class="gmb-combobox__input"
        id="channel-search"
        type="text"
        role="combobox"
        aria-autocomplete="list"
        aria-controls="channel-listbox"
        aria-expanded="false"
        autocomplete="off"
        value="Push"
      >
      <button class="gmb-combobox__toggle" type="button" tabindex="-1" aria-label="Показать варианты">
        <!-- SVG-стрелка -->
      </button>
    </div>

    <div class="gmb-combobox__menu" id="channel-listbox" role="listbox" hidden>
      <button class="gmb-combobox__option" type="button" role="option" data-value="Push" aria-selected="true">Push</button>
      <button class="gmb-combobox__option" type="button" role="option" data-value="Wallet" aria-selected="false">Wallet</button>
    </div>

    <input name="channel" type="hidden" value="Push">
  </div>
</div>
```

Редактируемый input отвечает за поиск, а скрытый input — за отправляемое значение.
Связывайте активный пункт с полем через `aria-activedescendant`, обновляйте
`aria-selected` после выбора и всегда синхронизируйте `aria-expanded` с фактической
видимостью меню. Полная реализация поведения есть в
[`example/list.js`](./example/list.js).

### Свитч

- Трек: `33 × 20 px`, радиус 14 px.
- Бегунок: `16 × 16 px`, белый.
- Выключен: полупрозрачный чёрный трек.
- Включен: трек `#26B99A`, бегунок смещается на 13 px.
- Подпись справа: Gilroy 13/16 px, weight 700; промежуток 10 px; визуально текст смещён вниз на 2 px относительно центра трека.
- Используйте настоящий `<input type="checkbox">`, чтобы управление работало с клавиатуры и скринридером.
- Строка свитча имеет высоту 28 px.
- Если нужна подсказка, оборачивайте строку и текст в `.gmb-switch-field`.
- Подсказка начинается сразу под строкой, выравнивается по левому краю всего компонента и использует Arial 12/14 px, weight 400, `#999999`.
- Не помещайте подсказку внутрь кликабельного `label`: иначе клик по тексту может неожиданно менять значение, а длинный текст усложняет компоновку.

```html
<div class="gmb-switch-field">
  <label class="gmb-switch">
    <input class="gmb-switch__input" type="checkbox">
    <span class="gmb-switch__track" aria-hidden="true"></span>
    <span class="gmb-switch__label">Включить контакт</span>
  </label>
  <p class="gmb-switch-field__hint">Показывать контактную информацию на сайте</p>
</div>
```

### Радиокнопки

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

- заголовок группы — Gilroy 14/18 px, weight 700;
- внешний круг — `18 × 18 px`, граница 2 px;
- выключенное состояние — граница `#777777`;
- выбранное состояние — граница и внутренний круг `#26B99A`;
- внутренний круг — `10 × 10 px`;
- расстояние от круга до подписи — 10 px;
- подпись — Gilroy 13/20 px, weight 400, `#3A3A3A`;
- расстояние между вариантами — 8 px;
- варианты визуально смещены вправо на 4 px относительно заголовка группы;
- пояснение располагается под подписью, а не под кругом: Arial 12/18 px, weight 400, `#999999`;
- вся строка является кликабельным `label`, а в разметке остаётся настоящий `<input type="radio">`.

```html
<fieldset class="gmb-radio-group">
  <legend class="gmb-radio-group__title">Переход на уровень</legend>
  <div class="gmb-radio-group__options">
    <label class="gmb-radio">
      <input
        class="gmb-radio__input"
        type="radio"
        name="levelTransition"
        value="automatic"
        checked
      >
      <span class="gmb-radio__control" aria-hidden="true"></span>
      <span class="gmb-radio__content">
        <span class="gmb-radio__label">
          Автоматически по достижению суммы покупок
        </span>
      </span>
    </label>

    <label class="gmb-radio">
      <input
        class="gmb-radio__input"
        type="radio"
        name="levelTransition"
        value="manual"
      >
      <span class="gmb-radio__control" aria-hidden="true"></span>
      <span class="gmb-radio__content">
        <span class="gmb-radio__label">
          Вручную или по промокоду (скрытый уровень)
        </span>
        <span class="gmb-radio__hint">
          Скрытый уровень будут видеть только его члены. Переход на уровень
          возможен вручную или по промокоду.
        </span>
      </span>
    </label>
  </div>
</fieldset>
```

У всех вариантов одной группы должно быть одинаковое значение атрибута `name`. При ошибке валидации связывайте сообщение со всей группой через `fieldset`, а не с одной радиокнопкой.

### Ползунок

Ползунок — для числовой настройки, у которой важен не сам вход, а результат:
масштаб, прозрачность, задержка. Если значение вводят точной цифрой (цена,
количество дней) — берите текстовое поле.

- заголовок — Gilroy 14/18 px, weight 700, как у группы радиокнопок;
- текущее значение — Arial 13/20 px, `#999999`, прижато вправо на одной строке
  с заголовком; показывается всегда, а не только при перетаскивании;
- дорожка — высота 4 px, радиус 2 px, `#E8E8E8`;
- пройденная часть — `#26B99A` (в Firefox рисуется через `::-moz-range-progress`);
- бегунок — круг 18 × 18 px, заливка `#FFFFFF`, граница 2 px `#26B99A`;
- расстояние между заголовком, дорожкой и пояснением — 8 px;
- пояснение под ползунком — Arial 12/18 px, `#999999`.

```html
<div class="gmb-range-field">
  <div class="gmb-range-field__head">
    <label class="gmb-range-field__label" for="overlay-scale">Масштаб логотипа</label>
    <span class="gmb-range-field__value" id="overlay-scale-value">80%</span>
  </div>
  <input class="gmb-range" type="range" id="overlay-scale" min="20" max="100" step="5" value="80">
  <p class="gmb-range__hint">Доля ширины баннера, которую занимает логотип.</p>
</div>
```

Значение рядом с заголовком обновляйте на событие `input`, а не `change`:
иначе цифра догоняет бегунок только после того, как его отпустили.

### Загрузка изображения

Компонент повторяет загрузчик со страницы редактирования новости:

- постоянная подпись — Gilroy 13/17 px, weight 700, отступ до плитки 4 px;
- пустая плитка — `200 × 170 px`, белый фон, пунктирная граница `#B0BAC3`, радиус 8 px;
- иконка — оригинальная `mdi-plus`, 50 px; текст действия — «Добавить», Gilroy 16/24 px, weight 300;
- hover — граница и содержимое `#858C92`;
- после выбора плитка заменяется карточкой `200 × 170 px` со сплошной границей;
- карточка имеет внутренний отступ 10 px и область предпросмотра высотой 125 px;
- между областью предпросмотра и строкой имени остаётся 9 px (`justify-content: space-between`);
- изображение показывается через `object-fit: contain`, поэтому не обрезается;
- имя файла — Arial 11 px, `#8C8C8C`, длинное имя сокращается многоточием;
- справа от имени используются оригинальные `mdi-download` и `mdi-trash-can-outline`, 16 px;
- подсказка — Arial 12/14 px, `#8C8C8C`;
- для баннера используйте рекомендацию `1200×800 px`, соотношение сторон `3:2`.

```html
<div class="gmb-upload">
  <span class="gmb-upload__label">Загрузите баннер для сайта</span>
  <div class="gmb-upload__items">
    <label class="gmb-upload__dropzone">
      <input class="gmb-upload__input" type="file" accept="image/*">
      <span class="gmb-upload__empty">Добавить</span>
    </label>
  </div>
  <p class="gmb-upload__hint">
    Рекомендуемый размер 1200×800 пикселей (соотношение сторон 3:2)
  </p>
</div>
```

Показывайте локальный предпросмотр сразу после выбора. При замене и удалении файла освобождайте URL через `URL.revokeObjectURL()`. Проверяйте MIME-тип на клиенте для удобства пользователя, но окончательную проверку типа, размера и содержимого файла всегда выполняйте на сервере.

### Favicon и предпросмотр вкладки

Для настройки favicon используйте тот же загрузчик изображения, но ограничьте форматы значениями PNG, ICO и SVG. Рекомендуемый исходный размер — `32×32 px`; в макете вкладки изображение всегда показывается как `16×16 px` через `object-fit: contain`.

Если экрану нужен предпросмотр, форма и превью образуют две равные колонки:

- максимальная общая ширина — 1040 px;
- расстояние между колонками — 80 px, а при ширине экрана до 1000 px — 40 px;
- ширина каждой колонки не превышает 480 px;
- при ширине до 800 px колонки располагаются одна под другой;
- подпись «Предпросмотр» использует тот же стиль, что и подписи полей: Gilroy 13/17 px, weight 700.

Название вкладки синхронизируется со значением META Title, если оно заполнено, иначе — со значением поля «Название сайта». Для обоих пустых полей используйте нейтральный текст «Новая вкладка». Пока favicon не выбран, показывайте стандартный значок глобуса; после выбора заменяйте его изображением без изменения геометрии вкладки. При удалении файла возвращайте значок глобуса.

```html
<div class="gmb-settings-layout">
  <form class="gmb-form-column">
    <!-- поля и загрузчик favicon -->
  </form>

  <aside class="gmb-browser-preview">
    <h3 class="gmb-browser-preview__label">Предпросмотр</h3>
    <!-- Chrome-подобная вкладка -->
  </aside>
</div>
```

### SEO-настройки

Внутри самостоятельной страницы iframe используйте обычные поля формы без дополнительного уровня навигации:

- META Title и META Description — стандартные однострочные `.gmb-input`;
- JS-скрипты — многострочное поле `.gmb-textarea`;
- ширина SEO-формы — до 480 px, как у остальных настроек;
- постоянная подпись располагается над каждым полем;
- пояснение располагается под полем и использует Arial 12/14 px, weight 400, `#999999`;
- многострочное поле имеет минимальную высоту 120 px и может растягиваться только вертикально.

Тексты SEO-настроек из GetMeBack:

| Настройка | Подсказка / ограничение |
|---|---|
| Заголовок страницы (META Title) | Отображается в заголовке вкладки. Рекомендуется использовать до 60 символов. |
| Описание сайта (META Description) | Отображается, когда ссылка на страницу отправляется в мессенджерах или социальных сетях. Должно быть привлекательным и содержать ключевые моменты. Оптимальная длина — до 160 символов. |
| JS скрипты(счётчики и т.п.) | Многострочное поле с подписью «Значение». |

Для META Title задавайте `maxlength="60"`, для META Description — `maxlength="160"`. Значение META Title сразу обновляет заголовок браузерного предпросмотра. Введённые JS-скрипты храните как обычный текст: не запускайте их внутри страницы настроек или предпросмотра.

```html
<div class="gmb-field">
  <label class="gmb-field__label" for="meta-title">
    Заголовок страницы (META Title)
  </label>
  <input class="gmb-input" id="meta-title" type="text" maxlength="60">
  <p class="gmb-field__hint">
    Отображается в заголовке вкладки. Рекомендуется использовать до 60 символов.
  </p>
</div>

<div class="gmb-field">
  <label class="gmb-field__label" for="seo-scripts">
    JS скрипты(счётчики и т.п.)
  </label>
  <textarea class="gmb-textarea" id="seo-scripts" rows="6"></textarea>
</div>
```

### Основная кнопка

- Высота: 36 px.
- Отступ слева/справа: 16 px.
- Фон: `#26B99A`, текст белый, радиус 4 px.
- Disabled: `rgba(0,0,0,.12)` и текст `rgba(0,0,0,.26)`.
- После отправки блокируйте повторный клик и показывайте состояние «Сохраняем…».

### Кнопка назад

Это текстовая кнопка, а не самостоятельная навигационная панель. Иконка 16 px, промежуток до текста 8 px. В iframe удобнее вызвать `history.back()`, а если истории нет — отправить родителю событие `gmb:back`.

### Ссылка

- Цвет `#26B99A`, при наведении `#169F85` — те же значения, что у акцента.
- Без подчёркивания в покое, подчёркивание появляется при наведении: в плотном тексте подсказок постоянная линия рябит, а цвета для различения достаточно.
- Отдельного класса нет, правило применяется к `a` внутри набора.
- Токены `--gmb-color-link` и `--gmb-color-link-hover` заданы отдельно от акцента: ссылку в тексте может понадобиться отличить от кнопки, не перекрашивая кнопки.

Ссылки уместны в подсказках полей и описаниях секций. Действие, которое меняет данные, оформляйте кнопкой, а не ссылкой, — иначе непонятно, что произойдёт по нажатию.

## 6. Состояния формы

- Сразу после загрузки «Сохранить» выключена.
- Любое реальное изменение включает кнопку.
- Успешное сохранение снова выключает кнопку и показывает короткое сообщение `role="status"`.
- Ошибка валидации показывается рядом с конкретным полем; фокус переводится на первое ошибочное поле.
- Не используйте только цвет для передачи ошибки или успеха.
- Состояние свитча должно быть видно и по положению бегунка.

## 7. Адаптивность

- До 800 px форма и правый предпросмотр становятся одной колонкой.
- До 500 px форма и кнопки занимают доступную ширину.
- На малой ширине внешний отступ уменьшается с 20 до 12 px.
- Заголовок страницы уменьшается до 28/34 px, чтобы не создавать лишний горизонтальный скролл.
- Минимальная удобная высота интерактивной зоны — 36 px; вокруг маленького свитча кликабельна вся строка.
- Страница iframe не должна иметь горизонтальной прокрутки.

## 8. Доступность

- У каждого `input` есть связанный `label`.
- У страницы один `h1`, заголовки секций идут как `h2`.
- Фокус видим: используется зелёное кольцо с достаточным отступом.
- Сообщения сохранения имеют `role="status"` и `aria-live="polite"`.
- Кнопка назад и сохранение — настоящие `<button>`.
- Для декоративных SVG используйте `aria-hidden="true"`.
- У поискового селекта есть `role="combobox"`, связанный `role="listbox"` и
  клавиатурное управление стрелками, Enter и Escape.

## 9. Что не копировать из исходной админки

- Хэшированные классы наподобие `title-YDXrwe`: они меняются между сборками.
- Весь Vuetify bundle: для двух-трёх контролов это большой и ненужный runtime.
- CSS меню, верхней панели, таблиц и чата поддержки: они находятся вне области iframe.
- Глобальные reset-стили без пространства имён, способные сломать содержимое модуля.

Все классы набора начинаются с `gmb-`, поэтому их безопасно переносить в существующий проект.

## 10. Страница со списком и модальной карточкой

Готовый рабочий шаблон находится в [`example/list.html`](./example/list.html). Внешний вид списка повторяет страницу «Опросы», а карточка редактирования — паттерн «Геоуведомлений»: хлебные крошки, счётчик записей, фильтр, поиск, табличный список и форма поверх страницы.

### Каркас страницы списка

Порядок элементов:

1. Хлебные крошки Arial 13/17 px с вертикальным отступом 8 px; ссылка акцентная, остальные сегменты и разделители серые.
2. Заголовок страницы и primary-кнопка добавления на одной строке.
3. Короткое описание, если без него назначение списка неочевидно.
4. Строка `Всего: N записей` вторичным текстом.
5. Белый контейнер с фильтрами, поиском и таблицей.
6. Пустое состояние внутри того же контейнера.

Правила таблицы:

- контейнер — белый, граница `#E1E6EA`, радиус 8 px;
- панель фильтров имеет высоту не менее 58 px и внутренний отступ 12–14 px;
- свитч находится слева, поиск — справа на той же строке;
- поиск — поле `230 × 28 px`; на узком экране оно переносится на отдельную строку;
- справа используется оригинальная Material Design Icons `mdi-magnify` размером 22 px, цвет `#777777`;
- шапка таблицы — высота 36 px, фон `#F7F7F7`, Gilroy 13 px, weight 700;
- строка данных с одним значением — 52 px;
- горизонтальный отступ ячейки — 12 px;
- строки разделяются линией `#E8ECEF`;
- значения строк — Arial 13/18 px, weight 400, `#333333`;
- булево состояние в таблице показывается зелёной галочкой и текстом «Да» или красным крестиком и текстом «Нет»; свитч оставляйте для формы редактирования;
- на узкой ширине горизонтально прокручивается только `.gmb-table-scroll`, а не вся страница iframe.

В строке счётчика `Всего:` и форма слова «записи» используют Arial 13 px,
weight 400, `#999999`; само число — то же начертание с weight 700.

Строку можно открыть кликом мыши в свободной области. Для клавиатуры главное значение строки должно оставаться настоящей кнопкой `.gmb-table__primary-action`. Свитч и кнопка действий работают независимо и не должны открывать карточку случайно.

```html
<section class="gmb-list-card">
  <div class="gmb-list-toolbar">
    <!-- фильтр и поиск -->
  </div>
  <div class="gmb-table-scroll">
    <table class="gmb-table">
      <!-- thead и tbody -->
    </table>
  </div>
</section>
```

### Модальная карточка

Карточка редактирования повторяет поведение GetMeBack:

- затемнение страницы — `rgba(0,0,0,.46)`;
- максимальная ширина карточки — 600 px;
- белый фон, радиус 8 px и выраженная тень;
- высота шапки — не менее 56 px;
- заголовок — Gilroy 20/24 px, weight 700;
- крестик находится справа и имеет кликабельную область 32×32 px;
- внутренний отступ содержимого — 20 px;
- нижняя панель высотой не менее 60 px, действия выровнены вправо;
- primary-действие находится крайним справа;
- карточка закрывается крестиком, кнопкой «Отмена», кликом по затемнению и клавишей Escape;
- после закрытия фокус возвращается элементу, который открыл карточку;
- введённые значения не должны попадать в HTML через `innerHTML`: используйте `textContent` или безопасный рендеринг фреймворка.

```html
<div class="gmb-modal-layer" hidden>
  <section class="gmb-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
    <header class="gmb-modal__header">...</header>
    <div class="gmb-modal__body">...</div>
    <footer class="gmb-modal__footer">...</footer>
  </section>
</div>
```

При открытой карточке блокируйте прокрутку страницы классом `.gmb-modal-open`. Если форма длиннее окна, прокручивается сама карточка, а не затемнённый фон.

## 11. Чек-лист нового экрана

- [ ] Фон `#F7F7F7`, отсутствуют собственные меню и шапка.
- [ ] Один `h1`, размер 34/40 px.
- [ ] Форма не шире 480 px, если поля не требуют большей ширины.
- [ ] Все отступы кратны 4 px.
- [ ] Поля высотой 36 px, радиус 8 px.
- [ ] Выпадающие справочники используют поисковый combobox; меню равно ширине
  поля, а сохранённое значение меняется только после выбора варианта.
- [ ] Свитчи 33×20 px, включённый цвет `#26B99A`.
- [ ] Радиокнопки 18×18 px, выбранное состояние `#26B99A`, вся строка кликабельна.
- [ ] Подстрочный текст радиоварианта выровнен по началу основной подписи.
- [ ] Загрузчик изображения имеет пустое состояние, предпросмотр, удаление и сообщение об ошибке.
- [ ] Favicon отображается во вкладке как `16×16 px`, а название вкладки синхронизировано с названием сайта.
- [ ] META Title и META Description используют стандартные однострочные поля.
- [ ] JS-скрипты используют многострочное поле высотой не менее 120 px.
- [ ] META Title ограничен 60 символами, META Description — 160 символами.
- [ ] «Сохранить» выключена до изменения данных.
- [ ] Есть hover, focus, disabled, error и loading состояния.
- [ ] Нет горизонтальной прокрутки при ширине 320 px.
- [ ] Родитель проверяет origin сообщений iframe.
- [ ] Таблица находится в белом контейнере; на мобильном прокручивается только её обёртка.
- [ ] В панели списка фильтр и поиск стоят на одной строке; поиск имеет высоту 28 px и использует `mdi-magnify`.
- [ ] Текст строк таблицы — Arial 13 px, без принудительного Gilroy.
- [ ] У списка есть поиск, состояние «ничего не найдено» и актуальный счётчик строк.
- [ ] Модальная карточка имеет `role="dialog"`, `aria-modal="true"`, закрывается Escape и возвращает фокус.
