# QA дизайна CALL UI — AGIL Calls (live)

**Дата:** 13.11.2026, 20:20–21:05 MSK  
**Объект:** https://calls.guardify.ru (embed.html, живая комната, реальные presence-участники)  
**Сборка на момент финальных кадров:** `embed-DwXbjbYQ.js` · `index-DU0kJMK6.js` · `index-CAfFGbpL.css`  
**Проверено:** 1 / 9 / 25 / 50 участников · 563×545, 390×844, 768×1024, 1440×900 · light/dark · панель участников · меню · поднятая рука · ассистент · уведомления.  
**Артефакты:** `qa/call-design/screens/**` — 100 PNG (86 исходных + 14 аннотированных), машинные метрики — `qa/call-design/data/*.json`.

---

## 1. Вердикт

**Не готово к дизайн-приёмке.** В текущем виде CALL UI ломается именно там, где заказчик ждал качества — на многих участниках и на телефоне.

| Уровень | Кол-во | Суть |
|---|---|---|
| **P0** | 2 | Сетка теряет участников и делает верхние ряды недоступными; на 390 px кнопка «Выйти» невидима |
| **P1** | 5 | Видео не заполняет сцену; перегруженный top bar; split-кнопка собрана зеркально; панель участников недоступна на 563 px; fallback-тост с JWT ломает ширину |
| **P2** | 3 | 14 контролов в 2 ряда; подписи/бейджи конфликтуют на плитке 213×120; spotlight ассистента показывает мимо кнопки |
| **P3** | 3 | Нечётный последний ряд не центрирован; рука — полировка контраста/виньетки; технические пилюли в шапке |

**Что уже хорошо:** поднятая рука в финальной сборке получила тёплую золотую рамку, свечение и виньетку (запрос закрыт, нужна полировка); CLS уведомлений = **0,000**; тап-таргеты 48 px на desktop; desktop-панель участников аккуратная; тёмная тема консистентна.

**Главная причина дефектов:** не «косметика», а формула сетки: `auto-fit + 16:9 + min-height:120px + align-content:center + overflow:auto`. Когда контент выше сцены, дорожки сжимаются до 120 px, а плитки остаются 154–206 px и наезжают друг на друга; `align-content:center` уводит первый ряд в отрицательные координаты, куда нельзя доскроллить.

---

## 2. Как проверяли

- Создана реальная комната через `POST /v1/rooms` (`tenant=demo`), браузер вошёл как **Designer**.
- Для 9/25/50 участников добавлены WebSocket-presence через admin-invite (камера/микрофон off): это обходит публичный лимит 10 join/мин на IP+комнату и не создаёт WebRTC-нагрузку. Все плитки — штатные placeholder’ы, как в реальном аудио-звонке.
- Поднятая рука: `handRaised` через WS у удалённого участника и кнопка/ассистент у себя.
- CLS: `PerformanceObserver('layout-shift')`, `buffered:false`, до/после тоста; bbox footer до/после.
- Скриншоты: полный кадр iframe в реальном размере, DPR 1; для чистых кадров уведомления закрывались кликом (штатное поведение), не CSS-инъекцией.
- Продуктовый код не менялся; изменены только `qa/call-design/**` и этот отчёт.

Ограничения: presence-участники без медиа (камера off) — это худший и самый частый случай для 25–50 человек; WebRTC-качество/кодеки не оценивались. В синтетическом звонке индикатор соединения может показывать «Подключение…», пока не установятся peer-connection; на layout это не влияет. В процессе аудита прод обновился (появились hand-стили и split-CSS) — все финальные кадры сняты на сборке выше, старые отброшены.

---

## 3. Дефекты

### Ключевые кадры

![Top bar и пустая сцена](../qa/call-design/screens/annotated/01-topbar-video-controls-1440-light.png)

![50 участников: перекрытие и недоступный верх](../qa/call-design/screens/annotated/02-grid-overflow-50-1440-light.png)

![Невидимая кнопка «Выйти» на 390](../qa/call-design/screens/annotated/04-mobile-invisible-leave.png)

![Поднятая рука: золотая рамка и виньетка](../qa/call-design/screens/annotated/07-hand-raised-tokens.png)

![Split-кнопка: зеркальный порядок и зазор](../qa/call-design/screens/annotated/12-split-button-anatomy.png)


### P0-01. Сетка теряет участников: перекрытие рядов и недоступный верх

**Где:** 50/1440, 9/563, 9/768, 25/390, 50/390.  
**Факт (scrollTop = 0):**

| Сценарий | Первая плитка, y | Pitch ряда | Высота плитки | Перекрытие |
|---|---:|---:|---:|---:|
| 50 / 1440×900 | **−204,34 px** | 131,18 | 154,08 | **22,9 px** |
| 50 / 563×545 | **−1327,37 px** | 126,00 | 153,28 | **27,28 px** |
| 9 / 563×545 | **−67,37 px** | 126,00 | 153,28 | **27,28 px** |
| 9 / 768×1024 | 76,17 px | 163,36 | 205,64 | **42,28 px** |
| 25 / 390×844 | **−399,72 px** | 126,00 | 120,00 | 0 (но верх недоступен) |
| 50 / 390×844 | **−1155,72 px** | 126,00 | 120,00 | 0 (но верх недоступен) |

- 50 участников: 10 рядов, контент ≈1650 px в сцене 765 px; одновременно видны ~5,5 ряда. Верхние 10 участников нельзя увидеть даже при `scrollTop=0` — скролл не имеет отрицательного диапазона.
- 9 участников на обязательном 563×545: первый ряд (Designer/Анна) скрыт над сценой, подписи срезаны следующей плиткой.
- 768×1024: 2 колонки × 5 рядов, но плитка 365,6×205,6 при pitch 163,4 — наезд 42,3 px; у большинства плиток подписи полностью скрыты.
- 25/390: 13 рядов, первый ряд y=−399,7; 50/390: 25 рядов, первый y=−1155,7.

**Причина:** grid-дорожка = `max(120px, свободная высота / ряды)`, а плитка сохраняет `aspect-ratio: 16/9`; при нехватке высоты плитка выше дорожки и перекрывает следующую. `align-content:center` при overflow ставит контент выше scroll-origin.  
**Скриншоты:** `02-grid-overflow-50-1440-light.png`, `03-563-first-row-hidden.png`, `06-768-header-overflow.png`, `13-50-mobile.png`.

---

### P0-02. На 390 px кнопка «Выйти» невидима

**Где:** island ≤480 px, в т.ч. 390×844.  
**Факт:** leave — 40×40 px, x=243,36, между chat и «⋯». Фон прозрачный (island-правило перебивает `.is-danger`), иконка — `color: var(--agil-color-on-primary)` = белая. На белой пилюле видимого контраста **0**: в баре остаётся пустое место 40 px. Кнопка кликабельна, но affordance отсутствует.

**Скриншот:** `04-mobile-invisible-leave.png` (красный пунктир — место, где должна быть кнопка).

---

### P1-03. Видео не заполняет интерфейс: 66% сцены пустует

**Факт:**

| Сценарий | Плитки | Площадь плиток / grid | Доля viewport | Пусто |
|---|---|---:|---:|---:|
| 9 / 1440×900 | 5×2 | **34,5%** | 29,3% | 222 px сверху + 222 px снизу |
| 1 / 1440×900 | 1, max-width 960 | 66,8% | 37,9% | 225 px сверху/снизу + 240 px по бокам |
| 1 / 390×844 | 1, 378×213 | **28,0%** | 28,0% | 516 px по высоте |
| 25 / 1440×900 | 5×5 | 95,8% | — | хорошо, но 3,9 px наезд |
| 50 / 1440×900 | 5×10 | 191,5% | — | скролл 302 px + верх недоступен |
| 9 / 563×545 | 2×5 | 173% | — | скролл 152 px + верх скрыт |

- 9 участников на 1440 — это **5×2**, а не 3×3: последний ряд 4 из 5, справа дырка **285 px**.
- 1:1 на 390 — это 16:9-плитка 378×213 в сцене 729 px: 72% высоты пусто. «Видео на весь интерфейс» не выполняется даже для одного участника.
- Header + footer = 134,8 px = 15% высоты 900; это нормально, но видео внутри оставшихся 765 px всё равно не растягивается.
**Скриншоты:** `01-topbar-video-controls-1440-light.png`, `05-1to1-does-not-fill.png`.

---

### P1-04. Top bar перегружен, дублирует бренд/название и обрезается на 768

**Факт (1440×900):** высота **63,38 px**; **12 блоков / 15 видимых элементов**:

1. логотип 34,6 + «AGIL Calls» + org-строка с названием комнаты (224,6 px);
2. название комнаты + таймер (233,6 px);
3. `mesh` pill 50,5×27,4;
4. «Участников: 9» 97,8×27,4;
5. «Подключено» 93,2×27,4;
6. quality-dot 30×18 (без видимой подписи);
7. 5 иконок по 40 px: participants, notes, transcript, copy, theme;
8. security badge 104×29,8 с текстом **«AGIL Calls»** (дубль бренда при отсутствии E2EE/пароля/integrity).

- Между левым блоком (до 495 px) и статусом (с 776 px) — **282 px пустоты**.
- На **768×1024** правые элементы выходят за край: security right=**859**, copy=**907**, theme=**955**; `overflow:hidden` обрезает 3 элемента. Медиазапрос, который их прячет, начинается только с 720 px — на планшете шапка сломана.
- На 563/390 высота 51,55 px, 4 блока — терпимо, но название на 390 обрезается до «Дизайн-ауди…».
**Скриншоты:** `01-topbar-video-controls-1440-light.png`, `06-768-header-overflow.png`.

---

### P1-05. Split-кнопка камеры собрана зеркально и не является группой

**Факт:**

- DOM/визуальный порядок: `mic → chevron → camera`. Chevron стоит **перед** камерой, хотя должен быть её маленькой правой частью.
- chevron: **32×48**, радиус `0 50% 50% 0` (как правая половина);
- camera: **48×48**, радиус `50% 0 0 50%` (как левая половина);
- зазор **10 px** (на 390 — 6 px); общий размер 90 px вместо 72–76; разделитель не между частями; общей рамки/фокуса нет.

Это ровно то, что просил заказчик — «большая + маленькая стрелка вплотную», — но собранное наоборот.  
**Скриншот:** `12-split-button-anatomy.png` (close-up ×4).

---

### P1-06. Панель участников недоступна на 481–720 px (в т.ч. 563×545)

**Факт:** при `max-width:720` шапка скрывает participants/notes/transcript; в `bar` эти кнопки исключены (`BAR_EXCLUDED`), а «⋯» появляется только в island ≤480. На 563 px открыть панель из UI **нельзя**; принудительный клик по скрытой кнопке открывает её на всю ширину 563 px. На 390 px панель доступна через «⋯» и ведёт себя правильно (full-width drawer); на 768 px — через шапку.

**Скриншоты:** `03-563-first-row-hidden.png` (пункт 4), `08-participants-panel-360.png`.

---

### P1-07. Fallback «Скопировать ссылку» показывает JWT в тосте шириной 2283 px

**Факт:** при запрете clipboard (headless, iframe без permissions, Safari) вместо короткого сообщения показывается полный URL:

- тост: **2283,5×78,5 px**, right=**2299,5** при viewport 1440 — горизонтальный overflow на 860 px, текст обрезан;
- в тексте читается JWT (sub, roomId, tenantId, iat/exp) — секрет сессии уходит в UI и скриншоты (в кадрах ниже JWT отредактирован);
- CLS при этом **0,000** (footer top 828,609 → 828,609), то есть проблема не в CLS, а в контенте fallback.

**Скриншоты:** `10-notification-cls.png`, `11-toast-token-overflow.png`.

---

### P2-08. 14 контролов, 2 ряда и «сиротская» кнопка выхода

- Desktop: 14 кнопок в 1 ряд, 48 px, gap 10; footer 71,4 px — приемлемо, но 14 — много.
- 768×1024: 13 кнопок в первом ряду, **leave один во втором**; footer **129,4 px**, справа пустая полоса.
- 563×545: 2 ряда 10+4; footer **107,3 px** = **19,7%** высоты 545.
- 390: 6 видимых + невидимый leave; footer 63 px.

**Скриншоты:** `03-563-first-row-hidden.png`, `06-768-header-overflow.png`.

---

### P2-09. На 390 px подписи и бейджи не помещаются в оверлей

Плитка **213,3×120**; нижний оверлей **41,4 px**; внутренняя ширина 193,3 px. Info max-width 162 px + 3 бейджа по 28 px с gap 4 = **92 px** → 254 px при доступных 193 px: имя/роль/страна уходят под mic/cam/hand. На скриншоте «Борис 2», «Глеб 4» перекрыты красным бейджем микрофона, страна не читается.  
**Скриншот:** `04-mobile-invisible-leave.png` (пункт 2).

---

### P2-10. Spotlight ассистента показывает мимо кнопки

Запрос «подними руку» **выполняется** (рука поднята, кнопка active), но spotlight-кольцо стоит в пустой области видео: ring **680×410, 80×80**, кнопка hand **595,2×839,2, 48×48** → **Δx=84,8, Δy=429,2 px**. Карточка подсказки 360×121 в (540, 502) перекрывает 2 центральные плитки.  
**Скриншот:** `09-assistant-spotlight-mismatch.png`.

---

### P3-11. Нечётный последний ряд не центрирован

9 участников: 5×2 → последний ряд 4 плитки слева, справа **285 px**; на 390 — 2×5 → последняя плитка одна слева. Для нечётного количества нужен `justify-content:center` на последней строке или смена колонок (3×3).

### P3-12. Поднятая рука: запрос закрыт, нужна полировка

Сейчас: border 1 px + inset 3 px **#FFB020** (эффективно 4 px), glow `0 0 36px rgba(255,176,32,.55)`, виньетка `radial-gradient(transparent 48%, rgba(255,176,32,.32) 100%)`, имя **#FFD27A/700**, badge **28×20 #FFB020 / #3A2400**, title «Рука поднята». Визуально это тёплая золотая рамка + свечение + виньетка — требование выполнено. Полировка: контраст рамки на белом ≈**1,83:1**, виньетка 0,32 субъективно слабая; badge 28×20 мелкий.

### P3-13. Технические/дублирующие элементы в шапке

`mesh` — технический термин; quality-dot 30×18 без подписи; security badge «AGIL Calls» дублирует бренд. Всё это должно уйти в stats/settings или появляться только при отклонении.

---

## 4. Замеры

### 4.1 Top bar

| Viewport | Высота | Блоков | Элементов | Overflow |
|---|---:|---:|---:|---|
| 1440×900 | **63,38** | 12 | 15 | нет, 282 px пустоты |
| 768×1024 | **63,38** | 12 | 15 | **3 элемента за краем** |
| 563×545 | 51,55 | 4 | 5 | нет |
| 390×844 | 51,55 | 4 | 5 | нет, title обрезан |

Padding: 11,2 px / 16 px; gap 12 px; status gap 8 px. Кнопки шапки 40×40.  
Рекомендация: **48 px / 44 px**, ≤7 элементов, без обрезки на 768.

### 4.2 Видео

| Сценарий | Grid, px | Плитка, px | Fill | Scroll | First y | Pitch / overlap |
|---|---|---:|---:|---:|---:|---:|
| 1 / 1440 | 960×765 | 934,4×525,6 | 66,8% grid / 37,9% viewport | 0 | 183,2 | — |
| 9 / 1440 | 1440×765 | 273,9×154,1 | **34,5%** | 0 | 286,3 | 165,3 / 0 |
| 25 / 1440 | 1440×765 | 273,9×154,1 | 95,8% | 3 | 76,2 | 150,2 / **3,9** |
| 50 / 1440 | 1440×765 | 273,9×154,1 | 191,5% | 302 | **−204,3** | 131,2 / **22,9** |
| 9 / 768 | 768×831 | 365,6×205,6 | 106% | 41 | 76,2 | 163,4 / **42,3** |
| 9 / 563 | 563×386 | 272,5×153,3 | 173% | 152 | **−67,4** | 126 / **27,3** |
| 9 / 390 | 390×729 | 213,3×120 | 81% | 0 | 104,3 | 126 / 0 |
| 25 / 390 | 390×729 | 213,3×120 | 225% | 458 | **−399,7** | 126 / 0 |
| 50 / 390 | 390×729 | 213,3×120 | 450% | 1214 | **−1155,7** | 126 / 0 |
| 1 / 390 | 390×729 | 378×212,6 | **28%** | 0 | 310 | — |

Chrome (header+footer) = **15%** высоты 1440; panel 360 px = 25% ширины, grid 1080, fill 72% (9 участников, 3×3).

### 4.3 Контролы

- Desktop: 14 кнопок, 48×48, gap 10; leave 58,55×48; группа центрирована (322,2…1117,5, центр 719,8 при 720).
- 768: 13+leave, 2 ряда, footer 129,4; leave один во втором.
- 563: 10+4, footer 107,3 (19,7%).
- 390 island: 6 видимых по 40 px, gap 6; leave 40×40 невидим; контейнер 374×54, x=8, y=782.
- Split: 32 + 48 + gap 10 = **90 px**; target 44 + 30 + 0 = **74 px**.
- Видео-меню 280 px (max-height 480), открывается вверх; на 390 — bottom sheet. More-меню island — 300 px max, 7–12 пунктов. Оба absolute, layout не смещают; см. `menu-video-*`, `menu-more-*`.

### 4.4 Плитки и симметрия

- Aspect 16:9 = **1,7778** на 1440/768/390; при 50/1440 — 154,08 px высоты.
- Grid padding 12,8 px со всех сторон; gap 11,2 px (6 px ≤480), колонки равные.
- 9/1440: последний ряд 4/5; 9/390: 1/2 в последнем ряду — асимметрия.
- 25/1440: 5×5, почти заполнено, но 3,9 px наезд подписей.
- Header padding 16/16 — симметрично; левый кластер 495 px, правый 648 px, зазор 282 px — визуальный дисбаланс.

### 4.5 Поднятая рука

| Токен | Значение |
|---|---|
| frame | `border: 1px solid #FFB020; box-shadow: inset 0 0 0 3px #FFB020` (эффективно 4 px) |
| glow | `0 0 36px rgba(255,176,32,0.55)` |
| vignette | `radial-gradient(ellipse at center, transparent 48%, rgba(255,176,32,0.32) 100%)` |
| name | `#FFD27A`, 700 |
| badge | 28×20, bg `#FFB020`, ink `#3A2400` |
| self control | bg `#FFB020`, glow `0 0 18px rgba(255,176,32,.45)` |
| contrast on white | **1,83:1** (нужен тёмный контур 1 px) |

### 4.6 Overlap/clipping

- Grid: 22,9 px (50/1440), 27,3 px (9/563), 42,3 px (9/768), 3,9 px (25/1440).
- Первый ряд: −204,3 / −67,4 / −399,7 / −1155,7 px.
- 768 header: 3 элемента за краем (right 859/907/955).
- 390 labels: 254 px контента в 193 px оверлея → 61 px overlap.
- Toast: 2283,5 px при 1440 → 860 px overflow.
- Footer/controls при тосте не смещаются.

### 4.7 CLS

**0,000** (нормальный тост и fallback): footer top 828,609 до/после; контролы не двигаются; тост absolute, top 75,6 px. Проблема уведомлений — контент fallback и перекрытие видео, не layout shift.

---

## 5. Спека

### 5.1 Верхняя панель: 48 / 44 px, 3 зоны, 7 элементов

```
[logo 28] [Комната 14/600 ……… 00:24]        [👥 9] [•] [⋯ 40]
```

- Высота **48 px** desktop, **44 px** ≤720; padding 12 / 10; gap 8; border-bottom 1 px divider; background `surface 72%` + blur 12.
- Оставить: логотип, название комнаты (ellipsis), таймер 12 px mono, счётчик 12 px, точка соединения 8 px (текст только при degraded/offline), «⋯» 40 px.
- Убрать/сгруппировать:
  - org-строку, если она равна названию комнаты;
  - security badge, если нет E2EE/пароля/integrity/geo/audit;
  - `mesh` → stats/settings (показывать только broadcast/sfu);
  - quality-dot → в настройки/по ховеру connection;
  - notes/transcript/copy/theme → в «⋯»;
  - participants-icon → счётчик открывает панель;
  - reactions/assistant/pip/fullscreen/newtab/minimize/settings → в overflow контролов.
- На 768 не прятать «⋯»: шапка не должна обрезать элементы; max-width 100%, `flex-wrap:nowrap`, title `min-width:0`, status `flex:none`.
- Итог: было 15 элементов / 63,4 px → **7 / 48 px**; на мобиле 5 / 44 px.

### 5.2 Видео: full-bleed + автоподбор сетки

- Сцена — весь виджет: header/footer плавают поверх (absolute), видео уходит под них; safe-area 56 px сверху / 80 px снизу для контента.
- `main` absolute inset 0; фон сцены — размытый cover активного спикера/self; плитки сверху.
- Сетка:
  - `display:grid`, `grid-template-columns: repeat(var(--cols), minmax(0,1fr))`, `grid-auto-rows: minmax(0,1fr)`, `align-content:stretch`, `overflow:hidden`; у плитки `aspect-ratio:auto; height:100%; min-height:0`.
  - `cols = clamp(1, round(sqrt(n * (W/H) / (16/9))), n)`, `rows = ceil(n/cols)`; затем проверить `tileH = (H - 2*pad - (rows-1)*gap)/rows`; если `tileH < 120` — compact-режим (name only, бейджи 16 px, overlay 28 px); если `< 96` — пагинация.
  - **Запрещено:** `align-content:center` при overflow, `min-height:120` на плитке без синхронизации с дорожкой, `aspect-ratio` при `height:auto` в constrained grid.
- Ориентиры:

| Viewport | 1 | 9 | 25 | 50 |
|---|---|---|---|---|
| 1440×900 | 1, cover | **3×3** (≈443×249) | 5×5 (≈263×148) | 8×7 compact (≈158×89) или 2 страницы по 25 |
| 768×1024 | 1, cover | 2×5 (≈284×159) | 4×7 compact (≈180×101) или страницы | страницы по 25 |
| 563×545 | 1, cover | **3×3 compact** (≈173×97) | страницы по 9 | страницы по 9 |
| 390×844 | 1, cover | 2×5 (≈182×102) | страницы по 9 | страницы по 9 |

- 1:1 на телефоне — не letterbox: `object-fit:cover` на всю сцену (кроп по бокам) или spotlight + self-PiP.
- Для >25 на 1440 — пагинация/виртуализация с индикатором «1–25 из 50», а не бесконечный скролл с недоступным верхом. Для 25–50 — сортировка: поднятая рука → спикер → остальные.

### 5.3 Split-кнопка: 44 + 28–32, вплотную

```
[  main 44  ][ chevron 30 ]   = 74 × 44
 radius 22 0 0 22 | 0 22 22 0
 divider 1 px между половинами
```
- Порядок в DOM и визуале: **main → chevron**.
- Main 44×44, chevron 30×44 (допустимо 28–32), gap **0**, общая рамка 1 px, `overflow:hidden`, `border-radius` группы 22.
- Разделитель: `1px solid rgba(255,255,255,.14)` в тёмной / `rgba(15,23,42,.12)` в светлой; он между частями, не слева от chevron.
- Hover/active — только у наведённой половины; focus-ring — вокруг всей группы; `aria-haspopup`, `aria-expanded` на chevron; иконка 14 px.
- Группы: mic + устройства, camera + видео-меню. Leave и screen — без split.

### 5.4 Поднятая рука: токены

```css
--hand: #FFB020;
--hand-ink: #3A2400;
--hand-name: #FFD27A;
/* tile */
border: 1px solid var(--hand);
box-shadow:
  inset 0 0 0 4px var(--hand),
  0 0 0 1px rgba(58, 36, 0, .35),      /* контур для светлой темы */
  0 0 40px rgba(255, 176, 32, .60);
/* ::after */
background: radial-gradient(ellipse at center,
  transparent 42%, rgba(255, 176, 32, .40) 100%);
/* badge 28×20, radius 6, bg #FFB020, color #3A2400, weight 700 */
```
- Имя — `#FFD27A`, 700; badge title/aria «Рука поднята».
- Сортировка: поднятая рука первой в grid и в панели; в панели — badge + «Поднята рука».
- Анимация — только при первом появлении (200 ms), без постоянного пульса.
- Контраст #FFB020 к белому 1,83:1 — декоративную рамку это не спасает; тёмный 1 px контур обязателен.

### 5.5 Spacing-токены

| Токен | Desktop | Mobile |
|---|---:|---:|
| topbar height | 48 | 44 |
| topbar padding-x | 12 | 10 |
| topbar gap | 8 | 6 |
| control | 44 | 40 |
| control gap | 8 | 6 |
| split chevron | 30 | 28 |
| footer height | 64 | 60 |
| stage padding | 8 | 6 |
| tile gap | 8 | 6 |
| radius tile | 12 | 12 |
| radius group | 22 | 22 |
| panel width | 360 | 100% |
| toast max-width | 340 | 340 |

Базовые `4 / 8 / 12 / 16 / 24`; никаких дробных 11,2 / 9,6 — округлять до целых.

### 5.6 Что удалить / сгруппировать

| Сейчас | Действие |
|---|---|
| org-строка = название комнаты | удалить (дубль) |
| security badge «AGIL Calls» | скрыть без флагов; оставить E2EE/🔒 |
| `mesh` pill | stats/settings; показывать только broadcast/sfu |
| quality-dot | в настройки; в шапке — только при poor/critical |
| participants/notes/transcript/copy/theme icons | participants — счётчик; остальное в «⋯» |
| 14 контролов | 6 primary (mic split, camera split, screen, chat, hand, leave) + «⋯» |
| reactions/assistant/pip/fullscreen/newtab/minimize/settings | overflow |
| 5×2 для 9 | 3×3 |
| scrolling grid для 25/50 | пагинация/виртуализация |

### 5.7 Адаптив

- ≥1280: top bar 48, 6 контролов + overflow, grid 3×3 / 5×5 / 8×7, panel 360.
- 721–1279: top bar 48, title ellipsis, 6 контролов, grid по формуле; panel 360.
- 481–720: top bar 44, island? **нет** — bar с 6 контролами + «⋯»; panel доступна из «⋯» или из счётчика (обязательно).
- ≤480: top bar 44; island 6 контролов: mic, camera split, screen, chat, **leave (видимый, 44, red)**, «⋯»; panel full-width drawer.
- Все кнопки ≥44×44 (мобильные 40 — только если вся группа 44 и есть padding).

### 5.8 Уведомления

- CLS уже 0 — сохранить absolute/toast вне layout.
- Max-width **340 px**, `overflow-wrap:anywhere`, max-height 120, 2 шт.
- Никогда не показывать URL/токен: fallback «Не удалось скопировать. Ссылка — в меню «Пригласить»».
- Позиция: top-left под шапкой (top = header+12), не перекрывать контролы; на 390 — full-width минус 12 px.
- Тост не должен перекрывать нижние 2 плитки: при 563/390 — над контролами или в шторке.

### 5.9 Ассистент

- Spotlight считать по `getBoundingClientRect()` целевой кнопки **после** reflow и с учётом `transform` родителя; проверять `Math.abs(ring - target) < 4 px`.
- Если действие выполнено (hand active) — кольцо не нужно; показывать «Готово» + подсветку только когда действие требует клика.
- Карточка подсказки — 320 px, не перекрывать центральные плитки; на 390 — bottom sheet.

---

## 6. Сравнение с Meet / Zoom / Teams — только принципы

- **Google Meet:** верхняя строка минимальна (название/код, время, меню), основные контролы — одна плавающая строка; сетка сама подбирает колонки под число участников; поднятая рука получает цветной контур и попадает в список.
- **Zoom:** нижний тулбар с «More» для второго уровня; участники — правая панель; поднятая рука — очередь и золотая иконка; gallery pagination вместо бесконечной ленты.
- **Teams:** компактная шапка с контекстом встречи, secondary actions в overflow; raise hand — баннер/очередь и цветовой акцент; видео — главный слой, chrome — поверх.

Общий принцип: **primary controls всегда видимы, secondary — в overflow; видео — hero-слой; поднятая рука различается цветом, формой и позицией, а не только иконкой; при переполнении — страницы, а не скрытый скролл.**

---

## 7. Приёмочные критерии

1. При `scrollTop=0` первый ряд каждой сцены имеет `y ≥ stageTop + padding` (нет отрицательных координат).
2. `rowPitch ≥ tileH + gap` для всех сценариев; нет наезда подписей.
3. 9 участников видны полностью на 563×545, 768×1024, 1440×900; 50 — либо все читаемы, либо пагинация с индикатором.
4. 1:1 на 390 заполняет ≥90% высоты сцены (cover/spotlight).
5. На 390 кнопка «Выйти» видима и имеет контраст ≥3:1, размер ≥44.
6. Top bar ≤48 px (44 mobile), ≤7 элементов, ни один элемент не обрезан на 768.
7. Split: 44 + 30, gap 0, порядок main→chevron, divider между частями.
8. Панель участников открывается на 563 px (шапка/«⋯»/счётчик), на mobile — full-width.
9. На 390 имя/роль/страна не пересекаются с бейджами (проверка `getBoundingClientRect`).
10. Hand: 4 px #FFB020 + виньетка ≥0,38 + тёмный контур; badge 28×20.
11. CLS <0,1; в тостах нет URL/JWT; тост ≤340 px.
12. Spotlight: `|ring.x−target.x| < 4` и `|ring.y−target.y| < 4`; карточка не перекрывает центральные плитки.

---

## 8. Приложение

### 8.1 Аннотированные кадры

| # | Файл | Что показывает |
|---|---|---|
| 01 | [topbar-video-controls](../qa/call-design/screens/annotated/01-topbar-video-controls-1440-light.png) | Top bar 63,4 / 12 блоков; пустая сцена; 14 контролов |
| 02 | [grid-overflow-50](../qa/call-design/screens/annotated/02-grid-overflow-50-1440-light.png) | 50 участников: y=−204, overlap 22,9 |
| 03 | [563-first-row-hidden](../qa/call-design/screens/annotated/03-563-first-row-hidden.png) | 563: первый ряд скрыт, panel недоступна |
| 04 | [mobile-invisible-leave](../qa/call-design/screens/annotated/04-mobile-invisible-leave.png) | Невидимый leave, конфликт подписей |
| 05 | [1to1-does-not-fill](../qa/call-design/screens/annotated/05-1to1-does-not-fill.png) | 1:1 — 28% сцены |
| 06 | [768-header-overflow](../qa/call-design/screens/annotated/06-768-header-overflow.png) | 768: обрезка шапки, overlap 42,3 |
| 07 | [hand-raised-tokens](../qa/call-design/screens/annotated/07-hand-raised-tokens.png) | Рука: рамка/свечение/виньетка |
| 08 | [participants-panel-360](../qa/call-design/screens/annotated/08-participants-panel-360.png) | Панель 360 и перестройка видео |
| 09 | [assistant-spotlight](../qa/call-design/screens/annotated/09-assistant-spotlight-mismatch.png) | Spotlight Δy=429 |
| 10 | [notification-cls](../qa/call-design/screens/annotated/10-notification-cls.png) | CLS 0 + fallback 2283 px |
| 11 | [toast-token](../qa/call-design/screens/annotated/11-toast-token-overflow.png) | JWT в UI |
| 12 | [split-anatomy](../qa/call-design/screens/annotated/12-split-button-anatomy.png) | Зеркальный split, gap 10 |
| 13 | [50-mobile](../qa/call-design/screens/annotated/13-50-mobile.png) | 50 на 390: 25 рядов, верх недоступен |

Контактный лист: [00-contact-sheet.png](../qa/call-design/screens/annotated/00-contact-sheet.png).

### 8.2 Матрица

- `qa/call-design/screens/call-{01,09,25,50}-{1440x900,768x1024,563x545,390x844}-{light,dark}.png` — 32 кадра.
- `clean-*` — 23 чистых кадра без тестовых ошибок (1/9/25/50, 4 размера, light/dark).
- `panel-participants-*` — 12; `menu-video-*` / `menu-more-*` — 8; `hand-raised-*` — 9; `controls-*` — 2; `clean-split`, `clean-toast3`, `clean-assistant`, `clean-video-menu`.
- Всего: **100 PNG** (86 + 14 annotated).

### 8.3 Машинные данные

- `qa/call-design/data/summary.json` — сводка (header/video/controls/hand/CLS/spotlight).
- `qa/call-design/data/metrics.json` — 59 сценариев, bbox, computed styles, scroll, tiles.
- `qa/call-design/data/clean.json` — чистые кадры 1/9/25/50.
- `qa/call-design/data/extra.json` — 563/768/390, hand, темы, toast, assistant.

### 8.4 Воспроизведение

```bash
# путь: корень репозитория; админ-ключ только в памяти
export AGIL_ADMIN_KEY=...            # нужен для presence >8 (invite tokens)
node qa/call-design/capture.mjs      # 32 матричных кадра + панели/меню/рука
node qa/call-design/clean.mjs        # чистые 1/9/25/50
node qa/call-design/clean2.mjs       # 768/563/390 clean
node qa/call-design/annotate.py      # аннотированные борды
```

Ключ не хранится в репозитории; публичный join для self не требует ключа.

### 8.5 Ограничения

- Presence-участники — WS-only, без медиа; оценивался layout, не WebRTC-качество.
- Камера off — худший случай для подписей; при видео подписи те же.
- Прод обновился во время аудита; финальные кадры — сборка `embed-DwXbjbYQ` / `index-DU0kJMK6` / `index-CAfFGbpL`.
- Тестовые ошибки join/reconnect в матричных кадрах — артефакт серии быстрых перезапусков; чистые кадры (`clean-*`) без них.

---

**Итог:** сначала чинить P0-01 (сетка) и P0-02 (leave), затем P1-03/P1-04/P1-05/P1-06/P1-07. После этого CALL UI будет соответствовать и запросу заказчика, и базовым паттернам Meet/Zoom/Teams — без копирования их визуала.
