Спецификация паритета с Figma

Живой документ: доведение android-events до макета «Podushe | Mobile-app». Текущее положение дел — в разделах 0.4–0.15: сводка, журнал изменений, трассировка задач, хвосты внутри закрытых задач, состояние всех 75 экранов, сценарии приёмки, тесты, пометки в коде, блокеры, ресурсы, окружение и правила работы.

версия 2.107.09.202622 из 83 задач8 из 75 экранов84 тестаблокер B-1
# Спецификация: паритет `android-events` с макетом Figma «Podushe | Mobile-app»

Версия 2.1 · 6 сентября 2026 (правки 7 сентября) · для исполняющего агента
Основание: аудит от 06.09.2026 — https://salam0nn.ru/artifacts/podushe-figma-audit.html

> **Живой документ.** Состояние работ на 07.09.2026: **22 задачи из 83**
> закрыты — веха M0 целиком, кроме T-01, и 8 задач вехи M1. Сборка, lint и
> 84 юнит-теста зелёные. Один активный блокер: **B-1** — квота Figma API,
> снимется около 10.09.
>
> Подробности: **0.4** сводка · **0.5** журнал изменений спеки · **0.6**
> матрица трассировки задач · **0.7** блокеры, отклонения и решения владельца ·
> **0.8** ресурсы и ссылки · **0.9** правила работы · **0.10** хвосты внутри
> закрытых задач · **0.11** состояние всех 75 экранов · **0.12** сценарии
> приёмки · **0.13** тесты · **0.14** пометки в коде · **0.15** окружение и
> ловушки. Разделы 1–8 правятся только при изменении требований и всегда с
> записью в журнал 0.5.

---

## 0. Как читать и как работать по этому документу

**Кто пользователь документа.** Автономный агент-разработчик, который доводит Android-приложение «По душе» (`android-events/`, Kotlin + Jetpack Compose) до полного соответствия макету. Документ самодостаточен: в нём названы файлы, интерфейсы, точные размеры, тексты, состояния и критерии приёмки. Чего в нём нет — берётся из нод Figma по ссылкам, а не додумывается.

**Источник истины и приоритет.**
1. Figma `Bwjfrippxq86431WDzDDP7` — ноды по id. Внутри файла приоритет у секций с пометкой **«NEW … от 25 авг 2026»**; секции «1 версия (от 28 июля 2026)», страницы «Прототип», «Архив», «Дизайн концепт(тестовое)» — не источник.
2. Этот документ (структура, размеры, тексты, поведение, порядок работ).
3. Существующий код — только как отправная точка; всё, что расходится с п. 1–2, переделывается.

Ссылка на ноду: `https://www.figma.com/design/Bwjfrippxq86431WDzDDP7/Podushe?node-id=<ID с дефисом>` (например `node-id=230-19369`). API: `GET https://api.figma.com/v1/files/Bwjfrippxq86431WDzDDP7/nodes?ids=230:19369` с заголовком `X-Figma-Token`. ⚠️ Токен Starter-аккаунта исчерпал квоту 06.09 до ~11.09 (429, `retry-after ≈ 4,6 суток`); нужен токен другого аккаунта. Не тратить квоту на выгрузку целых страниц (по 12–60 МБ) — запрашивать конкретные ноды с `depth`.

**Единицы.** Размеры в макете даны для экрана 390 pt (iPhone 15). В Compose 1 pt макета = 1 dp. Ширина контента 358 = 390 − 2×16; кнопки действия 342 = 390 − 2×24.

**Приложения (в репозитории, `android-events/docs/figma/`):**
- `screens.json` — инвентарь 75 логических экранов: ноды, состояния, статус, находки аудита, ссылки на код.
- `strings.md` — все тексты трёх выгруженных страниц по секциям с весами/кеглями.
- `flows.md` — 381 переход макета, разрешённый до пар «экран → экран».
- `assets.json` — 69 иконок и 20 3D-иллюстраций: имена и id инстансов для экспорта.

**Цикл работы агента.** Веха → задача (один экран или один компонент) → реализация → самопроверка по чек-листу приёмки → доказательства (команды и их вывод, скриншот экрана рядом со ссылкой на ноду) → коммит → следующая задача. Не переходить к следующей вехе, пока у текущей не закрыты все критерии из раздела 8.

**Если данных не хватает.** Открыть ноду. Если доступа к Figma нет — реализовать по спеке, пометить в коде `// FIGMA-CHECK: <нода> <что не сверено>` и вынести в отчёт. Не выдумывать тексты, цвета и размеры.


### 0.1 Что читать перед какой задачей (карта контекста)
Документ большой; агент грузит его по частям, чтобы не размывать контекст:

| Задача | Обязательно | По требованию |
|---|---|---|
| любая | разделы 0–3, 8; таблица компонентов 4.5; **0.7 блокеры и 0.9 правила** | 0.6 матрица — что уже сделано |
| компонент (T-01…T-11) | 4.1–4.6, строка компонента в 4.5, `components.md` (внутренняя геометрия инстанса) | нода компонента на странице Ui-kit |
| экран | блок экрана в разделе 6, его редлайн в `redlines.md` (по id ноды), его строки в `strings.md`, его переходы в `flows.md` (поиск по id), таблицы состояний 4.8 | `screens.json` (находки аудита по этому экрану) |
| навигация (T-12) | раздел 5, 3.1, `Routes.kt` в 3.7 | — |
| данные (T-13) | 3.2, 3.4, 3.7 (модели и репозитории), Приложение E | `api-schema.yml` |
| приёмка вехи | 8.1–8.3, сценарии 8.5 для вехи | 0.4 сводка, `docs/figma/PROGRESS.md` |
| ассеты | 0.7 (блокер B-1), `docs/figma/ASSETS.md` | `tools/figma_export.py` |
| сверка с макетом | `docs/figma/reference/` — 49 рендеров экранов ×2 | 0.9 правила П-2 и П-3 |

### 0.2 Глоссарий
- **Нода** — элемент Figma по id `A:B`; **экран** — фрейм 390×N; **состояние** — вариант экрана (ошибка, пусто, пермишен).
- **Лист** — белый контейнер с верхними углами r32, начинающийся под шапкой (y = 124) на сером фоне `#F5F5F5`.
- **Шапка** — зона 124 px: кнопка «назад» 48 на (16, 64), заголовок 590/18 по центру, правый слот 48.
- **Зона действия** — нижняя зона 96 px (паддинг 24) с кнопкой 342×48; на тёмных экранах — с градиентом.
- **Стеклянная пара** — `GlassPair`: две иконки 36 в одной стеклянной пилюле 102×48 с разделителем.
- **Строка события** — `EventRow` (Default-card-event 358×108); **акцентная карточка** — `AccentEventCard` (300×228).
- **Fake-репозиторий** — реализация интерфейса репозитория в памяти для того, чего нет в API; помечена `BACKEND-GAP`.
- **`FIGMA-CHECK`** — пометка в коде/отчёте: значение взято не из ноды, требует сверки.
- **P0/P1/P2** — приоритет экрана: без P0 приложение не соответствует макету принципиально; P1 — заметное расхождение; P2 — детали.

### 0.3 Definition of Done всего проекта
Проект готов, когда: (1) каждый из 75 экранов имеет скриншот в каждом состоянии из раздела 6 и он визуально совпадает с нодой (композиция, размеры ±2 dp, цвета, шрифты, иконки, тексты); (2) все 64 правила поведения из `flows.md`-таблицы аудита воспроизводятся или помечены `BACKEND-GAP`; (3) в `ui/` нет кириллических литералов, `Icons.Default.*` и эмодзи-иконок; (4) `assembleDushaDebug`, `lint`, юнит-тесты зелёные; (5) итоговый отчёт по 8.3 сдан.

---

## 0.4 Сводка выполнения (на 07.09.2026)

| Веха | Задач | Готово | Статус |
|---|---|---|---|
| M0 · Дизайн-система и каркас | 15 | 14 | закрыта, кроме T-01 (блокер B-1) |
| M1 · Вход и регистрация | 14 | 8 | в работе |
| M2 · Афиша | 10 | 0 | не начата |
| M3 · Событие и чат | 5 | 0 | не начата |
| M4 · Создание и кабинет | 14 | 0 | не начата |
| M5 · Профиль и настройки | 14 | 0 | не начата, эталоны макета есть |
| M6 · Свайпы, избранное, уведомления | 7 | 0 | не начата |
| M7 · Финиш | 4 | 0 | не начата |
| **Итого** | **83** | **22** | |

**Состояние гейтов раздела 8.1 на последнем коммите:** `assembleDushaDebug`
зелёный · `lintDushaDebug` без ошибок · юнит-тестов **84**, падений 0 ·
`grep -rn '"[А-Яа-яЁё]' app/src/main/java/ru/podushe/app/ui` пуст ·
`Icons.Default` и эмодзи-иконок в `ui/` нет.

**Ветка:** `feature/figma-parity` (от текущего HEAD, а не от `origin/main` —
см. отклонение О-1 в 0.7).

---

## 0.5 Журнал изменений спецификации

Формат по [Keep a Changelog](https://keepachangelog.com): что изменилось,
когда, почему. Номер версии — по semver: MAJOR — несовместимая смена
требований, MINOR — новые требования, PATCH — уточнения и опечатки.

### 2.1 — 07.09.2026

**Добавлено**
- Разделы 0.4–0.9: сводка, этот журнал, матрица трассировки, блокеры,
  ресурсы, правила ведения документа.
- Колонка «Статус» в таблицах вех раздела 7.

**Решено владельцем**
- **Р-1 · Узкое начертание заголовков.** Веса 650 и 760 в макете — это
  `SFPro-CondensedSemibold` и `SFPro-CondensedBold`. Inter (решение 3.3)
  оси ширины не имеет. Владелец: «подогнать под то, что требует Figma» →
  введено горизонтальное сжатие 0.72 для этих двух весов (см. 4.2).
- **Р-2 · Скриншоты без эмулятора.** В окружении нет KVM. Владелец:
  «делай свой обходной путь» → снимки рендерятся на JVM через Paparazzi
  (см. блокер B-2).

**Обнаружено при сверке (расхождения спеки с нодами — верна нода, раздел 0)**
- 4.5 № 10: боковые круги таббара названы 62, пилюля 217; в ноде 54 и 210 —
  только с ними ряд укладывается в 390 (20+54+16+210+16+54+20).
- 4.5 № 21: аватар `UserCard` назван 48; во всех трёх инстансах ноды он 64.
- 4.5 № 1: «disabled — белый оверлей 11 %»; в нодах он свой у каждого стиля —
  brand и delete 12 %, primary 20 %.
- 6.A · A5: зона действия лежит **поверх** листа, а не под ним.
- 6.A · A1: фотокарточки коллажа **не наклонены**; слот 121×121 на (12, 447) —
  это 3D-пазл, а не круглое фото.

**Уточнено**
- 3.3: эмодзи как заглушка 3D заменена пустым слотом — см. правило П-4 в 0.9.

**Дополнено (второй заход 07.09)**
- 0.10: хвосты внутри задач, помеченных как закрытые, — одиннадцать пунктов
  с указанием, куда каждый переходит. Галочка означает «сделано по описанию
  из раздела 7», а не «в этой области больше нечего делать».
- 0.11: состояние всех 75 экранов раздела 6 тремя статусами; отдельно
  отмечены 15 файлов, где экран есть, но по старой вёрстке.
- 0.12: каждый сценарий приёмки 8.5 вехи M1 с указанием, каким тестом или
  снимком он закрыт и какие закрыты лишь частично.
- 0.13: сверка требований 8.2 по тестам — что есть, чего нет и почему.
- 0.14: полный список пометок в коде (2 FIGMA-CHECK, 10 BACKEND-GAP,
  1 CONTENT-GAP, 26 FIGMA-ASSET) с расшифровкой каждой.
- 0.15: воспроизведение окружения с нуля и шесть ловушек, на которых уже
  теряли время.

### 2.0 — 06.09.2026
Первая версия для исполняющего агента: 75 экранов, вехи M0–M7, приложения.

---

## 0.6 Матрица трассировки задач

Связь «задача → что сделано → ноды → доказательство». Статусы: `✓` закрыта,
`~` в работе, `—` не начата, `⛔` блокер.

### M0 · Дизайн-система и каркас — **закрыта, кроме T-01 (блокер B-1)**

| ID | Статус | Что сделано | Ноды / разделы | Доказательство |
|---|---|---|---|---|
| T-01 | ⛔ | Ассеты не выгружены (блокер B-1). Работает запасной путь 3.3: Material через `AppIcons`, пустые слоты через `Icon3d`, всё под `FIGMA-ASSET`. Коллаж A1 вырезан из сохранившегося рендера | Ui-kit 175:164 | `docs/figma/ASSETS.md`, `tools/figma_export.py` |
| T-02 | ✓ | Токены 4.1 и стили 4.2; `Terracotta` убрана | 175:164 | `ui/theme/Color.kt`, `Type.kt` |
| T-03 | ✓ | 257 литералов вынесены; состояния ViewModel носят `@StringRes` | `strings.md` | `res/values/strings.xml` |
| T-04 | ✓ | Кнопки 48/32, `ListRowButton`, `GlassPair` | 260:23102, 232:28419, I688:61525;663:74411, I287:27893;178:6329 | `ui/components/Buttons.kt` |
| T-05 | ✓ | Поля с лейблом и ошибкой, `SearchField`, `OtpField`; `NumPad` удалён | 261:25046, 292:25550, 192:19947, 261:27191 | `ui/components/Fields.kt` |
| T-06 | ✓ | `CategoryChip`, `Badge` восьми тональностей, `Icon3d` | 284:25643, I287:27893;179:7366;178:5635 | `ui/components/Chips.kt` |
| T-07 | ✓ | `ScreenScaffold` (шапка 124, лист r32), `BottomAction`, `StepProgress`, `EmptyState`, snackbar | 260:24430, 158:9505, 231:21434 | `ui/components/Scaffold.kt`, `EmptyState.kt`, `Snackbar.kt` |
| T-08 | ✓ | `UserCard`, `Avatar`, `RoleBadge`, `CabinetRow`, `QuestionCard` | 302:41633, I688:61525;663:74402, 660:71483 | `ui/components/People.kt`, `Cabinet.kt` |
| T-09 | ✓ | Чекбокс, радио, ползунок, календарь, барабаны, action sheet, алерт, дропдаун | 609:74131, 228:11549, 194:27047, 732:79393, 732:77022, 732:77001 | `Controls.kt`, `Calendar.kt`, `Pickers.kt`, `Sheets.kt` |
| T-10 | ✓ | `EventRow` 590/18 с расстоянием и организатором, `AccentEventCard`, `ChatBubble`, `MessageBar`, кластер «+ еще N» | I287:27893;179:7390 и ;179:7366, 232:23094, 232:22994 | `EventCards.kt`, `ChatComponents.kt`, `EventMap.kt` |
| T-11 | ✓ | Таббар по ноде, галерея `debug/uikit` | I287:27893;179:7551 | `ui/components/TabBar.kt`, `ui/debug/UiKitGallery.kt` |
| T-12 | ✓ | `Routes.kt` по 3.7.1, `AppNavHost`, `AppScaffold`; флаги из `MainActivity` убраны (517 → 40 строк) | раздел 5 | `ui/navigation/` |
| T-13 | ✓ | Модели 3.7.2, `MockEvent` → `EventCard`, DTO по контракту, 5 Fake-репозиториев | 3.7.2, 3.4 | `data/model/Models.kt`, `data/api/Mapping.kt`, `MappingTest` (15) |
| T-14 | ✓ | `PermissionGate` и шторка «Перейти в настройки»; `OnboardingFlags` | 732:76894, 732:77193 | `ui/permissions/PermissionGate.kt` |
| T-15 | ✓ | Вся таблица 4.7 | 4.7 | `ui/format/Formats.kt`, `FormatsTest` (20) |

### M1 · Вход и регистрация

| ID | Статус | Что сделано | Ноды | Доказательство |
|---|---|---|---|---|
| T-20 | ✓ | A1 велком: коллаж из рендера макета, заголовок и зона действия свои | 158:9505 | `screens/auth/WelcomeScreen.kt`, снимок `A1-welcome-158-9505` |
| T-21 | ✓ | A2 в четырёх состояниях; отказ по аккаунту отделён от сбоя сети | 260:24430, 261:25228, 582:52876, 579:52473 | `screens/auth/PhoneScreen.kt`, 4 снимка |
| T-22 | ✓ | A3 документы; текст ноды в `res/raw` (23 абзаца, `CONTENT-GAP`) | 261:26902 | `screens/legal/PolicyScreen.kt` |
| T-23 | ✓ | A4 в четырёх состояниях; машина состояний 4.8 отдельным классом | 261:26987, 261:27203, 261:27297, 592:5040 | `screens/auth/OtpScreen.kt`, `OtpStateMachineTest` (10) |
| T-24 | ✓ | A14 ветвление после кода | 287:29289, 610:75380 | `data/auth/AuthBranching.kt`, `AuthBranchingTest` (8) |
| T-25 | ✓ | A5 локация: город по гео, лимит двух районов, «Весь город» эксклюзивен | 597:55329, 597:56595, 730:75312 | `screens/registration/LocationStepScreen.kt`, 3 снимка |
| T-26 | ✓ | A6 поиск города | 597:56057 | `screens/registration/CitySearchScreen.kt` |
| T-27 | ✓ | A7 поиск районов | 609:74123 | `screens/registration/DistrictSearchScreen.kt` |
| T-28…T-33 | — | A8, A9, A10, A11 (нужен CameraX), A12, A13 | | |

Вехи M2–M7 — не начаты; таблицы задач в разделе 7.

---

## 0.7 Блокеры, отклонения и решения владельца

### Блокеры

| ID | Блокер | Влияние | Обход | Снимается |
|---|---|---|---|---|
| **B-1** | Квота Figma API исчерпана: `429`, `retry-after` 329 630 с на `/images` и `/files`. Лимит считается на аккаунт | T-01 не выполнить: 68 иконок и 20 3D-иллюстраций не выгружены | Material по таблице 4.6 через `AppIcons`, пустые слоты через `Icon3d`, всё под `FIGMA-ASSET`. Коллаж A1 вырезан из уцелевшего рендера | Само около **10.09.2026** либо токеном другого аккаунта. Скрипт готов: `tools/figma_export.py` |
| **B-2** | В окружении нет KVM и аппаратной виртуализации | Эмулятор не поднимается: критерий 8.1.2 (`adb exec-out screencap`) и Compose-тесты `androidTest` недоступны | Снимки рендерятся на JVM через **Paparazzi** на кадре 390 dp. Одобрено владельцем (Р-2) | Появлением машины с KVM |
| **B-3** | Ручной прогон сценариев 8.5 невозможен по той же причине | Сценарии закрываются юнит-тестами и снимками | — | За владельцем |

### Отклонения от буквы спеки (сознательные, с причиной)

| ID | Где | Что сделано иначе | Почему |
|---|---|---|---|
| **О-1** | 2 «Ветка от `origin/main`» | Ветка отведена от текущего HEAD | `android-events/` на `origin/main` и HEAD побайтово одинаков, а переключение переписало бы 490 не связанных файлов, включая незакоммиченную работу по ingestion |
| **О-2** | 7, T-11 «`ui/debug/UiKitGallery.kt`» | Файл в `main`, маршрут регистрируется по `BuildConfig.DEBUG` | Маршрут `debug/uikit` объявлен в `Routes.kt` (main) и не может ссылаться на composable из debug-набора |
| **О-3** | 3.3 «3D — плейсхолдер-круг с эмодзи» | Пустой слот с пунктирной обводкой | Системный эмодзи не совпадает с 3D-иллюстрацией ни формой, ни цветом и выдаёт себя за готовый макет |
| **О-4** | 2 «менять зависимости — спросить» | Добавлен `app.cash.paparazzi` (только тесты) | Единственный способ выполнить 8.1.2 без эмулятора; одобрено (Р-2) |
| **О-5** | 8.2 «`ValidatorsTest`» | Валидаторы лежат в `data/validation`, не в `ui/format` | Это доменные проверки; таблица гласных в `ui/` ложно срабатывала на гейт 8.1.3 |

### Решения владельца

| ID | Вопрос | Решение | Отражено в |
|---|---|---|---|
| **Р-1** | Заголовки шире макета: веса 650 и 760 в Figma — узкие начертания SF Pro, у Inter оси ширины нет | Подогнать под макет: горизонтальное сжатие 0.72 для весов 650 и 760 | 4.2, `PodusheType.condensed` |
| **Р-2** | Скриншоты без эмулятора | Использовать обходной путь агента (Paparazzi) | 8.1.2, B-2 |

### Открытые вопросы к владельцу

Пока нет. Ранее открытый вопрос по заголовку A1 закрыт решением Р-1.
Вопросы из 8.3 (пороги репутации, семантика QR, тексты политики, 9 vs 5
причин жалобы, «Избранные люди», тело `company-invite`) остаются к финишу M7.

---

## 0.8 Ресурсы: где что лежит

### Документы

| Что | Где |
|---|---|
| Эта спецификация | `android-events/docs/FIGMA_PARITY_SPEC.md` · копия: https://salam0nn.ru/artifacts/podushe-figma-parity-spec.html |
| Журнал работ и ревью | `android-events/docs/figma/PROGRESS.md` |
| Ассеты: что выгрузить и как | `android-events/docs/figma/ASSETS.md` |
| Аудит-основание | https://salam0nn.ru/artifacts/podushe-figma-audit.html |
| Сравнение экранов входа с макетом | https://salam0nn.ru/artifacts/podushe-parity-screens-a.html |

### Выгрузка Figma (приложения)

| Что | Где |
|---|---|
| 75 экранов: ноды, состояния, находки | `docs/figma/screens.json` |
| 677 строк текста по секциям | `docs/figma/strings.md` |
| Геометрия 172 нод разделов A–C | `docs/figma/redlines.md` |
| Внутренняя геометрия 46 компонентов | `docs/figma/components.md` |
| 381 переход «экран → экран» | `docs/figma/flows.md` |
| 68 иконок и 20 3D с id нод | `docs/figma/assets.json` |
| **49 рендеров экранов ×2** (весь «Профиль», карта, вход) | `docs/figma/reference/` + `README.md` с таблицей |

### Figma

- Файл: `Bwjfrippxq86431WDzDDP7` («Podushe | Mobile-app»)
- Нода по ссылке: `https://www.figma.com/design/Bwjfrippxq86431WDzDDP7/Podushe?node-id=<id с дефисом>`
- Страницы-источники: Авторизация `112:160`, Афиша `579:52323`,
  Создание/управление `579:52328`, Взаимодействие `792:36278`,
  Свайпы `579:52324`, Уведомления `579:52326`, Избранные `579:52327`,
  Профиль `579:52329`, Чаты `579:52330`, Настройки `634:54612`,
  Ui-kit `175:164`

### Команды

```bash
cd android-events
./gradlew :app:assembleDushaDebug         # сборка (перед каждым коммитом)
./gradlew :app:lintDushaDebug             # lint
./gradlew :app:testDushaDebugUnitTest     # юнит-тесты
./gradlew :app:recordPaparazziDushaDebug  # снимки экранов на JVM (B-2)
python3 tools/figma_export.py             # выгрузка ассетов, когда снимется B-1
```

Снимки: `app/src/test/snapshots/images/`. Токен Figma — в `~/.figma_token`,
в репозиторий не попадает.

### Окружение (собрано агентом, вне репозитория)

Android SDK `/root/android-sdk` (platform 35, build-tools 35) · JDK 17
`/usr/lib/jvm/java-17-openjdk-amd64` · запуск через `android-events/build.sh`
(ужимает кучу демонов: на машине 7 ГБ, дефолтные 4 ГБ роняют демон).

---

## 0.9 Как вести этот документ и работать по нему

Правила выведены из практики первых 22 задач — каждое стоило найденной
ошибки.

**П-1 · Нода важнее текста спеки.** Приоритет из раздела 0 работает буквально:
при расхождении верна нода, а не проза. Так найдены пять расхождений
(перечислены в журнале 2.1). Каждое такое расхождение — запись в журнал 0.5,
а не молчаливая правка кода.

**П-2 · Сверять глазами, а не только по числам.** Пять из одиннадцати правок
по ревью нашлись только на скриншоте: зона действия поверх листа, сдвиг
коллажа на высоту статус-бара, курсор в закрытом поле, разная ширина кнопок,
эмодзи вместо иллюстраций. Чтением редлайна они не видны. Порядок:
собрать → снять снимок → сравнить с нодой (или с `docs/figma/reference/`) →
править → снять снова.

**П-3 · Замерять, а не спорить.** Расхождение шрифта закрылось только после
замера по рендеру: 280 dp в макете против 389 у нас. Числа находят причину
(узкое начертание) там, где на глаз видно лишь «что-то не так».

**П-4 · Заглушка не должна выдавать себя за макет.** Пустой слот с пунктиром
честнее эмодзи или выдуманного градиента: видно, что ассета нет, и никто не
принимает заглушку за готовый экран.

**П-5 · Не додумывать.** Наклон карточек на A1 и «круглое фото» вместо пазла —
выдумки, всплывшие при сверке с рендером. Нет данных → `FIGMA-CHECK` и
вопрос владельцу, а не правдоподобная догадка.

**П-6 · Одна точка подмены.** Временные Material-иконки собраны в `AppIcons`,
3D — в `Icon3d`. Когда снимется B-1, правятся два файла, а не тридцать
экранов. Так же с текстами (`strings.xml`) и форматированием (`Formats`).

**П-7 · Логика отдельно от Compose.** Машина состояний OTP, ветвление A14,
валидаторы и форматтеры вынесены в чистый Kotlin — 84 теста гоняются на JVM
без эмулятора, что при блокере B-2 единственный способ проверить поведение.

**П-8 · Один коммит — одна задача.** Сообщение по-русски, в теле `Spec: T-xx`,
ноды и что нашло ревью. По этим сообщениям собрана матрица 0.6.

**Порядок правки самого документа:** менять разделы 1–8 только при изменении
требований; каждую правку записывать в журнал 0.5 с версией и причиной;
статусы обновлять в 0.4 и 0.6; блокеры и решения владельца — в 0.7; ничего не
удалять задним числом — история и есть доверие к живому документу.

---


---

## 0.10 Что осталось внутри закрытых задач

Галочка в 0.6 означает «задача выполнена по своему описанию из раздела 7», а
не «в этой области больше нечего делать». Ниже — всё, что сознательно
отложено, чтобы это не всплыло сюрпризом.

| Задача | Хвост | Куда переходит |
|---|---|---|
| T-03 | Тексты экранов, ещё не переверстанных по макету, лежат под префиксом `legacy_*` (примерно 120 ключей). Это не тексты макета, а подписи текущей вёрстки | Снимаются вместе с экранами в M2–M6, финальная чистка — T-113 |
| T-09 | `PodusheSlider` тянется жестом и рисует засечки, но кривая раскладки меток упрощена: позиция заливки берётся линейно от индекса, а в ноде B2 она чуть иная | Уточнить в T-41 (B2 радиус), там ползунок и живёт |
| T-10 | Кластер карты рисует стопку обложек первых событий выборки, а не тех, что реально попали в кластер: MapLibre не отдаёт состав кластера в иконку | Ограничение движка. Если нужен точный состав — своя кластеризация, это отдельное решение владельца |
| T-12 | В графе зарегистрированы **23 маршрута из 54** объявленных. Остальные ждут своих экранов — пустой `composable` показал бы чёрный экран и прятал незавершённость (8.4) | По мере M2–M6 |
| T-13 | Интерфейс `EventsRepository` имеет 28 методов, полный список 3.7.3 — больше. Не заведены: `calendar`, `collections`, `collection`, `stories`, `venue`, `deck(filter)`, `reportScreen`, `reviewScreen`, `review`, `checkin`, `agenda`. Ручки под них в 3.4 есть, но экранов-потребителей ещё нет | Добавляются вместе с экранами: `calendar`/`collections` — T-44/T-49, `reportScreen` — T-50, `review` — T-82, `checkin` — M4 |
| T-13 | Демо-каталог из 25 событий лежит в `data/mock/MockEvents.kt`, а не в `FakeEventsRepository`, как просит 3.2 | T-113 |
| T-14 | `PermissionGate` умеет запрос, отказ и уход в настройки, но определение города по координатам не реализовано: `detectCity` берёт первый город списка | Блокер по геокодеру, `BACKEND-GAP: reverse-geocode`. Уточнить при T-40 (карта) |
| T-20 | Коллаж A1 — единая картинка из рендера макета, поэтому подпись «Питомцы» запечена в неё, а не берётся из `strings.xml` | Разбирается на слои после снятия блокера B-1 |
| T-21 | `FilterTab` оставлен тонкой обёрткой над `FilterChip`, чтобы не ломать ещё не переверстанные экраны | Убирается, когда последний экран переедет на `FilterChip` |
| T-25 | Мини-карта города — серая подложка: живой MapLibre подключается вместе с картой афиши, чтобы не поднимать движок дважды | T-40 |
| T-25 | Маршрут `MapPick` (полноэкранная карта с пином, ноды 730:75625 / 731:75848) объявлен, но экран не написан | Остаток T-25, делать вместе с T-40 |

---

## 0.11 Состояние экранов раздела 6

Статусы: `✓` собран по нодам · `≈` в коде есть, но по старой вёрстке, не по
макету · `—` экрана нет.

| Экран | Статус | Комментарий |
|---|---|---|
| A1 велком | ✓ | коллаж из рендера макета |
| A2 номер | ✓ | четыре состояния |
| A3 политика | ✓ | текст ноды в `res/raw`, `CONTENT-GAP` |
| A4 код | ✓ | четыре состояния, машина 4.8 |
| A5 локация | ✓ | кроме полноэкранной карты с пином |
| A6 поиск города | ✓ | |
| A7 поиск районов | ✓ | |
| A8 о себе | — | T-28 |
| A9 интересы | ≈ | `auth/OnboardingScreen.kt` — старая вёрстка, переверстать в T-29 |
| A10 верификация-интро | — | T-30 |
| A11 селфи | — | T-31, нужен CameraX |
| A12 поп-ап success | — | T-32 |
| A13 запрос уведомлений | — | T-33, гейт готов (T-14) |
| A14 ветвление | ✓ | логика и тесты |
| B1 карта | ≈ | `afisha/AfishaMapScreen.kt`: чипы временные, peek не 216, блока «Куда иду» нет |
| B6 поиск | ≈ | `afisha/SearchScreen.kt` — переписать в T-45 |
| B2–B5, B7, B9, B13, B14 | — | M2 |
| B10 событие | ≈ | `event/EventScreen.kt`: восьми состояний CTA нет, шторок нет, блок идущих скрыт |
| B11 чат | ≈ | `chats/ChatScreen.kt`: без карточки события, статусов и фото |
| C1 создание | ≈ | `create/CreateEventScreen.kt`: мастер из шагов вместо одной формы макета |
| C5 управление, C7–C13, C4 | ≈ / — | `event/CrewScreen.kt` — упрощённый список участников; остальное в M4 |
| D3–D5 | — | M3, M4 |
| E1–E6 свайпы | ≈ | `swipes/SwipesScreen.kt` — старая вёрстка |
| F1 уведомления | ≈ | `notifications/NotificationsScreen.kt` |
| G1, G2 избранные | ≈ | `favorites/FavoritesScreen.kt`, людей нет |
| H1–H9 профиль | ≈ / — | `profile/ProfileScreen.kt`, `DiscountsScreen.kt`; **эталоны 48 рендеров есть** |
| I1–I5 чаты и люди | ≈ / — | `chats/ChatsScreen.kt`; чужого профиля и подписок нет |
| J1–J5 настройки | ≈ | `settings/SettingsScreens.kt` — подстраницы вместо одного экрана |
| K дизайн-система | ✓ | галерея `debug/uikit` |

Итого по 75 логическим экранам: **8 собраны по нодам**, 15 файлов держат
экраны по старой вёрстке, остальные не начаты.

---

## 0.12 Сценарии приёмки 8.5: чем закрыт каждый

Ручной прогон недоступен (блокер B-3), поэтому сценарии закрываются
юнит-тестами и снимками. Ниже — что именно доказывает каждый.

### M1 (сценарии 1–13)

| № | О чём | Состояние | Чем закрыт |
|---|---|---|---|
| 1 | Запуск без токена → велком → ввод номера | ✓ | снимок `A1-welcome-158-9505`, маршруты `Welcome → Phone` |
| 2 | 9 цифр — кнопка неактивна, 10 — активна, «000» — «Некорректный номер» | ✓ | `ValidatorsTest.номер с недопустимым префиксом…`, снимки `A2-phone-empty`, `A2-phone-error` |
| 3 | Заблокированный аккаунт → «Связаться с поддержкой» | ✓ | снимок `A2-phone-blocked`, `AccountBlockedException` в `AuthRepository` |
| 4 | Таймер 50 с → кнопка повторной отправки | ✓ | `OtpStateMachineTest.по нулю таймера…`, `…повторная отправка…` |
| 5 | Пять неверных кодов → лимит 14:59, «Изменить номер» | ✓ | `OtpStateMachineTest.пять неверных попыток…`, снимок `A4-otp-limited` |
| 6 | Новый аккаунт → запрос гео → шаг 1 либо поиск города | частично | `AuthBranchingTest` закрывает ветвление; сам системный диалог руками не проверить (B-3) |
| 7 | Два района выбираются, третий нет; «Весь город» снимает остальные | ✓ | `RegistrationViewModel.toggleDistrict`, снимок `A5-location-selected` |
| 8 | Тап по мини-карте → карта с пином | — | экран `MapPick` не написан (0.10) |
| 9 | Отказ в галерее дважды → «Перейти в настройки» | частично | `PermissionGate` готов, экран A8 нет |
| 10 | Три ошибки валидации на шаге «о себе» | частично | `ValidatorsTest` закрывает все три правила, экрана A8 нет |
| 11 | Две категории — кнопка неактивна, три — активна | — | T-29 |
| 12 | Селфи → «Получилось селфи?» → ошибка распознавания | — | T-31 |
| 13 | Существующий аккаунт → сразу карта | ✓ | `AuthBranchingTest.профиль с именем…`, `…повторный вход…` |

### M0 и M7 (сценарии 65–67)

| № | О чём | Состояние | Чем закрыт |
|---|---|---|---|
| 65 | Галерея со всеми компонентами | ✓ | маршрут `debug/uikit`, семь снимков `UiKitScreenshotTest` |
| 66 | Нет кириллических литералов и `Icons.Default` в `ui/` | ✓ | оба `grep` пусты, проверяется каждым коммитом |
| 67 | Поворот и сворачивание не теряют состояние | частично | `OtpStateSaver` для экрана кода; остальные экраны без своего состояния. Полная проверка требует устройства (B-3) |

Сценарии 14–64 относятся к вехам M2–M6 и не начаты.

---

## 0.13 Тесты: что требует 8.2 и что есть

| Требуется 8.2 | Есть | Где | Тестов |
|---|---|---|---|
| `MappingTest` | ✓ | `data/api/MappingTest.kt` | 15 |
| `FormatsTest` | ✓ | `ui/format/FormatsTest.kt` | 20 |
| `ValidatorsTest` | ✓ | `data/validation/ValidatorsTest.kt` | 9 |
| `OtpStateMachineTest` | ✓ | `ui/format/OtpStateMachineTest.kt` | 10 |
| `AuthBranchingTest` | ✓ | `data/auth/AuthBranchingTest.kt` | 8 |
| `ReputationLevelTest` | частично | пороги 70/40 проверяются внутри `MappingTest` | 1 |
| `CtaStateTest` (таблица 4.8) | — | появится с T-50, там же и таблица CTA | 0 |
| Compose UI (`androidTest`) | — | недоступны: блокер B-2 | 0 |
| **Итого юнит** | | | **84** |

Снимки вместо `androidTest`: `UiKitScreenshotTest` (7 наборов компонентов) и
`ScreensScreenshotTest` (15 состояний экранов A1–A7).

---

## 0.14 Пометки в коде: полный список

Ищутся так: `grep -rn "FIGMA-CHECK\|BACKEND-GAP\|CONTENT-GAP\|FIGMA-ASSET" app/src/main/java`.

### FIGMA-CHECK — значение не сверено с нодой (2)

| Где | Что не сверено |
|---|---|
| `ui/components/Controls.kt` | Нода 194:25194 — цвет включённого `PodusheSwitch`. В 4.5 указан `#007AFF`, в инстансе 51×31 он `#282A31`, в инстансе 39×23 — `#007AFF`. Цвет вынесен параметром |
| `ui/components/TabBar.kt` | Нода I287:27893;179:7551 — в 4.5 боковые круги 62 и пилюля 217, в ноде 54 и 210; взята нода, потому что только с ней ряд укладывается в 390 |

### BACKEND-GAP — ручки нет, работает Fake (8)

| Где | Чего не хватает от API |
|---|---|
| `data/model/Models.kt` | пороги уровней репутации — ручка отдаёт только `score` |
| `data/model/Models.kt` | изображения события, теги, «только проверенные», скрытый адрес |
| `data/repo/FakeRepositories.kt` | вопросы при регистрации (чтение и запись) |
| `data/repo/FakeRepositories.kt` | команда события и роли |
| `data/repo/FakeRepositories.kt` | скидки партнёров — по решению владельца ручки не будет |
| `data/repo/FakeRepositories.kt` | локальные поля черновика |
| `data/repo/FakeRepositories.kt` | блокировка и разблокировка пользователей |
| `data/repo/FakeRepositories.kt` | избранные люди — вопрос владельцу, нужен ли раздел |
| `ui/screens/registration/RegistrationViewModel.kt` | обратный геокодер «координаты → город» |
| `ui/screens/event/EventScreen.kt` | `going_preview[]` в модели — блок идущих скрыт, чтобы не выдумывать состав |

### CONTENT-GAP — текст временный (1)

`ui/screens/legal/PolicyScreen.kt` — в макете текст документа это рыба;
финальные политику и соглашение даёт владелец. Файлы `res/raw/policy.md` и
`res/raw/terms.md`.

### FIGMA-ASSET — временная замена ассета (26)

Собраны в двух точках: `ui/components/AppIcons.kt` (41 иконка Material по
таблице 4.6) и `ui/components/Icon3d.kt` в `Chips.kt` (пустые слоты вместо
20 3D-иллюстраций). Плюс `welcome_collage` на A1 — вырезка из рендера, а не
отдельные слои. Снимаются в T-110 после блокера B-1.

---

## 0.15 Воспроизведение окружения и известные ловушки

Всё, что стоит вне репозитория, собрано агентом с нуля. Если рабочее место
меняется — повторить.

```bash
# Android SDK (командные инструменты, платформа 35, build-tools 35)
curl -sSL -o cmdline-tools.zip \
  https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
unzip -q cmdline-tools.zip -d /root/android-sdk/cmdline-tools-tmp
mv /root/android-sdk/cmdline-tools-tmp/cmdline-tools /root/android-sdk/cmdline-tools/latest
yes | /root/android-sdk/cmdline-tools/latest/bin/sdkmanager --sdk_root=/root/android-sdk --licenses
/root/android-sdk/cmdline-tools/latest/bin/sdkmanager --sdk_root=/root/android-sdk \
  "platform-tools" "platforms;android-35" "build-tools;35.0.0"

# JDK 17: в системе стоял только JRE 21, без компилятора
apt-get install -y openjdk-17-jdk-headless
```

Запуск — через `android-events/build.sh` (не в репозитории, создаётся рядом):
он подставляет `ANDROID_HOME`, `JAVA_HOME` и ужимает кучу демонов.

### Ловушки, на которые уже наступили

| Симптом | Причина и что делать |
|---|---|
| `Toolchain … does not provide the required capabilities: [JAVA_COMPILER]` | В системе JRE без `javac`. Ставить именно JDK |
| `Gradle build daemon disappeared unexpectedly` | На машине 7 ГБ, а `gradle.properties` просит 4 ГБ на демон плюс столько же на Kotlin. Передавать `-Dorg.gradle.jvmargs=-Xmx2048m -Dkotlin.daemon.jvmargs=-Xmx1280m --no-parallel`, репозиторный файл не трогать — он рассчитан на CI |
| `lintDushaDebug` падает на манифесте | `PermissionImpliesUnsupportedChromeOsHardware`: к разрешению камеры нужен `<uses-feature android:required="false">`. Добавлено |
| Снимки Paparazzi в разных масштабах | Он подгоняет PNG под контент, поэтому пиксели сопоставимы только внутри одного снимка. Мерить в dp, отталкиваясь от известного элемента |
| Разряды цены не сходились в тестах | В коде оказался неразрывный пробел, а в макете («1 000₽», «1 024 фото») — обычный |
| Figma отвечает `401`, потом `429` | `/v1/me` работает даже при исчерпанной квоте, а `/files` и `/images` — нет. Отличать «нет токена» от «нет квоты» |

---

---

## 1. Цель, границы, не-цели

**Цель.** Все 75 логических экранов макета (10 разделов, 608 фреймов) присутствуют в приложении, выглядят как в макете (композиция, размеры, типографика, цвета, иконки, иллюстрации, тексты) и ведут себя по правилам переходов макета на стороне клиента.

**Что входит.**
- Дизайн-система: токены, 28 компонентов UI-kit с точной геометрией, иконки и 3D-иллюстрации из Figma, пустые состояния, snackbar, системные диалоги, запросы разрешений.
- Навигация на Navigation Compose с типизированными маршрутами, шторками и модалками.
- Все экраны и их состояния, включая ошибки, пустые состояния, пермишены и подтверждения.
- Подключение к **уже существующим** ручкам API там, где они есть (раздел 3.4 и Приложение D).
- Локальные заглушки (`Fake*Repository`, in-memory) там, где ручек нет, с реалистичными данными из макета.

**Не-цели (не делать).**
- Новая серверная логика и новые ручки. Всё, чего нет в API (теги, вопросы при регистрации, команда и роли, «только проверенные», радиус, статус, подписчики, блокировки, QR-механика, распознавание лица), реализуется как UI + локальное состояние в `Fake*`-репозитории с пометкой `// BACKEND-GAP: <что нужно от API>`.
- Интеграции: FCM/пуши (только экран пермишена и локальные тумблеры), платежи, реальная верификация селфи (UI камеры + отправка фото в существующую ручку, без своей проверки), геокодер (поиск места — по `/public/events?q=` и `/public/catalog`, карта — MapLibre как есть).
- iOS (`ios/`), веб, бэкенд (`backend/`) — не трогать.

**Определение «серьёзной бэкенд-логики» для этой работы:** любое изменение вне `android-events/`. Если для экрана нужен запрос, которого нет, — Fake + пометка, а не правка Django.

---

## 2. Окружение, команды, границы

**Проект.** `android-events/` — Kotlin, Jetpack Compose (BOM 2024.12.01), Material 3, `minSdk 28`, `targetSdk 35`, JVM 17, Gradle 8.13. Зависимости уже есть: `navigation-compose 2.8.5` (не используется — использовать), `material-icons-extended`, Retrofit + kotlinx.serialization, OkHttp, DataStore, Coil (+svg), MapLibre 11.5.2. Разрешено добавить: `androidx.camera:camera-*` (селфи), `androidx.activity` PickVisualMedia (уже в activity-compose 1.9), `com.google.zxing:core` или `zxing-android-embedded` (рисование QR), `androidx.compose.material3` ModalBottomSheet (есть в BOM).

**Флейворы.** `local` (127.0.0.1:8000 через `adb reverse tcp:8000 tcp:8000`), `dusha` (https://dusha.uxbrain.ru), `stage`, `prod`. Для разработки — `local` или `dusha`.

**Команды.**
```bash
cd android-events
./gradlew :app:assembleDushaDebug            # сборка (обязательно перед каждым коммитом)
./gradlew :app:installDushaDebug             # на устройство/эмулятор
./gradlew :app:lintDushaDebug                # lint
./gradlew :app:testDushaDebugUnitTest        # юнит-тесты (создать, см. 8.2)
adb exec-out screencap -p > /tmp/<screen>.png   # доказательство для отчёта
```
Тестов в проекте сейчас нет; юнит-тесты кладутся в `app/src/test/java/ru/podushe/app/…`, Compose-тесты — в `app/src/androidTest/…`.

**Git.** Ветка `feature/figma-parity` от `origin/main`. Один коммит — одна задача из раздела 7 (`T-xx`). Формат сообщения как в истории репозитория: `feat(android): <что>` / `fix(android): <что>` по-русски, первая строка ≤ 72 символов, в теле — `Spec: T-xx`, ссылка на ноду. Не пушить без запроса.

**Границы.**
- ✅ Всегда: собирать перед коммитом; тексты только через `res/values/strings.xml`; цвета/типографика только через `PodusheColors`/`PodusheType`; размеры из этой спеки; каждую задачу закрывать скриншотом.
- ⚠️ Спросить: менять DTO/контракт (`data/api/*`), удалять существующие экраны или маршруты, менять зависимости версий, включать RenderEffect-блюр (влияет на производительность карты).
- 🚫 Никогда: править `backend/`, `ios/`, `web-*`; хардкодить фиктивные данные на живых экранах (только через `Fake*Repository`); коммитить токены; менять `applicationId`; ломать `local`-флейвор.

---

## 3. Архитектурные решения (зафиксированы, не пересматривать)

### 3.1 Навигация
Заменить ad-hoc-флаги в `MainActivity.kt` (`showProfile`, `showSearch`, `openEvent`…) на `NavHost` (`navigation-compose`) с типизированными маршрутами (`@Serializable data object/class`) в `ui/navigation/Routes.kt` и одним `AppNavHost`. Таббар — общий скаффолд для трёх корневых стеков; стек назначений сохраняется при переключении табов (`saveState/restoreState`). Шторки — `ModalBottomSheet` (Material 3) внутри маршрута; системные диалоги — `AlertDialog`; action sheet — свой `PodusheActionSheet` (см. 4.6). Полный список маршрутов — раздел 5.

### 3.2 Данные
Схема остаётся: `interface XxxRepository` → `NetworkXxxRepository` (Retrofit) + `FakeXxxRepository` (in-memory), выбор в `AppContainer`. Новые доменные модели вводятся под именами макета: `EventCard` (переименовать `MockEvent` — механический коммит в M1), `Participant`, `Organizer(Role)`, `RegistrationQuestion`, `Invite`, `UserProfile`, `PublicProfile`, `Subscription`, `Notification`, `Discount`. Для того, чего нет в API, — `FakeXxxRepository` хранит состояние в памяти процесса и (где уместно) в DataStore; в UI оно ведёт себя как живое. Каждый такой класс начинается с комментария `// BACKEND-GAP: …`.

### 3.3 Строки, ресурсы, шрифты
- Все пользовательские строки — `res/values/strings.xml`, ключи `screen_element` (`auth_phone_title`, `event_cta_join`). Тексты — дословно из макета (Приложение A / `strings.md`), включая «ё».
- Иконки — SVG из Figma → `res/drawable/ic_<name>.xml` (Vector Asset). Имена — из `assets.json` (`icon-ticket` → `ic_ticket`). 3D-иллюстрации — PNG scale 3 → `res/drawable-xxhdpi/img3d_<name>.png` (и `-xhdpi` scale 2). Пока нет квоты экспорта — временно ближайший `Icons.Outlined.*` с пометкой `// FIGMA-ASSET: icon-…`; 3D — плейсхолдер-круг с эмодзи, тоже с пометкой. Финальная приёмка вехи M0 требует настоящих ассетов.
- Шрифт остаётся Inter variable (решение владельца); веса 400 / 510 / 590 / 650 / 760.

### 3.4 Бэкенд: что уже есть и обязательно используется
Из `backend/nikea-backend/django-main-app/api-schema.yml` (27.08.2026), базовый префикс `/api/v1`:
- События: `GET /public/events?bbox&city&district&from&to&interest&price=free|paid|unknown&q&sort=new|popular|soon&online&limit&offset`, `GET /public/events/{id}`, `GET /events/{id}` (авторизованная: `rsvp{my_status, my_queue_position, seats_left}`, `is_organizer`, `going_preview[]{name, initials, color}`, `sessions[]`), `GET /public/events/calendar`, `GET /public/catalog` (interests с `total`, cities, districts), `GET /public/collections`, `GET /public/collections/{slug}`, `GET /public/stories`, `GET /public/venues/{id}`, `GET /cities`, `GET /events/topics`.
- Участие: `POST/DELETE /events/{id}/rsvp`, `POST /events/{id}/reaction {would_go|not_interested}`, `DELETE …/reaction`, `GET /events/{id}/participants` (going, pending, waitlisted, declined, cancelled), `POST …/rsvp/{user_id}/decide {approve}`, `POST /events/{id}/cancel`, `PATCH /events/{id}` (только title, description, address, seats), `GET /events/{id}/report` (reasons[], already_sent) + `POST` {reason, comment}, `GET/POST /events/{id}/review`, `POST /events/{id}/checkin`, `POST /events/company-invite`, `GET /events/company-invites`, `POST …/{id}/respond`, `GET /events/mine`, `GET /events/mine/summary`, `GET /events/deck`, `GET /events/people?q=` (люди с общих прошедших событий), `GET /events/matches`.
- Черновик: `GET/POST/DELETE /events/draft` (title, description, interests, is_paid, price, starts_at, ends_at, city_name, address, lat, long, seats, rsvp_mode ∈ {auto, selective}, cover_url, cover_theme, cover_color, is_online, online_url, visibility ∈ {public, link}), `POST /events/draft/publish`.
- Пользователь: `GET/PUT /users/me/event-prefs {topics[], districts[]}`, `GET /users/me/reputation` (score, restriction, can_participate), `GET/POST /users/me/notifications`, `GET/POST/DELETE /users/me/calendars {kind: organizer|topic, value}` (+ `agenda[]`), `GET /public/organizers/{user_id}`, `POST /feedback {email, message}`, `GET /users/me/l2-eligibility`.
- Дейтинговые (по kb/30-backend/10, kb/20-product/63): `GET/PATCH /users/profile`, фото профиля, `POST /users/profile/verify/photo`, `GET …/verify/status`, `POST /users/{id}/report`, `POST /auth/otp`, `/auth/otp/verify`, `/auth/token/refresh`, `/auth/logout`.
- Чаты: `GET /api-chats/v1/event-chats`, `GET /event-chats/{id}/messages`, WebSocket `send-text/edit/delete/typing/read`, `POST /api/v1/chat-media` (multipart `photo`).
- Реализовано на бэке локально, на стенде может отсутствовать (kb 75/76/79): фото в чатах, событийные пуши, `POST /users/me/delete` (+ `/cancel`). Использовать, если ручка отвечает; иначе Fake.

**Ловушки контракта (закладывать сразу):** `DELETE /rsvp` отвечает 200 с телом; `/public/events` и `/events` — разные обёртки; `online=1|0`, не `format`; `/cities` отдаёт `id` строкой; координаты в `/events/deck` — строками; `district` — объект `{key, name, zone}`, `category` — `{key, label, color}`; черновик принимает `rsvp_mode` только `auto|selective` — **режима «Без записи» в макете нет, чип удалить**; лимит WS — 5 соединений на человека, сокет живёт только пока открыт экран чата; отказ по состоянию — 409 `{code, detail}` (`rsvp_closed`, `l2_required`).

### 3.5 Разрешения
`ACCESS_FINE/COARSE_LOCATION`, `CAMERA`, `READ_MEDIA_IMAGES` (API 33+) / `READ_EXTERNAL_STORAGE` (≤32), `POST_NOTIFICATIONS` (API 33+) — запрашиваются через `rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission())` ровно в тех местах, где в макете системный диалог; отказ → экран/шторка макета «Разрешите доступ… / Перейти в настройки» (открывает `Settings.ACTION_APPLICATION_DETAILS_SETTINGS`). Фото — `PickVisualMedia`; камера — CameraX `Preview` + `ImageCapture`.

### 3.6 Стекло
`Modifier.liquidGlass(shape, style)` остаётся базой (подложка + обводка + тень). На API 31+ поверх карты и фото добавить `RenderEffect.createBlurEffect(12f, 12f)` на снимке фона под элементом (haze-подход) — только для таббара, шапки карты и стеклянных пар на странице события; ниже 31 — как сейчас. Это единственное место, где допускается «⚠️ спросить» (см. 2) — если производительность карты падает, оставить без блюра.


### 3.7 Кодовые скелеты (обязательные имена и сигнатуры)

Ниже — контракты, на которые ссылаются блоки экранов. Имена файлов и типов фиксированы; тела — на усмотрение агента.

#### 3.7.1 Маршруты — `ui/navigation/Routes.kt`
```kotlin
package ru.podushe.app.ui.navigation

import kotlinx.serialization.Serializable

sealed interface Route

// Вход и регистрация
@Serializable data object Welcome : Route
@Serializable data object Phone : Route
@Serializable data class Otp(val phone: String) : Route
@Serializable data class Policy(val kind: String) : Route           // "privacy" | "terms"
@Serializable data class RegLocation(val step: Int = 1) : Route
@Serializable data class CitySearch(val returnTo: String) : Route   // "reg" | "filters" | "afisha"
@Serializable data class DistrictSearch(val city: String, val returnTo: String) : Route
@Serializable data object MapPick : Route
@Serializable data class RegAbout(val totalSteps: Int = 4) : Route   // 4 в регистрации, 3 из профиля
@Serializable data class RegInterests(val totalSteps: Int = 4) : Route
@Serializable data class RegVerify(val totalSteps: Int = 4) : Route
@Serializable data object Selfie : Route
@Serializable data object VerifySuccess : Route                    // шторка над картой

// Табы
@Serializable data object AfishaMap : Route
@Serializable data object Favorites : Route
@Serializable data object Chats : Route
@Serializable data object Swipes : Route

// Афиша
@Serializable data object AfishaList : Route
@Serializable data object CityAfisha : Route
@Serializable data class Category(val key: String) : Route
@Serializable data class Collection(val slug: String) : Route
@Serializable data object Search : Route
@Serializable data object Going : Route
@Serializable data class Event(val id: String) : Route
@Serializable data class EventRegister(val id: String) : Route
@Serializable data class Chat(val chatId: String) : Route
@Serializable data class ChatPhoto(val chatId: String, val index: Int) : Route
@Serializable data class DirectChat(val chatId: String) : Route

// Создание и кабинет
@Serializable data object Create : Route
@Serializable data class Manage(val id: String) : Route
@Serializable data class ManageEdit(val id: String) : Route
@Serializable data class Participants(val id: String, val tab: String = "pending") : Route
@Serializable data class ParticipantCard(val id: String, val userId: String) : Route
@Serializable data class Questions(val id: String) : Route
@Serializable data class QuestionEditor(val id: String, val questionId: String? = null) : Route
@Serializable data class Organizers(val id: String) : Route
@Serializable data class OrganizerAdd(val id: String) : Route
@Serializable data class OrganizerRole(val id: String, val userId: String) : Route
@Serializable data class Invite(val id: String) : Route
@Serializable data class InviteCompose(val id: String, val userIds: List<String>) : Route
@Serializable data class EventQr(val id: String) : Route

// Профиль, люди, настройки
@Serializable data object Notifications : Route
@Serializable data object Profile : Route
@Serializable data object ProfileEdit : Route
@Serializable data object MyEvents : Route
@Serializable data object ProfileQr : Route
@Serializable data object Discounts : Route
@Serializable data object ProfileComplete : Route                  // → RegAbout(3)…
@Serializable data class User(val id: String) : Route
@Serializable data class Subscriptions(val userId: String, val tab: String = "following") : Route
@Serializable data object Settings : Route
@Serializable data object Support : Route
@Serializable data object Blocked : Route
@Serializable data object UiKitGallery : Route                     // только debug
```
Шторки (`ModalBottomSheet`) — не маршруты, а состояние экрана-хозяина: `FiltersSheet`, `RadiusSheet`, `CalendarSheet`, `PlaceSheet`, `TagsSheet`, `DateTimeSheet`, `ReportSheet`, `ShareSheet`, `StatusMenu`, `LocationSheet`, `RoleSheet`, `DropdownSheet`. Корневой скаффолд: `AppScaffold(navController) { PodusheTabBar visible = currentRoute in setOf(AfishaMap, Favorites, Chats, Swipes, AfishaList, CityAfisha, Category, Going, Profile) }`.

#### 3.7.2 Доменные модели — `data/model/*.kt`
```kotlin
data class EventCard(                       // бывший MockEvent; поля — из EventCard/EventCardDetail api-schema.yml
    val id: String, val title: String, val description: String,
    val coverUrl: String?, val coverColor: Long, val coverEmoji: String?, val coverImages: List<String>,
    val interests: List<String>, val categoryLabel: String?, val categoryColor: String?,
    val startsAt: OffsetDateTime?, val endsAt: OffsetDateTime?,
    val venueName: String?, val address: String?, val districtName: String?, val cityName: String?,
    val lat: Double?, val lng: Double?, val distanceM: Int?,
    val isOnline: Boolean, val onlineUrl: String?,
    val isFree: Boolean?, val priceMin: Double?, val priceMax: Double?, val priceText: String?,
    val goingCount: Int, val goingToday: Int, val seats: Int?, val seatsLeft: Int?, val capacity: Int?,
    val organizer: Person?, val goingPreview: List<Person>, val isOrganizer: Boolean,
    val rsvp: RsvpInfo?, val myReaction: Reaction?, val visibility: Visibility,
    val status: EventStatus,                 // Active | Cancelled | Finished (по датам и cancel)
)
data class Person(val userId: String, val name: String, val initials: String, val color: String,
                  val avatarUrl: String? = null, val reputation: Int? = null, val districtName: String? = null,
                  val commonInterests: Int? = null, val isVerified: Boolean = false)
data class RsvpInfo(val mode: RsvpMode, val myStatus: RsvpStatus?, val queuePosition: Int?, val seatsLeft: Int?)
enum class RsvpMode { Off, Auto, Selective }
enum class RsvpStatus { Pending, Going, Waitlisted, Declined, Cancelled }
enum class Reaction { WouldGo, NotInterested }
enum class Visibility { Public, Link }
enum class EventStatus { Active, Cancelled, Finished }
data class Participant(val person: Person, val status: RsvpStatus, val note: String, val answers: List<Answer>, val createdAt: OffsetDateTime?)
data class UserProfile(val id: String, val name: String, val phone: String?, val photoUrl: String?, val bio: String?,
                       val birthDate: LocalDate?, val gender: Gender?, val isVerified: Boolean,
                       val reputation: Reputation, val districts: List<District>, val topics: List<String>,
                       val relationshipStatus: RelationshipStatus?, val following: Int, val followers: Int?)
data class Reputation(val score: Int, val level: ReputationLevel, val canParticipate: Boolean, val restriction: String?)
enum class ReputationLevel { High, Medium, Low }   // score ≥ 70 / 40..69 / < 40 — BACKEND-GAP: thresholds
enum class RelationshipStatus { Single, InRelationship, Complicated }
data class District(val key: String, val name: String, val total: Int? = null)
data class City(val id: String, val name: String, val hasDistricts: Boolean)
data class Topic(val key: String, val label: String, val bucket: String, val total: Int?, val icon3d: Int?)
data class EventDraft(/* поля EventDraft api-schema + локальные: images: List<Uri>, tags: List<String>,
                         verifiedOnly: Boolean, hideAddress: Boolean, capacityLimited: Boolean */)
data class RegistrationQuestion(val id: String, val type: QuestionType, val text: String, val required: Boolean,
                                val options: List<String>, val multiple: Boolean, val maxAnswers: Int?, val order: Int)
enum class QuestionType { Text, Choice, Link, Checkbox }
data class Answer(val questionId: String, val value: String)
data class TeamMember(val person: Person, val role: TeamRole, val showOnPage: Boolean)
enum class TeamRole { Creator, Manager, CoOrganizer, Host }
data class Invite(val id: String, val event: EventCard, val from: Person, val message: String?, val myResponse: String?)
data class Subscription(val kind: SubscriptionKind, val value: String, val name: String, val initials: String, val color: String)
enum class SubscriptionKind { Organizer, Topic }
data class AppNotification(val id: String, val kind: String, val title: String, val eventId: String?, val eventTitle: String?,
                           val eventCover: String?, val isRead: Boolean, val createdAt: OffsetDateTime?, val inviteId: String?)
data class ChatMessage(val id: String, val frontId: String?, val authorId: String, val authorName: String, val text: String,
                       val photos: List<String>, val sentAt: OffsetDateTime, val mine: Boolean, val status: MessageStatus, val system: Boolean)
enum class MessageStatus { Sending, Sent, Read, Failed }
data class Discount(val id: String, val partner: String, val logoUrl: String?, val offer: String, val condition: String, val promo: String)
```

#### 3.7.3 Репозитории — `data/repo/*.kt` (интерфейс → живая реализация → Fake)
| Интерфейс | Методы | Живая ручка | Fake |
|---|---|---|---|
| `AuthRepository` | `requestOtp(phone)`, `verify(handle, code)`, `logout()`, `isLoggedIn`, `deleteAccount()` | `/auth/*`, `POST /users/me/delete` | `deleteAccount` при 404 |
| `EventsRepository` | `map(bbox, filter)`, `list(filter, sort, page)`, `detail(id)`, `calendar(from,to)`, `catalog(city)`, `collections()`, `collection(slug)`, `stories()`, `venue(id)`, `cities()`, `deck(filter)`, `react(id, reaction)`, `clearReaction(id)`, `join(id, note?)`, `leave(id)`, `reportScreen(id)`, `report(id, reason, comment)`, `reviewScreen(id)`, `review(id, rating, tags)`, `checkin(id)`, `favorites()`, `mine()`, `agenda()` | см. 3.4 | — |
| `DraftRepository` | `load()`, `save(draft)`, `publish()`, `extras(draftId)` (теги, изображения, verifiedOnly, hideAddress) | `/events/draft*` | `FakeDraftExtrasRepository` |
| `ManageRepository` | `participants(id)`, `decide(id, userId, approve)`, `kick(id, userId)`, `cancel(id)`, `edit(id, patch)`, `editExtras(id, extras)` | `/participants`, `/decide`, `/cancel`, `PATCH /events/{id}` | `kick` → decide(false) либо Fake; `editExtras` Fake |
| `QuestionsRepository` | `questions(eventId)`, `save(eventId, list)`, `answers(eventId, userId)`, `submitAnswers(eventId, answers)` | — | **Fake** (`BACKEND-GAP: registration questions`) |
| `TeamRepository` | `team(eventId)`, `add(eventId, userId, role, showOnPage)`, `update(...)`, `remove(...)`, `candidates(eventId, scope, query)` | кандидаты — `/events/people`, `/participants` | **Fake** (`BACKEND-GAP: team roles`) |
| `InvitesRepository` | `send(eventId, userIds, message)`, `incoming()`, `respond(inviteId, accept)` | `/events/company-invite*` | при несовпадении тела — Fake |
| `ProfileRepository` | `me()`, `update(patch)`, `uploadPhoto(uri)`, `verifyPhoto(uri)`, `verifyStatus()`, `prefs()`, `savePrefs(topics, districts)`, `reputation()`, `setStatus(status)`, `subscriptions()`, `subscribe(kind, value)`, `unsubscribe(kind, value)`, `publicProfile(userId)`, `metPeople(query)`, `reportUser(id, reason)`, `block(id)`, `unblock(id)`, `blocked()` | дейтинговые `/users/profile*`, `/users/me/event-prefs`, `/users/me/reputation`, `/users/me/calendars`, `/public/organizers/{id}`, `/events/people`, `/users/{id}/report` | `setStatus`, `block*`, `followers` — **Fake** |
| `NotificationsRepository` | `feed()`, `markRead(ids?)`, `markAllRead()` | `/users/me/notifications` | — |
| `ChatsRepository` | `eventChats()`, `directChats()`, `history(chatId)`, `connect(chatId)`, `send(chatId, text, photos)`, `markRead(chatId)`, `uploadPhoto(uri)` | `/api-chats/v1/*`, WS, `/chat-media` | `directChats` пустой при 404 |
| `SettingsRepository` | `settings()`, `update(patch)` (privacy, push, email, sms), `feedback(email, message)`, `policy(kind)` | `/feedback` | privacy/push/email/sms — **Fake** + DataStore |
| `DiscountsRepository` | `list()` | — | **Fake** (данные ноды 348:46562) |
| `OnboardingFlags` (DataStore) | `registrationDone`, `manageOnboardingSeen`, `swipesTourSeen`, `notificationsAsked`, `verifyPromptShownAt` | — | — |

#### 3.7.4 Базовые composable-контракты — `ui/components/Scaffold.kt`
```kotlin
@Composable fun ScreenScaffold(
    title: String?, onBack: (() -> Unit)?, modifier: Modifier = Modifier,
    rightSlot: (@Composable RowScope.() -> Unit)? = null,   // одна Button-icon или GlassPair
    sheet: Boolean = true,                                    // белый лист r32 от y=124
    scrollable: Boolean = true,
    bottomAction: (@Composable () -> Unit)? = null,          // BottomAction(...)
    dark: Boolean = false,                                   // тёмная шапка (страница события, селфи)
    content: @Composable ColumnScope.() -> Unit,
)
@Composable fun BottomAction(primary: @Composable () -> Unit, secondary: (@Composable () -> Unit)? = null, gradient: Boolean = false)
@Composable fun EmptyState(icon3d: Int, title: String, subtitle: String, action: Pair<String, () -> Unit>? = null)
@Composable fun StepProgress(total: Int, current: Int)
class SnackbarController { fun show(text: String, icon: Int = R.drawable.ic_check, action: Pair<String, () -> Unit>? = null) }
```

#### 3.7.5 Ключи строк — `res/values/strings.xml`
Правило имени: `<экран>_<элемент>[_<состояние>]`, экран — латиницей по id спеки (`a2`, `b10`, `c1`, `h1`) или по имени (`event`, `create`, `profile`), общие — `common_*`. Примеры: `a2_title = "Авторизация"`, `a2_phone_error = "Некорректный номер"`, `a4_limit_title = "Лимит отправок исчерпан"`, `event_cta_join = "Участвовать"`, `event_cta_pending = "Вы подали заявку"`, `common_continue = "Продолжить"`, `common_cancel = "Отмена"`, `common_go_to_settings = "Перейти в настройки"`. Множественные формы — `<plurals>` (`plural_going`, `plural_events`, `plural_common_interests`, `plural_subscribers`). Форматные строки — с позиционными аргументами (`%1$s`).


---

## 4. Дизайн-система

Все значения сняты из дерева нод Figma (заливки, `absoluteBoundingBox`, auto-layout) и обязательны. Файлы: `ui/theme/Color.kt`, `Type.kt`, `Glass.kt`, `ui/components/*`.

### 4.1 Цвета (`PodusheColors`) — существующие токены верны, добавить:
| Токен | Значение | Где |
|---|---|---|
| `TextInverseSecondary` | `#FFFFFF@40%` | подписи на тёмных экранах (уже `TextOnDarkSecondary`) |
| `AccentBubble` | `#007AFF@11%` | своё сообщение в чате, выделение таба |
| `IconTintNeutral` | `#000000@7%` | подложка иконок в строках кабинета, обводка чипов категорий |
| `DangerTint11` | `#F84040@11%` | подложка иконки «Отменить событие» |
| `GoldTint11` | `#DFA107@11%` | подложка иконки «Вопросы/Организаторы» |
| `SuccessTint11` | `#36B554@11%` | бейдж «Создатель», статус «В отношениях» |
| `Scrim` | `#000000@23%` | затемнение под шторками (уже есть) |
| `CoverGradientStart/End` | `#00CCFF` → `#8637C6` | градиент акцентных карточек и фонов без обложки |

Удалить использование `Terracotta` для аватаров-заглушек и точки бейджа (в макете точка бейджа `#282A31@40%`, аватар без фото — серый круг с иконкой пользователя, как в 348:42676).

### 4.2 Типографика (`PodusheType`) — Inter, трекинг −0.6 (кроме 10 и 42)

> **Решение Р-1 (07.09.2026).** Веса **650 и 760** в макете — это
> `SFPro-CondensedSemibold` и `SFPro-CondensedBold`, узкие начертания
> (650 встречается 469 раз — заголовок каждого экрана). Inter вариативен
> только по осям `opsz` и `wght`, оси ширины у него нет. По решению владельца
> тем же двум весам (`Display`, `TitleScreen`) даётся горизонтальное сжатие
> **0.72** — `PodusheType.condensed`. Проверено по рендеру 158:9505: строка
> «Реальные встречи» 281.9 dp против 280 в макете. Остальные веса
> (400 / 510 / 590 / 700) в макете обычные и не сжимаются.
| Стиль | Вес/кегль/интерлиньяж | Использование |
|---|---|---|
| `Display` **(добавить)** | 760 / 42 / 42 | велком, онбординг кабинета («Настраивайте свои событие») |
| `TitleScreen` | 650 / 28 / 32, трекинг −0.4 | заголовки экранов и блоков, цифры OTP, пустые состояния |
| `Title20` **(добавить)** | 590 / 20 / 24 | заголовок Card-event large |
| `Title18` | 590 / 18 / 22 | заголовок шапки, названия в карточках, имена |
| `Subtitle18` **(добавить)** | 510 / 18 / 22 | подзаголовки регистрации, дата на странице события, «Шаг 2 из 4» |
| `Body` | 510 / 16 / 20 | основной текст, лейблы полей |
| `BodyStrong` | 590 / 16 / 20 | кнопки, чипы, активные сегменты |
| `BodyRegular` **(добавить)** | 400 / 16 / 22 | описание события, тексты системных диалогов |
| `Small` | 510 / 14 / 18 | подписи, счётчики символов, ошибки |
| `SmallStrong` | 590 / 14 / 18 | бейджи, малые кнопки |
| `Caption` | 590 / 10 / 12, трекинг 0 | подписи таббара, время в чате |
| `MarkerLabel` **(добавить)** | 700 / 10 / 12 | подпись маркера карты |

### 4.3 Радиусы и отступы
Скругления: 100 % — пилюли (кнопки, поля, чипы-фильтры, сегменты); 32 — лист контента (верхние углы), карточки-плитки профиля, бейджи; 24 — многострочные поля, группы полей, строки-кнопки списка; 20 — обложка события, строки кабинета, snackbar, карточки рельса; 16 — мини-карта, ячейки OTP; 8 — обложка в строке события; 6 — обложка маркера. Отступы: боковой 16 (контент 358), зона действия 24 (кнопка 342), gap между полями 12, между блоками 20–24, внутри карточек 12, паддинг лейбла над полем 12 по горизонтали. Шапка экрана 124 px: статус-бар 54 + 10 + ряд 48 + 12.

### 4.4 Стекло и тени
`GlassStyle.Regular` (#FAFAFA@69–70 %, обводка #FFF@30 %), `Medium` (#F5F5F5@60 %), `Clear` (#FFF@7 % + обводка #FFF@30 %, для тёмных экранов). Тень кнопок 12/`#000@16%`, карточек 8/`#000@12%`, чипов категорий 4/`#000@7%` + обводка `#000@7%`. Snackbar — backdrop-blur 20.

### 4.5 Компоненты: обязательная геометрия и API
Ниже — что должно существовать в `ui/components/` после вехи M0. Сигнатуры — ориентир; параметры можно расширять, размеры менять нельзя.

| # | Composable (файл) | Спека Figma | Что сделать |
|---|---|---|---|
| 1 | `PodusheButton(text, onClick, style = Brand/Primary/Delete/Secondary, size = Large/Small, enabled, leadingIcon)` (`Buttons.kt`) | Large: высота **48**, пилюля, лейбл 590/16, паддинг 20/12, ширина по контейнеру (342 в зоне действия); Small: высота **32**, лейбл 590/14, паддинг 12/8; disabled — белый оверлей 11 %, pressed — 30 %; Secondary — стекло Regular; Delete — `#F84040` с иконкой ✕ слева | поднять высоту 44 → 48, добавить `Small`, `leadingIcon` |
| 2 | `PodusheIconButton(icon, onClick, dark, badge)` | 48×48 круг, стекло (Clear на тёмном), иконка 24 | есть; проверить иконку chevron-left 24 |
| 3 | `GlassPair(left, right, badge)` | 102×48: два слота 36 + разделитель 2×24 `#000@8%`, паддинг 6, gap 8 | вынести из `AfishaMapScreen` в компоненты, использовать на карте, свайпах, странице события (Clear) |
| 4 | `ListRowButton(text, leadingIcon?, danger)` **(новый, = Button-mini)** | 358×44 r24 `#F5F5F5`, паддинг-лево 16, текст 510/16 (красный для «Удалить») | «Добавить организатора», «Добавить вопрос», «Удалить вопрос», «Пригласить участников», «Начать чат», «Отменить событие» |
| 5 | `PodusheTextField(value, onValueChange, label?, placeholder, error?, multiline, maxChars?, leading, trailing)` | однострочное 358×48 (46 на входе) r100 `#F5F5F5`, текст 510/16, плейсхолдер `#282A31@40%`, внутренняя тень 2 `#000@4%`; ошибка — обводка 1 `#F84040` + строка 510/14 `#F84040` под полем; многострочное 358×90/104 **r24** со счётчиком «0/150 символов» 510/14@40 % справа снизу; лейбл над полем 510/14@40 % с паддингом 12 | добавить `label`, `error`, `multiline`, `maxChars`; убрать высоту 120 у пилюли |
| 6 | `SearchField(value, onChange, onCancel?, placeholder)` **(новый)** | 358×46 r100: лупа 20 слева, текст 510/16, крестик `icon-x-mark` справа при непустом; вариант с «Отмена» 590/16 `#007AFF` справа (поле 290 + 60) | поиск, поиск города/районов, участники, организаторы |
| 7 | `OtpField(code, length, error)` | ячейки 84×96 r16 `#F5F5F5`, цифра 650/28, курсор 2×32 `#0088FF`, gap 8; ошибка — обводка `#F84040` на всех ячейках | добавить ошибку; убрать самодельный `NumPad` — системная цифровая клавиатура |
| 8 | `FilterChip(text, active, trailingIcon?)` (= tab-filter) | высота 40, стекло Medium, паддинг 16/8, текст 590/16; активный — `#282A31` с белым текстом; вариант с chevron-down («Рядом ▾») | есть (`FilterTab`); добавить trailing |
| 9 | `CategoryChip(label, icon3d, selected)` **(новый, = tab-category)** | высота 44 r32, белый, обводка `#000@7%`, тень 4; паддинг 6/4/16/4, 3D-иконка 36 + gap 6 + текст 590/16; выбранный — `#282A31`, белый текст, иконка `icon-check` 20 справа | регистрация/интересы, фильтры, создание события, поиск |
| 10 | `PodusheTabBar` | 390×95, паддинг 20/16/20/24; пилюля стекла 217×62 с тремя `tab-item` 54 (иконка 24 + gap 2 + подпись 590/10, паддинг 8/6), выделение `#007AFF@11%` r100 + синий цвет; боковые круги стекла **62** с иконкой 24 (`icon-cards`, `icon-plus`); иконки `ic_ticket`, `ic_heart`, `ic_chat_bubble_oval` | боковые 54 → 62, нижний паддинг 24, иконки из Figma |
| 11 | `AccentEventCard(event)` | 300×228: обложка 300×170 r20 (градиент `#00CCFF→#8637C6` под фото), на обложке бейдж категории (белый, `+1` при нескольких), заголовок 590/18 (1 строка), мета 510/16@40 % «9.3 км от вас · Завтра» | бейдж на обложке, мета: расстояние · день |
| 12 | `EventRow(event, onDark, trailing)` (= Default-card-event) | 358×108: обложка 84×84 r8; справа колонка: бейдж 26, заголовок **590/18** 1–2 строки, «2.4 км от вас» с `icon-map-pin` 16, строка организатора (аватар 20 + имя 510/16@40 %); gap 12; варианты: без организатора (52 — карточка в шапке чата: обложка 52, заголовок 590/18, «Уже завтра в 10:00»), с сердцем справа (избранное), с датой «29 июля · 11:00» (поиск) | заголовок 16 → 18, мета: расстояние + организатор, `trailing`-слот |
| 13 | `Badge(text, icon?, tone = Light/Dark/Success/Purple/Danger)` | высота 26 r32, паддинг 6/2/12/2, gap 4; светлый `#F5F5F5`, тёмный `#FFF@15%`, тинты 11 %; текст 590/14; на странице события — 30 px с `icon-hashtag` 18 и паддингом 6/6/12/6 | добавить `icon`, `tone`, размер |
| 14 | `OrganizerRow(name, avatar, verified, onClick)` | 358×36, аватар 32, имя 510/16, chevron-right 20; gap 8 | есть |
| 15 | `PodusheSwitch` | 51×31, кружок 27 белый с тенью; on — `#007AFF`(сверить с Ui-kit «Switch»; сейчас `#36B554`) | сверить цвет |
| 16 | `SegmentedControl(options, selected)` | 358×40 r100 `#F5F5F5`, паддинг 8/4; активный — белая пилюля с тенью 8, текст 590/16; неактивные 510/16 | есть — **использовать** |
| 17 | `PodusheSnackbar(text, icon = check)` **(новый)** | 342×50 r20, тёмная подложка `#282A31@80%` + blur 20, паддинг 12, иконка 20 + текст 590/16 белый; показывается над таббаром 3 с | «Добавлено в избранные», «Заявки приняты», «Промокод скопирован», «Ссылка на профиль скопирована», «Событие завершено» |
| 18 | `ChatBubble(message, mine)` | max 270; r20, «хвост» r2 (левый нижний у чужого, правый нижний у своего); чужое `#F5F5F5`, своё **`#007AFF@11%`**, текст 510/16 `#282A31`; имя автора над чужим 510/16@40 %; время 590/10@40 % справа снизу; статус у своего: `icon-clock` (отправляется), одна галочка (доставлено), `icon-double-check` (прочитано), красный `!` + «Повторить» (ошибка); паддинг 8, gap 6 | переписать |
| 19 | `MessageBar(text, onSend, onAttach)` **(новый, = Masseg bar)** | 390×95 как таббар: стекло-круг 44 `icon-paper-clip` · стекло-поле 230×44 (плейсхолдер «Сообщение» 510/16@40 %) · стекло-круг 44 `icon-paper-airplane` | заменить белую полосу |
| 20 | `EventMarker`/`ClusterMarker` (bitmap в `EventMap.kt`) | маркер 110×110: обложка 44×44 r6 с белой обводкой 2, подпись 700/10 в 2 строки `#282A31`; кластер — стопка 2–3 обложек со сдвигом + подпись «+ ещё N» | кластер: стопка + «+ ещё N» вместо кружка |
| 21 | `UserCard(user, subtitle1, subtitle2, trailing)` **(новый, = Card-user-save)** | 358×70: аватар 48 круг, имя 590/18, «Хамовники» с `icon-map-pin`, «3+ общих интереса» 510/16@40 %; справа слот: ✓/✕ (круги 32 стекло), сердце, чекбокс 24 | участники, организаторы, приглашения, избранные-люди, похожие интересы |
| 22 | `CabinetRow(icon, title, subtitle, badge?, tint, onClick)` **(новый, = Event controls)** | 358×64 r20 `#F5F5F5`: иконка 36 r24 на тинте (`#000@7%` / `#F84040@11%` / `#DFA107@11%`), заголовок 510/16, подпись 510/14@40 %, бейдж-число (синий круг 20, 590/16 белый), chevron 20; группы из нескольких строк — один контейнер r20 с разделителями `#000@7%` с отступом 64 | экран «Управление» |
| 23 | `QuestionCard(question, type, required, dragHandle)` **(новый)** | 358×66/101, паддинг 16/12: текст вопроса 510/18, подпись «Текст · Необязательно» 510/14@40 %, drag-handle `icon-bars` слева при ≥2 вопросах | конструктор вопросов |
| 24 | `PodusheCheckbox` 24 r6 (`#007AFF` с галочкой), `PodusheRadio` 24, `PodusheSlider` (358×44 r24 `#007AFF@11%`, метки 2/10/25/50/100+), `CalendarGrid` (ячейки 48×48, заголовок месяца 590/18, дни недели 510/16@40 %, выбранный день — синий круг, диапазон — тинт), `WheelDateTimePicker` (три барабана как iOS, шрифт 400/22 в центре) | **новые** |
| 25 | `PodusheActionSheet(items, cancel)` | как iOS: список действий 510/17 на белом r14, «Отмена» отдельной кнопкой; затемнение `Scrim`; варианты для фото: «Сделать фото» / «Выбрать из галереи» | **новый** |
| 26 | `PodusheAlert(title, message, actions)` | системный `AlertDialog` M3 с текстами макета (заголовок 590/17, текст 400/14) | использовать |
| 27 | `DropdownSheet(options, selected)` **(новый, = «Выпадающий список»)** | шторка со списком радио-опций 510/16 и «Применить» | настройки, статус |
| 28 | `EmptyState(icon3d, title, subtitle, action?)` **(новый)** | 3D-иконка 96, заголовок 650/28 по центру, подпись 510/16@40 % по центру, необязательная кнопка; вертикально по центру области | все пустые состояния (сейчас эмодзи 64 + 18) |
| 29 | `ScreenScaffold(title, onBack, rightSlot, sheet = true, bottomAction)` **(новый)** | фон `#F5F5F5`; шапка 124: `PodusheIconButton` chevron-left на (16, 64), заголовок 590/18 **по центру**, правый слот 48; контент — белый лист с верхними углами r32 от y=124 (если `sheet`); `bottomAction` — зона 96 с паддингом 24 и кнопкой Large | обёртка для всех экранов кроме карты/события |
| 30 | `BottomAction(primary, secondary?)` | зона 96 (паддинг 24), на тёмном — градиент к `#232B48`; вторая кнопка слева стеклом (115×48) при `secondary` | вынести из экранов |
| 31 | `StepProgress(total, current)` **(новый)** | 358×6: сегменты r32, активные `#282A31`, остальные `#000@7%`, gap 6 | регистрация «Шаг N из 4», мастер |
| 32 | `Avatar(photo, size, ring, verified)` **(новый)** | круг; `ring` — градиентное кольцо 3 px (`#8637C6→#00CCFF`) для подтверждённых; `verified` — зелёный бейдж-галочка 24 справа снизу; без фото — `#F5F5F5` с `icon-user` | профиль, участники, организаторы |

**Приёмка дизайн-системы (веха M0):** в debug-сборке есть маршрут `debug/uikit` (экран-галерея), где каждый компонент показан во всех состояниях рядом с подписью ноды Figma; скриншот галереи прикладывается к отчёту.

### 4.6 Иконки и иллюстрации
Экспортировать из Figma (`assets.json`): 69 иконок (`icon-*`, `heroicons-*`) как SVG 24 → VectorDrawable `ic_*`; 20 3D-иллюстраций (`3D-icon-*`) как PNG ×3 → `img3d_*`. До экспорта — соответствие Material (временно): `icon-chevron-left → Icons.Outlined.ChevronLeft` (не `ArrowBack`), `icon-ticket → ConfirmationNumber`, `icon-heart → FavoriteBorder`, `icon-chat-bubble-oval-left-ellipsis → ChatBubbleOutline`, `icon-cards → Style`, `icon-plus → Add`, `icon-filter → Tune`, `icon-magnifying-glass → Search`, `icon-bell-alert → NotificationsNone`, `icon-map-pin → LocationOn`, `icon-hashtag → Tag`, `icon-share → IosShare`, `icon-Flag → Flag`, `icon-cog-6-tooth → Settings`, `icon-qr-code → QrCode`, `icon-pencil → Edit`, `icon-paper-clip → AttachFile`, `icon-paper-airplane → Send`, `icon-NavigationArrow → NearMe`, `icon-calendar → CalendarMonth`, `icon-x-mark → Close`, `icon-check → Check`, `icon-trash → DeleteOutline`, `icon-bars → DragHandle`, `icon-eye → Visibility`, `icon-lock-closed → Lock`, `icon-user-group → Group`, `icon-user-plus → PersonAdd`, `icon-banknotes → Payments`, `icon-shield-exclamation → GppMaybe`, `icon-clock → Schedule`, `icon-double-check → DoneAll`.


### 4.7 Форматирование данных и склонения (единый модуль `ui/format/Formats.kt`)
| Что | Правило | Примеры |
|---|---|---|
| Дата события в карточках | «Сегодня» / «Завтра» / «29 июля» (день + месяц в родительном, без года, если текущий год) + при необходимости « · 11:00» | «Завтра», «29 июля · 11:00» |
| Заголовок дня в группах | день 590/18 + « / » + название дня недели 400/18@40 % строчными | «Сегодня / четверг», «24 июля / среда» |
| Дата на странице события | `d MMMM, HH:mm - HH:mm` (дефис с пробелами), год только если не текущий; без `ends_at` — `d MMMM, HH:mm` | «14 марта, 10:00 - 18:00» |
| Дата в форме создания | `d MMMM yyyy г., HH:mm` для «Начало», `HH:mm` для «Конец» | «27 июля 2026 г., 11:00» / «12:00» |
| Относительное время в шапке чата | «Сегодня в HH:mm», «Уже завтра в HH:mm», иначе «d MMMM в HH:mm» | «Уже завтра в 10:00» |
| Время уведомления | «только что», «N мин назад», «N ч назад», «вчера», «d MMM» | как сейчас |
| Расстояние | `distance_m` < 1000 → «{n} м от вас» (округление до 50); ≥ 1000 → «{x.y} км от вас» (одна десятичная, точка); null → не показывать | «500 м от вас», «2.4 км от вас», «11.1 км от вас» |
| Цена | `is_free` → «Бесплатно»; `price_text` как есть; `price_min` → «от {n}₽» (без пробела перед ₽, разряды пробелом); зачёркнутая старая цена «1 000₽» | «от 750₽», «1 000₽» |
| Счётчик идущих | «{n} Идёт» (в макете без склонения — сохранить) | «66 Идёт» |
| «и ещё N» | `going_count − показанных` | «Данил Колбасенко, Андро и ещё 64» |
| Общие интересы | plurals: «1 общий интерес», «2 общих интереса», «5 общих интересов»; «3+» — если ≥ 3 и точное число неизвестно | «3+ общих интереса» |
| Счётчики категории | plurals «событие/события/событий», «подписчик/подписчика/подписчиков» | «32 Событий · 342 Подписчика» (в макете капитализация с большой — сохранить как в ноде) |
| Счётчик символов | «{n}/{max} символов»; для описания события до 30: «{n}/1000 · минимум 30 символов» | «113/1000 символов» |
| Телефон | «+7 (912) 888-92-62» | |
| Таймер | `m:ss` для 50 с; `mm:ss` для 15 мин | «0:50», «14:59» |
| Имя в карточках | «Имя Ф.» (фамилия инициалом) там, где так в макете (списки), полное — на профиле и странице события | «Мария А.», «Данил Колбасенко» |

### 4.8 Таблицы состояний

**CTA страницы события** (вход: `is_organizer`, `status`, `rsvp.mode`, `rsvp.my_status`, `seats_left`, наличие вопросов):
| Условие (первое подходящее сверху) | Кнопка | Стиль | Вторичная |
|---|---|---|---|
| `is_organizer` | «Управлять» | Brand | ссылка «Отменить событие» красным над кнопкой |
| `status = Cancelled` | «Событие отменено» | Delete, disabled, `icon-x-mark` | — |
| `status = Finished` | «Оставить отзыв» (если `can_review`) иначе кнопки нет | Secondary | пилюля «✓ Событие завершено» на мини-карте |
| `my_status = Going` | «Вы идёте» | Primary + `icon-check` | «Перейти в чат» (стекло 115×48), если чат есть |
| `my_status = Pending` | «Вы подали заявку» | Primary, disabled | — |
| `my_status = Declined` | «Заявка отклонена» | Primary, disabled | — |
| `my_status = Waitlisted` | «В листе ожидания · {queue}» | Primary, disabled | — |
| `seats_left = 0` | «Достигнут лимит участников» | Primary, disabled | — |
| `mode = Off` | «Без записи» — кнопки нет, подпись «Вход свободный» | — | — |
| `mode = Selective` или есть вопросы | «Подать заявку» → D3 | Brand | — |
| иначе | «Участвовать» | Brand | — |
Переход `Pending → Going` (одобрили) показывать как «Заявка одобрена» 3 с, затем «Вы идёте» (по данным при следующем открытии/обновлении).

**OTP (A4):** `Idle(timer=50) → Entering → Verifying → Success | Error(attempts+1) ; attempts ≥ 5 → Limited(timer=900) → (timer=0) Idle`; повторная отправка сбрасывает `attempts`.

**Ветвление после входа (A14):** `verify OK → profile = GET /users/profile; prefs = GET /users/me/event-prefs; isNew = accountState?.isNew ?: (profile.name.isBlank() && prefs.topics.isEmpty()) && !flags.registrationDone → isNew ? RegLocation : AfishaMap`.

**Статус сообщения в чате (B11):** `Sending (clock, alpha .6) → Sent (check) → Read (double-check синий)`; таймаут 10 с без эха → `Failed` («Не отправлено · Повторить»); повтор → `Sending`.

**Уведомления (F1) по `kind`:** `rsvp_confirmed` — обложка события, «Вы записаны»; `rsvp_approved` — «Заявка одобрена»; `rsvp_declined` — «Заявка отклонена»; `event_reminder` — «Напоминание»; `event_updated` — «Событие изменилось»; `event_cancelled` — «Событие отменено»; `daily_digest` — иконка приложения; `company_invite` — аватар отправителя + кнопки «Принять / Отклонить»; `chat_message` — обложка события; `team_invite` (локальный) — аватар + «Принять / Отклонить». Заголовок берётся из `title` бэка, подзаголовок — `event_title`.

**Разрешения:** гео — A5 (шаг 1), B1 (кнопка «к моей локации», чип «Рядом»), A6/B4 («Определить по геолокации»); камера — A8/H5 (фото), A11/H6 (селфи), C1 (изображения), B11 (фото в чат); галерея — A8/H5, C1, B11; уведомления — A13, J1 (switch Push). Отказ: системный диалог отказа → при повторной попытке (`shouldShowRequestPermissionRationale = false`) — action sheet макета «Разрешите доступ… / Перейти в настройки / Отмена».

**Репутация:** `score ≥ 70 → High («Высокая», #36B554)`, `40..69 → Medium («Средняя», #DFA107)`, `< 40 → Low («Низкая», #F84040)`; `can_participate = false` → на профиле подпись «Участие ограничено до {restricted_until}» под репутацией.

---

## 5. Навигационная модель

Один `NavHost`; корневой скаффолд с таббаром для трёх табов. Маршруты (`ui/navigation/Routes.kt`, `@Serializable`):

| Маршрут | Экран (раздел) | Откуда | Таббар |
|---|---|---|---|
| `auth/welcome` | A1 | старт без токена, выход | нет |
| `auth/phone`, `auth/otp` | A2, A4 | велком → номер → код | нет |
| `auth/policy` | A3 | ссылка на вводе номера | нет |
| `reg/location`, `reg/city-search`, `reg/districts`, `reg/map-pick` | A5, A6, A7 | после OTP у нового аккаунта (`account_state.is_new` или пустой профиль — см. A14) | нет |
| `reg/about`, `reg/interests`, `reg/verify`, `reg/selfie`, `reg/success` | A8–A12 | шаги 2–4 | нет |
| `main/afisha` (корень таба 1) | B1 карта | по умолчанию после входа | да |
| `afisha/list` | B7 | «Смотреть списком», свайп шторки вверх | да |
| `afisha/city` | B14 | «Афиша города ›» | да |
| `afisha/category/{key}` | B9 | бейдж категории, плитка интереса | да |
| `afisha/search` | B6 | лупа в шапке | нет |
| шторки на карте: `filters`, `radius`, `calendar` (B3, B2, B5); экраны `filters/city`, `filters/districts` (B4) | | кнопка фильтров, «Рядом ▾», «Период», «Другой город/районы» | — |
| `going` | B13 | блок «Куда иду», плитка профиля | да |
| `event/{id}` | B10, C3 | отовсюду | нет (полноэкранный) |
| шторки события: `report`, `share`, `leave-confirm` | | «Пожаловаться», «Поделиться», «Отмена участия» | — |
| `event/{id}/register` | D3 | «Подать заявку» при вопросах | нет |
| `chat/{chatId}` | B11 | таб «Чаты», кнопка на событии, кабинет | нет |
| `chat/{chatId}/photo/{i}` | B11 просмотр фото | тап по фото | нет |
| `create` | C1 | «+» в таббаре | нет |
| шторки создания: `place` (C2), `tags`, `interests`, `datetime` | | поля формы | — |
| `manage/{id}` | C5 (+ онбординг C6 при первом входе) | «Управлять» на своём событии, «Мои события» | нет |
| `manage/{id}/edit`, `/participants`, `/participant/{uid}`, `/questions`, `/questions/new`, `/questions/{qid}`, `/organizers`, `/organizers/add`, `/organizers/{uid}`, `/invite`, `/invite/compose` | C7–C11 | пункты меню | нет |
| `manage/{id}/qr` | C4 | «Поделиться → QR» | нет |
| `main/favorites` (таб 2) | G1, G2 | таббар | да |
| `main/chats` (таб 3) | I1 | таббар | да |
| `notifications` | F1 | колокольчик | нет |
| `profile` | H1 | аватар в шапке карты/свайпов | да (в макете таббар виден) |
| `profile/edit`, `profile/my-events`, `profile/history`, `profile/qr`, `profile/discounts`, `profile/complete` (3 шага), `profile/selfie` | H2–H8 | плитки и кнопки профиля | нет |
| шторки профиля: `status`, `location` | H7 | чипы | — |
| `user/{id}`, `user/{id}/subscriptions` | I2, I3 | организатор, участник, «Похожие интересы» | нет |
| `dm/{chatId}` | I4 | «Написать» | нет |
| `settings`, `settings/support`, `settings/policy`, `settings/terms`, `settings/blocked` | J1–J5 | шестерёнка в профиле | нет |
| `swipes` | E1 | круглая кнопка слева в таббаре (переключатель) | да |
| `debug/uikit` | галерея | только debug | нет |

Правила: системная «назад» = стрелка в шапке; после публикации события стек `create` заменяется на `event/{id}`; после выхода стек очищается до `auth/welcome`; deep-link `polubvi://events?event_id=` → `event/{id}`; открытие события из уведомления — по `event_id`.


---

## 6. Экраны

Формат каждого блока: **Figma** (ноды: базовая + состояния) · **Сейчас** (файл, статус по аудиту) · **Маршрут** · **Макет** (сверху вниз, размеры в px = dp) · **Тексты** (дословно; ключи strings.xml придумывает агент по правилу `screen_element`) · **Состояния** · **Поведение** · **Данные** · **Приёмка**. Приоритет: P0 — без него приложение не соответствует макету принципиально; P1 — заметно; P2 — детали. **Редлайн** — ссылка на точную геометрию всех блоков экрана в `docs/figma/redlines.md` (172 ноды разделов A–C: координаты, размеры, радиусы, заливки, паддинги auto-layout, стили текста, входящие/исходящие переходы); числа в тексте блока — выдержка из редлайна, при расхождении верен редлайн. Геометрия шапок, листа и зоны действия одинакова везде и описана в 4.5 (№ 29–30), ниже не повторяется.

### 6.A Вход, регистрация, верификация (Figma 112:160)

#### A1 · Экран входа (велком) — P0
**Figma:** 158:9505. **Сейчас:** `AuthScreens.kt:61 WelcomeScreen` — заглушки вместо коллажа. **Маршрут:** `auth/welcome`.
**Редлайн:** `docs/figma/redlines.md` → раздел «158:9505» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** фон `#F5F5F5` с тремя цветными пятнами (`#85DADE@30%`, `#933ED8@40%`, `#AD6DE1@40%`, крупные размытые эллипсы за коллажем, см. ноду); коллаж из двух фотокарточек с наклоном (225×425 r13 на (43, 89) и 238×410 r15 на (115, 153)) + круглое фото 120 r30 на (11, 446) + 3D-иконки (гантеля 194×194 на (197, 36), пазл) + стеклянная пилюля 163×83 r37 на (211, 489) с `img3d_pet` 47 и текстом «Питомцы» 590/18 + три белые звёздочки-искры. Ниже: заголовок `Display` 760/42 на y=608 (две строки; вторая строка «рядом с тобой» цветом `#282A31@40%`), подзаголовок 510/18@40 % на y=704, кнопка «Войти» Brand Large 342×48 на y=772.
**Тексты:** «Реальные встречи / рядом с тобой», «Находи встречи, заводи знакомства / и открывай новые хобби.», «Войти», «Питомцы».
**Данные:** фото коллажа — экспорт из ноды (image fills `image 42` и т. п.) в `drawable-nodpi/welcome_*.jpg`.
**Поведение:** «Войти» → `auth/phone`. При наличии токена экран не показывается (как сейчас).
**Приёмка:** [ ] коллаж, 3D-иконки и пилюля из макета, не плашки; [ ] вторая строка заголовка серая; [ ] кнопка 48 с отступами 24.

#### A2 · Ввод номера — P0
**Figma:** 260:24430 (пусто), 261:25228 (заполнено), 582:52876 (ошибка), 579:52473 (заблокирован). **Сейчас:** `PhoneScreen` — частично. **Маршрут:** `auth/phone`.
**Редлайн:** `docs/figma/redlines.md` → раздел «260:24430» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(title = «Вход», sheet = true)`; в листе блок заголовка (паддинг 24/16/16, gap 6): «Авторизация» 650/28, «Введите номер телефона для входа / в мобильное приложение» 510/16@40 %; далее (паддинг 16, gap 32): поле 358×46 с префиксом «+7» 510/16 и плейсхолдером «(000) 000-00-00», затем блок (gap 16): кнопка «Получить код» Brand Large, юридический текст 510/14@40 % по центру с двумя ссылками цветом `#007AFF`. Системная цифровая клавиатура.
**Тексты:** «Вход», «Авторизация», «Введите номер телефона для входа в мобильное приложение», «+7», «(000) 000-00-00», «Получить код», «Авторизуясь, вы принимаете условия Политики конфиденциальности и Соглашение о получении рекламных рассылок», «Некорректный номер», «Аккаунт заблокирован», «Если вы считаете, что это ошибка, свяжитесь с поддержкой.», «Связаться с поддержкой».
**Состояния:** пусто (кнопка disabled) · заполнено · ошибка валидации: обводка поля `#F84040` + «Некорректный номер» 510/14 красным под полем, кнопка disabled · заблокирован: под полем «Аккаунт заблокирован» красным + «Если вы считаете…» 510/14@40 %, кнопка заменяется на «Связаться с поддержкой» (Brand) → `settings/support`.
**Поведение:** валидация локально при потере фокуса/вводе 10 цифр (маска как сейчас); ссылки кликабельны → `auth/policy` (обе ведут на политику, вторая — на тот же экран с заголовком «Соглашение»); ответ `/auth/otp` с ошибкой аккаунта (`account_state.blocked` или 403 с `code=blocked`) → состояние «заблокирован»; прочие ошибки сети — snackbar «Не получилось отправить код» (единственный текст не из макета, допускается).
**Данные:** `POST /auth/otp`. **Приёмка:** [ ] заголовок шапки по центру; [ ] 4 состояния; [ ] ссылки открывают A3; [ ] кнопка 48.

#### A3 · Политика конфиденциальности / Соглашение — P1
**Figma:** 261:26902. **Сейчас:** нет. **Маршрут:** `auth/policy` (тот же экран используется из настроек J3).
**Редлайн:** `docs/figma/redlines.md` → раздел «261:26902» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(title = «Политика»)`, лист с текстом 510/16 `#282A31`, паддинг 24/16, прокрутка. **Тексты:** заголовок «Политика»; тело — текст документа из ноды (в макете рыба «Настоящее Пользовательское соглашение регулирует отношения между ООО «Название»…»; положить в `res/raw/policy.md` и `terms.md`, пока владелец не даст финальный текст — пометка `// CONTENT-GAP`).
**Приёмка:** [ ] открывается из A2 и из J3; [ ] прокручивается; [ ] назад возвращает на исходный экран.

#### A4 · Ввод кода из СМС — P0
**Figma:** 261:26987 (пусто), 261:27203 (заполнено), 261:27297 (ошибка), 592:5040 (лимит), 261:27386. **Сейчас:** `OtpScreen` — своя клавиатура, нет лимита и таймера. **Маршрут:** `auth/otp`.
**Редлайн:** `docs/figma/redlines.md` → раздел «261:26987» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Вход»)`; «Код из смс» 650/28; «Код отправлен на номер / +7 (912) 888-92-62» 510/16@40 %; ряд `OtpField` (6 ячеек — живой код шестизначный; ширину делит ряд, gap 8, высота 96); кнопка «Войти» Brand Large; строка таймера «Отправить код повторно 0:50» 510/14@40 % по центру. Системная клавиатура (`KeyboardType.NumberPassword`, автозаполнение SMS через `autofill`).
**Тексты:** «Код из смс», «Код отправлен на номер {phone}», «Войти», «Отправить код повторно {m:ss}», «Отправить код повторно» (кнопка после 0:00, вторичная), «Неправильный код авторизации», «Получить код заново», «Лимит отправок исчерпан», «Повторить попытку через {mm:ss}», «Изменить номер».
**Состояния:** пусто (кнопка disabled) · заполнено · ошибка: ячейки с красной обводкой, «Неправильный код авторизации» 510/14 красным на месте таймера, кнопка меняется на «Получить код заново» (Brand) · лимит (после 5 неверных вводов подряд — счётчик локальный, либо 429/`code=otp_limit` от бэка): «Лимит отправок исчерпан» красным, кнопка disabled с текстом «Повторить попытку через 14:59» (таймер 15 минут), под ней вторичная стеклянная «Изменить номер» → назад на A2.
**Поведение:** таймер 50 с идёт с момента отправки; по нулю строка превращается в текстовую кнопку → `POST /auth/otp` повторно, таймер заново; при вводе шестой цифры проверка сразу (как сейчас); верный код → A14 (ветвление).
**Данные:** `POST /auth/otp/verify`, `POST /auth/otp`. **Приёмка:** [ ] системная клавиатура, `NumPad` удалён; [ ] таймер живой; [ ] 4 состояния; [ ] после 5 ошибок — лимит.

#### A5 · Регистрация 1/4 «Где вы обычно находитесь?» — P0
**Figma:** 597:55329 (Москва, гео), 732:76522, 597:56595 (выбрано 3 района), 597:56915 / 731:76129 / 609:75160 (СПб), 730:75312 (Пермь — без районов), 610:75380 (системный запрос гео), 730:75625 / 731:75848 (выбор точки на карте). **Сейчас:** нет. **Маршрут:** `reg/location`.
**Редлайн:** `docs/figma/redlines.md` → раздел «597:55329» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** шапка без заголовка: слева `PodusheIconButton` назад (16, 66), справа стеклянная «Пропустить» 110×44 (только на шагах 2–4; на шаге 1 нет «Пропустить»); лист r32 от y=128: `StepProgress(4, 1)` 358×6 на y=152; «Шаг 1 из 4» 510/18@40 % (y=190), «Где вы обычно находитесь?» 650/28, «Выберите 1-2 района вашего города, чтобы лента подстроилась под вас» 510/16@40 %; блок «Город» (лейбл 510/16@40 %): карточка 358×~190 с мини-картой (MapLibre, статичная, центр города, пин) и строкой «Москва» 590/16 + «Определили по геопозиции» 510/14@40 % + «Изменить» 510/16 `#007AFF` справа; блок «Районы» (лейбл): чипы `FilterChip` в 3 ряда gap 8: «Весь город», «Хамовники», «Патриаршие пруды», «Тверской», «Китай-город», «Замоскворечье» (для Москвы; для СПб — «Центральный», «Петроградский», «Василеостровский», «Адмиралтейский», «Приморский»), выбранные тёмные; строка «Другие районы ›» 510/16@40 % с chevron; кнопка «Продолжить» Brand Large (disabled, пока не выбран город). Маленький город (нет районов в `/public/catalog`): блока «Районы» нет, лист короче.
**Тексты:** «Шаг 1 из 4», «Где вы обычно находитесь?», «Выберите 1-2 района вашего города, чтобы лента подстроилась под вас», «Город», «Определили по геопозиции», «Изменить», «Районы», «Весь город», «Другие районы», «Продолжить», «Где вы находитесь?», «Выбранное место», «Выбрать это место».
**Состояния:** запрос гео (системный диалог) → разрешено: город по координатам (`/cities` ближайший; если геокодера нет — Москва + пометка `BACKEND-GAP: reverse-geocode`), запрещено → `reg/city-search`; районы: 0–2 выбранных («Весь город» снимает остальные); «Другие районы» → A7; тап по мини-карте → полноэкранная карта `reg/map-pick` с заголовком «Где вы находитесь?», перетаскиваемым пином, шторкой «Выбранное место / Каменноостровский проспект, 44Б 650/28 / Петроградская, Санкт-Петербург 510/18@40 % / кнопка «Выбрать это место»» (адрес — из `/public/events?bbox` ближайшего события или строка координат; `BACKEND-GAP: reverse-geocode`).
**Данные:** `GET /cities`, `GET /public/catalog?city=`, `PUT /users/me/event-prefs {districts}` при «Продолжить». **Приёмка:** [ ] системный запрос гео; [ ] обе ветки; [ ] лимит 2 района; [ ] «Весь город» эксклюзивен; [ ] Пермь без районов; [ ] карта с пином.

#### A6 · Поиск города — P0
**Figma:** 597:56057, 732:76379 (отказ гео → action sheet), 597:56806 (выбран), 597:56259 (ввод «Мос»), 597:56371 (не найдено). **Маршрут:** `reg/city-search` и `filters/city`.
**Редлайн:** `docs/figma/redlines.md` → раздел «597:56057» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Поиск города»)`; в листе `SearchField` (плейсхолдер «Поиск города») на y=134; список городов строками 44 (текст 590/16, разделители 1 px `#000@7%` с отступом 16), выбранный — с `icon-check` справа; внизу липкая стеклянная кнопка «Определить по геолокации» 342×48 (Secondary). Пусто: `EmptyState(img3d_watch, «Ничего не найдено», «Попробуйте изменить запрос / или проверить его написание.»)`.
**Поведение:** фильтр по подстроке; тап → выбор и возврат; «Определить по геолокации» → запрос гео; отказ → `PodusheActionSheet`(«Разрешите доступ к геопозиции», «Чтобы автоматически определить ваш город и показывать события рядом, разрешите доступ к геопозиции в настройках устройства.», «Перейти в настройки», «Отмена»).
**Данные:** `GET /cities`. **Приёмка:** [ ] 5 состояний; [ ] action sheet отказа.

#### A7 · Поиск районов — P0
**Figma:** 609:74123, 609:74208. **Маршрут:** `reg/districts`, `filters/districts`.
**Редлайн:** `docs/figma/redlines.md` → раздел «609:74123» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Поиск района»)`; `SearchField` («Поиск района»); список строк 65 px: название 590/16 + `PodusheCheckbox` справа; внизу две кнопки: «Сбросить» (Secondary 167×48) и «Применить» (Brand 167×48), «Применить» появляется при выборе. **Данные:** `/public/catalog?city=`.districts. **Приёмка:** [ ] мультивыбор с чекбоксами; [ ] «Сбросить» очищает; [ ] возврат с выбранным набором.

#### A8 · Регистрация 2/4 «Расскажите о себе» — P0
**Figma:** 291:25219 (пусто), 732:77022 / 582:53231 (action sheet фото), 582:52967 (системный запрос медиатеки), 592:4865 (запрос камеры), 732:76894 / 732:77193 (отказ → «Перейти в настройки»), 292:25561 / 732:77325 (фото добавлено), 594:15361 (ввод имени), 732:78559 (wheel-picker даты), 732:78780 (дата выбрана), 594:15488 (дропдаун пола), 594:53410 (пол выбран), 292:25695 (заполнено, 100/150), 292:25797 (ошибки валидации). **Сейчас:** нет. **Маршрут:** `reg/about` (и `profile/complete` шаг 1 из 3 с заголовком «Шаг 1 из 3»).
**Редлайн:** `docs/figma/redlines.md` → раздел «291:25219» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** шапка с «назад» и «Пропустить»; `StepProgress`; «Шаг 2 из 4», «Расскажите о себе» 650/28, «Так другим пользователям и организаторам будет проще узнать вас.» 510/16@40 %; `Avatar` 100 по центру с подписью «Добавить фото» / «Изменить фото» 510/16@40 % (тап → action sheet «Cделать фото» / «Выбрать из галереи»); поля с лейблами (510/14@40 %, паддинг 12): «Имя Фамилия» → «Введите имя и фамилию»; «Дата рождения» → «ДД.ММ.ГГГГ» (тап → `WheelDateTimePicker` день/месяц/год в шторке с кнопкой «Применить»); «Пол» → «Выберите пол» с chevron-down (тап → `DropdownSheet` «Мужской» / «Женский»); «Коротко о себе» → многострочное «Расскажите о себе...» 358×104 r24 со счётчиком «0/150 символов»; кнопка «Продолжить» (disabled до валидности имени и даты).
**Тексты:** «Пропустить», «Шаг 2 из 4», «Расскажите о себе», «Так другим пользователям и организаторам будет проще узнать вас.», «Добавить фото», «Изменить фото», «Cделать фото», «Выбрать из галереи», «Имя Фамилия», «Введите имя и фамилию», «Дата рождения», «ДД.ММ.ГГГГ», «Применить», «Пол», «Выберите пол», «Мужской», «Женский», «Коротко о себе», «Расскажите о себе...», «0/150 символов», «Продолжить», ошибки: «Введите корректное имя с фамилией», «Введите корректную дату рождения», «Введите осмысленное описание», диалоги: «Разрешите доступ к галерее», «Чтобы выбрать фотографию для профиля, разрешите приложению доступ к галерее в настройках устройства.», «Разрешите доступ к камере», «Чтобы сделать фотографию для профиля, разрешите приложению доступ к камере в настройках устройства.», «Перейти в настройки», «Отмена».
**Поведение:** валидация при «Продолжить»: имя — минимум два слова буквами; дата — реальная, возраст 14–100; «о себе» — не менее 2 слов, если заполнено; ошибки — красная обводка + текст под полем (292:25797). Камера → CameraX-экран (как A11, без овала) или системная камера через `TakePicture`; галерея → `PickVisualMedia`. Отказ в разрешении → action sheet «Перейти в настройки / Отмена».
**Данные:** `PATCH /users/profile` (имя, дата, пол, bio), загрузка фото — дейтинговая ручка `photos`; если формат неизвестен — `FakeProfileRepository` + `BACKEND-GAP: profile photo upload`. **Приёмка:** [ ] все поля и пикеры; [ ] 3 текста ошибок; [ ] обе ветки разрешений; [ ] «Пропустить» ведёт на A9; [ ] счётчик 150.

#### A9 · Регистрация 3/4 «Какие события вам интересны?» — P0
**Figma:** 284:25333, 284:25738. **Сейчас:** `OnboardingScreen.kt:37` — расходится. **Маршрут:** `reg/interests` (и `profile/complete` шаг 2 из 3).
**Редлайн:** `docs/figma/redlines.md` → раздел «284:25333» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** шапка «назад» + «Пропустить»; `StepProgress`; «Шаг 3 из 4», «Какие события вам интересны?» 650/28, «Выберите минимум 3 категории, чтобы получать персональные рекомендации.» 510/16@40 %; сетка `CategoryChip` по 2 в ряд (ряды 44, gap 8, паддинг 16): Спорт (`img3d_sport`), Саморазвитие (`img3d_education`), Творчество (`img3d_camera`), Питомцы (`img3d_pet`), Активности (`img3d_puzzle`), Музыка (`img3d_music`), Еда (`img3d_eat`), Семья (`img3d_heart`), Прогулки (`img3d_thres`), Кино (`img3d_movie`); кнопка «Продолжить» (disabled < 3).
**Данные:** словарь — `GET /events/topics` (bucket-уровень) с маппингом на 10 категорий макета; сохранение `PUT /users/me/event-prefs {topics}`. Если словарь бэка шире — показывать только 10 макетных, остальное в «Мои интересы» профиля.
**Приёмка:** [ ] 10 чипов с 3D-иконками по 2 в ряд; [ ] выбранный — тёмный с галочкой; [ ] ≥3 → кнопка; [ ] «Пропустить» справа в шапке, а не внизу.

#### A10 · Регистрация 4/4 «Подтвердите, что вы — это вы» — P0
**Figma:** 284:26282, 286:26404 (запрос камеры), 287:26637 (отказ). **Маршрут:** `reg/verify` (и `profile/complete` шаг 3 из 3).
**Редлайн:** `docs/figma/redlines.md` → раздел «284:26282» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** шапка + «Пропустить»; `StepProgress`; «Шаг 4 из 4», «Подтвердите, что вы — это вы», «Сделайте селфи, чтобы мы могли убедиться, что за профилем стоит реальный человек.»; три строки-карточки 358×~76 r20 `#F5F5F5` с 3D-иконкой 40 слева: «Безопасное общение» 590/16 + «Защищаем сообщество от фейковых аккаунтов» 510/14@40 % (`img3d_verification`), «Больше доверия» + «Другие участники увидят, что профиль подтвержден» (`img3d_check`), «Только для проверки» + «Селфи используется только для проверки профиля» (`img3d_passport`); кнопка «Сделать селфи». Отказ в камере: над кнопкой подпись «Разрешите доступ к камере» 510/16@40 %, кнопка «Перейти в настройки».
**Поведение:** «Сделать селфи» → запрос `CAMERA` → A11; «Пропустить» → A14 с неподтверждённым профилем. **Приёмка:** [ ] карточки с 3D; [ ] обе ветки разрешения.

#### A11 · Селфи — P0
**Figma:** 287:26990, 287:27445 (камера), 287:27198 (результат), 732:79437 (ошибка). **Маршрут:** `reg/selfie`, `profile/selfie`.
**Редлайн:** `docs/figma/redlines.md` → раздел «287:26990» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** полноэкранная фронтальная камера (CameraX), затемнение вне овала 246×304 с пунктирной белой обводкой по центру; шапка: стеклянный «назад» (Clear) + заголовок «Сфотографируйте себя» 590/18 белый; внизу: слева круг 48 Clear `icon-sun` (вспышка/экспозиция), по центру кнопка спуска 72 (белое кольцо), справа круг `icon-arrow-path` (переворот). После снимка — шторка r32 снизу: «Получилось селфи?» 650/28, кнопки «Переснять» (Secondary 175×48) и «Да, далее» (Brand 175×48). Ошибка: «Не удалось распознать лицо» 650/28 `#F84040`, «Убедитесь, что лицо полностью видно в кадре, и попробуйте ещё раз.» 510/16@40 %, кнопка «Переснять» Brand Large.
**Поведение:** «Да, далее» → `POST /users/profile/verify/photo`; ответ ошибки (4xx `code=face_not_found` или любой 4xx) → состояние ошибки; успех → A12. Собственной проверки лица нет.
**Приёмка:** [ ] живая камера с овалом; [ ] три состояния; [ ] отправка в ручку.

#### A12 · Поп-ап «Профиль подтвержден» — P1
**Figma:** 287:27892 (над картой), 348:45991 / 348:43668 (над профилем). **Маршрут:** `reg/success` — шторка над целевым экраном.
**Редлайн:** `docs/figma/redlines.md` → раздел «287:27892» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ModalBottomSheet` r32: `img3d_check` 120 с зелёным свечением, «Профиль подтвержден» 650/28, «Теперь вы — проверенный участник сообщества. Это помогает сделать знакомства и встречи безопаснее для всех.» 510/16@40 % по центру, кнопка «Отлично!» Brand Large. **Поведение:** закрытие → карта (B1) → сразу A13. **Приёмка:** [ ] шторка над картой; [ ] после — запрос уведомлений.

#### A13 · Запрос разрешения на уведомления — P1
**Figma:** 736:98793. **Поведение:** на API 33+ — системный `POST_NOTIFICATIONS` сразу после первого показа карты после регистрации (один раз, флаг в DataStore); ниже 33 — ничего. Регистрация FCM-токена — вне скоупа (`BACKEND-GAP: push token`). **Приёмка:** [ ] диалог показывается один раз после регистрации.
**Редлайн:** `docs/figma/redlines.md` → раздел «736:98793» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

#### A14 · Ветвление после OTP — P0
**Figma:** 287:29289 («если уже есть аккаунт, просто вход»), 610:75380 («Переходим на регистрацию»), 287:28651 («пропустили селфи — не подтверждён»), 732:77542.
**Редлайн:** `docs/figma/redlines.md` → раздел «287:29289» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Поведение:** после `verify` прочитать `account_state` / `user` из ответа (`AuthResponseDto.accountState`, Dtos.kt:42): если бэк отдаёт признак нового аккаунта — по нему; иначе считать новым аккаунт, у которого `GET /users/profile` без имени **и** `GET /users/me/event-prefs` пуст. Новый → `reg/location`; существующий → `main/afisha`. Флаг «регистрация пройдена» — в DataStore, чтобы не спрашивать повторно. Неподтверждённый профиль (`profile_info.is_verified = false`) → см. H1/H6. **Приёмка:** [ ] новый аккаунт проходит A5→A12; [ ] существующий — сразу карта; [ ] повторный вход не запускает регистрацию.

### 6.B Афиша: карта, фильтры, поиск, событие, чат (Figma 579:52323)

#### B1 · Страница «Карта» (главный экран) — P0
**Figma:** 180:8884 (база), 180:8885 (чип активен), 181:9778 («Рядом · до 10 км»), 192:21131 / 192:22038 / 192:22445 / 193:22875 (зум), 228:12862 (зум-аут → категории), 732:80497 (без гео), 194:30552 (после фильтра), 693:70691 / 693:71370 / 693:71855 / 693:72303 / 693:72751 / 693:73689 (NEW — блок «Куда иду» 0/1/2/3/4+). **Сейчас:** `AfishaMapScreen.kt` — частично. **Маршрут:** `main/afisha`.
**Редлайн:** `docs/figma/redlines.md` → раздел «180:8884» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет (px):** карта на всю высоту (в макете 660 видимых), сверху белый фейд 166 px; шапка на y=54: слева `GlassPair(icon-filter, icon-magnifying-glass)`, справа `GlassPair(icon-bell-alert с красной точкой при непрочитанных, Avatar 36)`; ряд чипов на y=110 (высота 40, gap 8, горизонтальная прокрутка, паддинг 16): «Неделя», «Рядом ▾» (`trailingIcon = chevron-down`), «Популярные», «Новые», «Проверенные»; справа на (334, 289) белая пилюля 40×82 зум «+/−» (два круга 36) и на (334, 383) белый круг 40 `icon-NavigationArrow` (только при разрешённой гео); маркеры `EventMarker`; по центру снизу карты на y=572 `FilterChip`-пилюля 199×40 «Смотреть списком» с `icon-bars-3-bottom-left` слева и `chevron-down` справа; **шторка** (`BottomSheetScaffold`, peek = **216**, лист `#F5F5F5` r32, граббер 64×5 `#282A31@23%` на y=16): (1) NEW-блок «Куда иду» (`Upcoming Events Card` 358×104 r20 белый: заголовок «Куда иду» 590/16, обложка 52 + «Кофе и настолки» 590/16 + «+ еще 3 события» 510/16@40 %, chevron; состояния 0 — карточка-приглашение «Куда иду / Пока ничего», 1–3 — до трёх строк, 4+ — одна строка + «+ еще N события»); (2) «Афиша города ›» 650/28 (паддинг 24/16/16) → рельс `AccentEventCard` (gap 12, паддинг 16, снизу 20); (3) белый блок r32 «Просмотр по интересам» 650/28 (паддинг 24/16) с сеткой 3×2 плиток 110×44 r32 `#F5F5F5`: 3D-иконка 24 + «Спорт» 590/16 + счётчик «22» 590/16@40 % (Спорт, Языки, Фотосессии, Питомцы, Настолки, Пикники — по данным `/public/catalog`, первые 6 по `total`); (4) белый блок r32 «Подобрано для вас» 650/28 → группы по дням: заголовок дня «Сегодня / четверг» (день 590/18 + « / » и название дня 400/18@40 %), строки `EventRow` (паддинг 16, gap 16); внизу таббар.
**Тексты:** «Неделя», «Рядом», «Популярные», «Новые», «Проверенные», «Смотреть списком», «Куда иду», «+ еще {n} события», «Афиша города», «Просмотр по интересам», «Подобрано для вас», «Сегодня», «Завтра», названия дней недели строчными.
**Поведение:** чипы — взаимоисключающие, повторный тап снимает: «Неделя» → `from=today&to=+7`, «Популярные» → `sort=popular`, «Новые» → `sort=new`, «Проверенные» → `BACKEND-GAP` (клиентский фильтр по `organizer.reputation ≥ N`, N=50), «Рядом ▾» → шторка B2 и после применения чип показывает «Рядом · до {r} км», события сортируются по `distance_m`; фильтр-кнопка → B3; лупа → B6; колокольчик → F1; аватар → H1; зум-аут ниже zoom 10 — маркеры схлопываются в маркеры категорий (иконка 3D + число, `228:12862`); «к моей локации» — центрирует камеру; «Смотреть списком» и протяжка шторки выше 80 % высоты → B7; «Афиша города ›» → B14; плитка интереса → B9; блок «Куда иду» → B13; строка → B10. Без гео: чипа «Рядом» нет активного состояния (открывается B2, но список без сортировки), кнопки локации нет.
**Данные:** `GET /public/events?bbox=` (+ параметры чипов), `GET /public/catalog`, `GET /events/mine` (attending) или `/users/me/calendars.agenda` для «Куда иду»; `distance_m` — из ответа (если null — не показывать метку).
**Приёмка:** [ ] peek 216; [ ] пять чипов макета, «Рядом» с chevron; [ ] кнопка локации при разрешении; [ ] блок интересов с живыми счётчиками; [ ] группировка по дням; [ ] строки с расстоянием и организатором; [ ] кластер «+ ещё N»; [ ] «Смотреть списком» открывает B7; [ ] блок «Куда иду» в 5 состояниях; [ ] маркеры и подписи как в 4.5 №20.

#### B2 · Радиус «Расстояния от меня» — P1
**Figma:** 180:9724. **Маршрут:** шторка `radius`.
**Редлайн:** `docs/figma/redlines.md` → раздел «180:9724» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ModalBottomSheet` (над картой, затемнение): заголовок «Расстояния от меня» 650/28 + значение «10 км» (число 650/28, «км» @40 %); `PodusheSlider` с метками «2», «10», «25», «50», «100+» 510/16@40 %; кнопки «Сбросить» (Secondary 167×48) и «Применить» (Brand 167×48).
**Поведение:** «Применить» → чип «Рядом · до 10 км», клиентская фильтрация по `distance_m ≤ r` (`BACKEND-GAP: radius`); «Сбросить» → чип «Рядом», фильтр снят. **Приёмка:** [ ] слайдер с 5 метками; [ ] чип меняет текст.

#### B3 · Шторка «Фильтрация» — P0
**Figma:** 194:24494 (база), 194:25492 (выбор района), 194:26275 (период), 194:28212 / 194:28992 (скролл), 194:29751 (только проверенные), 194:30979 (после сброса), 604:64426 (нет событий), 604:65862 / 605:67935 (район изменён), 732:81350 / 732:82210 (карта в шторке). **Сейчас:** `SearchScreen.kt` — заменить. **Маршрут:** шторка `filters` над `main/afisha` (и над `swipes`).
**Редлайн:** `docs/figma/redlines.md` → раздел «194:24494» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ModalBottomSheet` на ~85 % высоты, лист `#F5F5F5` r32, заголовок «Фильтрация» 650/28 (паддинг 24/16/16), прокручиваемый контент, липкий низ с «Сбросить» (Secondary 167) и «Применить» (Brand 167). Блоки (лейбл 510/16@40 % над каждым): «Город» — карточка 358×~110 r20 белая: «Москва» 590/16, «Определили по геопозиции» 510/14@40 %, «Изменить» 510/16 `#007AFF` → B4-город; «Район» — чипы `FilterChip` (Весь город, Хамовники, Тверской, Китай-город, Арбат, Замоскворечье…) + «Другие районы ›» → B4-районы; «Дата проведения» — чипы «Все», «Сегодня», «Завтра», «На неделе», «Период» (→ B5, после выбора чип показывает «12–18 авг»); «Цена» — «Бесплатные», «Платные»; «Интересы» — `CategoryChip` по 2 в ряд (10 категорий); строка «Только проверенные» 590/18 + «От верифицированных пользователей» 510/16@40 % + `PodusheSwitch`. Пусто после применения: шторка остаётся с блоком `EmptyState(img3d_watch, «Ничего не найдено», «Попробуйте изменить фильтры»)` вверху (604:64426).
**Тексты:** «Фильтрация», «Город», «Изменить», «Определили по геопозиции», «Район», «Весь город», «Другие районы», «Дата проведения», «Все», «Сегодня», «Завтра», «На неделе», «Период», «Цена», «Бесплатные», «Платные», «Интересы», «Только проверенные», «От верифицированных пользователей», «Сбросить», «Применить».
**Поведение:** «Применить» → `GET /public/events?city&district&from&to&interest&price` и закрытие; «Сбросить» → все значения по умолчанию (город остаётся), список исходный; выбор районов — мультивыбор, «Весь город» эксклюзивен; интересы — мультивыбор; «Только проверенные» — клиентски (`BACKEND-GAP`). Состояние фильтра живёт в `AfishaViewModel` и переживает закрытие шторки.
**Приёмка:** [ ] шторка, а не экран; [ ] 7 блоков; [ ] «Сбросить/Применить» липкие; [ ] пустое состояние; [ ] чип «Онлайн» удалён.

#### B4 · Поиск города / районов из фильтра — P1
**Figma:** 604:65659, 605:67468, 605:67692. Те же экраны A6/A7 по маршрутам `filters/city`, `filters/districts`; возврат — в шторку с обновлёнными значениями. **Приёмка:** [ ] значения возвращаются в шторку.
**Редлайн:** `docs/figma/redlines.md` → раздел «604:65659» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

#### B5 · Календарь — P1
**Figma:** 194:27047, 194:27117 (из фильтра), 229:16403, 229:16598 (из афиши). **Маршрут:** шторка `calendar`.
**Редлайн:** `docs/figma/redlines.md` → раздел «194:27047» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ModalBottomSheet`: «Календарь» 650/28; заголовок месяца «Август 2026» 590/18 со стрелками `chevron-left/right`; дни недели «Пн … Вс» 510/16@40 %; сетка `CalendarGrid` 7×6 (ячейки 48, числа 590/16; дни с событиями — точка `#007AFF` 4 под числом по `/public/events/calendar`; выбранный — синий круг с белым числом; диапазон — тинт `#007AFF@11%` между двумя выбранными); кнопки «Сбросить» / «Применить».
**Поведение:** один тап — день, второй — конец диапазона; «Применить» → `from/to`; из афиши (B7) после применения список «Подобрано для вас» перестраивается и автоскроллится к нему (229:17818). **Данные:** `GET /public/events/calendar`. **Приёмка:** [ ] точки событий; [ ] диапазон; [ ] автоскролл в B7.

#### B6 · Поиск — P0
**Figma:** 192:19379 (старт), 192:20640 (результаты), 192:20840 (некорректный текст), 192:20918 (по категории), 192:21527 (после крестика). **Сейчас:** `SearchScreen.kt` — переписать. **Маршрут:** `afisha/search`.
**Редлайн:** `docs/figma/redlines.md` → раздел «192:19379» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** без шапки-заголовка: белый блок 134 px с нижними углами r32, внутри `SearchField` с «Отмена» на y=72 (фокус и клавиатура сразу); ниже на сером: «Недавние запросы» 510/16@40 % (y=158) → строки 44 px 590/16 с разделителями (последние 5 запросов из DataStore); «Популярные категории» 510/16@40 % → `CategoryChip` в 2 ряда (5 категорий с наибольшим `total`); при вводе: «Результаты поиска» 510/16@40 % → `EventRow` с датой «29 июля · 11:00» справа от бейджа; пусто: «Ничего не найдено» 650/28 + «Попробуйте изменить запрос или проверить его написание.» 510/16@40 % (`img3d_watch`).
**Тексты:** «Поиск...», «Отмена», «Недавние запросы», «Популярные категории», «Результаты поиска», «Ничего не найдено», «Попробуйте изменить запрос / или проверить его написание.».
**Поведение:** «Отмена» → назад на карту; крестик очищает поле и возвращает стартовое состояние; тап по категории → результаты `interest=`; дебаунс 350 мс; запрос сохраняется в недавние при открытии результата. **Данные:** `GET /public/events?q=`, `/public/catalog`. **Приёмка:** [ ] стартовое состояние с недавними и категориями; [ ] крестик; [ ] «Отмена».

#### B7 · Страница «Афиша» (режим списка) — P0
**Figma:** 610:75563 (база, без гео — с выбором города), 194:35194 / 229:13487 / 229:13904 (скролл), 732:87371 (карточки «куда иду»), 732:87837 (маленькие карточки), 229:17818 (после календаря), 421:54559, 229:18305, 194:35195, 194:31743 (переход из карты). **Сейчас:** нет. **Маршрут:** `afisha/list`.
**Редлайн:** `docs/figma/redlines.md` → раздел «610:75563» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** карта остаётся фоном под листом (лист поднят до y=0 с r32, карта видна 132 px сверху при скролле вниз «как вк фест»); шапка на листе: слева город «Москва» 590/18 + под ним район «Алтуфьевский» 510/16@40 % с chevron-down (тап → B4/шторка «Выбрать город/район»), справа `GlassPair(icon-calendar, …)`/кнопка календаря 48 → B5; ряд чипов как на карте (+ «Новые»); далее те же блоки, что в шторке карты: «Афиша города ›» рельс, маркер-стрип «Пример карточек событий на которые уже иду» = блок «Куда иду», «Просмотр по интересам», «Подобрано для вас» по дням. Кнопка «Смотреть на карте» — свайп листа вниз / стрелка назад.
**Поведение:** тот же `AfishaViewModel`, что и карта; календарь применяется к «Подобрано для вас» с автоскроллом; при отсутствии гео шапка предлагает выбрать город (610:75563).
**Приёмка:** [ ] лист поверх карты с r32; [ ] шапка город/район + календарь; [ ] переход из «Смотреть списком» и обратно.

#### B9 · Страница категории — P1
**Figma:** 194:36070, 229:14429 (подписан), 229:14668 (скролл), 229:14863 (share). **Маршрут:** `afisha/category/{key}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «194:36070» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(title = «Спорт», rightSlot = share)`; лист: 3D-иконка категории 64 по центру, «Категория “Спорт”» 650/28, строка «32 Событий · 342 Подписчика» (числа 590/18, слова 510/18@40 %), описание 510/16@40 %, кнопка «Подписаться» (Primary тёмная 342×48; после подписки — Secondary «Вы подписаны»), список событий по дням (`EventRow`).
**Тексты:** «Категория “{name}”», «Событий», «Подписчика» (склонение по числу), «Подписаться», «Вы подписаны», описания категорий — из ноды (пример: «Находите спортивные события рядом: тренировки, пробежки, велопрогулки и другие активности.»).
**Данные:** `/public/catalog` (total), `/public/events?interest=`, `POST/DELETE /users/me/calendars {kind: topic}`; число подписчиков — `BACKEND-GAP`, показывать только если приходит. **Приёмка:** [ ] заголовок и счётчики; [ ] подписка переключается через API; [ ] вход с бейджа категории на карточках.

#### B10 · Страница события — P0
**Figma:** 230:19369 (база), 232:28888 (полный скролл), 231:19370 / 231:19601 (скролл), 231:20559 («Вы идёте» + «Перейти в чат»), 231:21452 (action sheet отмены участия), 232:26455 (нужно одобрение), 232:27767 («Вы подали заявку»), 232:28174 («Заявка одобрена»), 232:28673 («Заявка отклонена»), 232:26224 («Достигнут лимит участников»), 232:27284 («Событие отменено»), 232:26686 / 232:27053 (краевые), 231:21433 / 231:22023 (snackbar «Добавлено в избранные»), 608:70144 / 608:70204 / 608:70529 (жалоба), 693:76613 (из «Куда иду»), 348:42416 («Событие завершено»). **Сейчас:** `EventScreen.kt` — частично. **Маршрут:** `event/{id}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «230:19369» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** полноэкранный тёмный: фон — обложка, растянутая на весь экран с блюром 40 + затемнение `#000@31%` (`RenderEffect` на 31+, ниже — обложка с `alpha 0.6` поверх `#232B48`); шапка на y=64: `PodusheIconButton(chevron-left, dark)`, «Событие» 590/18 белый по центру, справа `GlassPair(icon-outline-heart | icon-share)` Clear (для организатора: `icon-pencil | icon-share`, а также круг `icon-cog-6-tooth` «Управлять» — см. C3); обложка 358×358 r20 на y=130 (пейджер фото с точками-индикаторами внизу обложки, если фото несколько); бейджи (паддинг 24/16/20, gap 8): `Badge(tone = Dark, icon = hashtag)` по каждому интересу; заголовок 650/28 белый; дата «14 марта, 10:00 - 18:00» 510/18 `#FFF@40%` (формат `d MMMM, HH:mm - HH:mm`, без года, конец из `ends_at`); разделитель `#FFF@11%`; место: название 510/18 белый + «Москва-Сити · 2.4 км от вас» 510/16@40 % (район · расстояние; без `distance_m` — только район), мини-карта 358×140 r16 с пином и стеклянной кнопкой 40 `icon-arrow-top-right-on-square` («Открыть в картах»); «66 Идёт» 510/16@40 % → стопка аватаров 32 (нахлёст −6, из `going_preview`, круги с инициалами на `color`) + «Данил Колбасенко, Андро и ещё 64» 510/16 белый (имена из `going_preview`, «и ещё N» = going_count − показанных); «Организаторы» 510/16@40 % → `OrganizerRow` на каждого (`organizer` + `sessions`/команда, если есть) → I2; разделитель; «Описание события» 510/16@40 % + текст 400/16 белый; действия: `icon-chat-bubble-left-ellipsis` 20 + «Связаться с организатором» 510/16 белый; `icon-Flag` 20 + «Пожаловаться на событие» 510/16 `#F84040`; разделитель `#FFF@30%`; «Похожие события» 650/28 → горизонтальный пейджер: страницы 331 px, в каждой две `EventRow(onDark)` (gap 16), gap 20; снизу `BottomAction` (градиент к тёмному, паддинг 24/16/24/24) с CTA.
**CTA-состояния (одна кнопка 342×48):** `Участвовать` (Brand) · `Вы подали заявку` (Primary тёмная, disabled) · `Заявка одобрена` (Primary с `icon-check`; через 3 с сменяется на «Вы идёте») · `Заявка отклонена` (Primary disabled) · `Вы идёте` (Primary с `icon-check`) + слева вторичная стеклянная 115×48 «Перейти в чат» (если чат создан; иначе «Чат откроется за 2 дня» подписью) · `Достигнут лимит участников` (Primary disabled) · `Событие отменено` (Delete `#F84040` с `icon-x-mark`, disabled) · `Событие завершено` (подпись-пилюля на мини-карте, 348:42416; CTA «Оставить отзыв» Secondary → `GET/POST /events/{id}/review`, если `can_review`).
**Шторки:** «Пожаловаться» → `ModalBottomSheet` «Выберите причину» 650/28, список причин строками 590/18 с радио (из `GET /events/{id}/report`.reasons; при 5 причинах бэка показывать их лейблы — тексты макета 9 причин применять, только если бэк их отдаёт), поле «Опишите проблему» (многострочное) появляется при «Другое», кнопка «Отправить»; после отправки — snackbar «Жалоба отправлена» и пункт меняется на «Жалоба отправлена» (already_sent). «Отмена участия» → `PodusheActionSheet`(«Отменить участие?», «Отменить участие» красным, «Назад»). «Поделиться» → системный share (текст + deep-link).
**Тексты:** «Событие», «Идёт» (склонять: «1 Идёт», «66 Идёт» — в макете именно «Идёт» без склонения, оставить), «и ещё {n}», «Организаторы», «Описание события», «Связаться с организатором», «Пожаловаться на событие», «Похожие события», «Участвовать», «Вы подали заявку», «Заявка одобрена», «Заявка отклонена», «Вы идёте», «Перейти в чат», «Достигнут лимит участников», «Событие отменено», «Событие завершено», «Добавлено в избранные», «Выберите причину», «Отправить», «Жалоба отправлена», «Отменить участие?», «Отменить участие», «Назад», «км от вас», «м от вас» (< 1 км).
**Поведение:** RSVP как сейчас (`POST/DELETE /rsvp`, 409 → snackbar с `detail`); «Связаться с организатором» → `dm/{id}` при наличии парного чата, иначе snackbar «Написать организатору можно после события» (`BACKEND-GAP: direct chat`); сердце → реакция + snackbar «Добавлено в избранные» / без snackbar при снятии; «Открыть в картах» — как сейчас; организатор → `user/{id}`.
**Данные:** `GET /events/{id}` (rsvp, going_preview, is_organizer, sessions), `GET /events/{id}/report`, `POST …/report`, `GET /events/{id}/review`. Удалить хардкод `EventScreen.kt:219, 256, 345–358`.
**Приёмка:** [ ] нет фиктивных данных; [ ] 8 CTA-состояний; [ ] шторка причин; [ ] подтверждение отмены участия; [ ] «Перейти в чат»; [ ] бейджи 30 с решёткой; [ ] пейджер похожих; [ ] snackbar избранного; [ ] фон-блюр обложки.

#### B11 · Чат события — P0
**Figma:** 232:22272 (база), 232:25394 (пусто), 232:23256 (клавиатура, карточка скрыта), 232:24818 / 232:25127 (отправляется/не прочитано), 734:89321 (прочитано), 734:89451, 608:71782 / 736:90140 / 736:90488 (ошибка → повтор), 608:70819 / 623:41125 / 623:41378 / 734:88398 (фото: пермишены, отказ), 608:71500 (лимит фото), 232:25251 / 609:72260 / 734:89090 / 614:79669 (просмотр фото, зум, свайп), 734:88319 / 734:88824 / 734:88600 / 734:89620 / 609:72260. **Сейчас:** `ChatScreen.kt` — частично. **Маршрут:** `chat/{chatId}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «232:22272» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(title = «Чат события», rightSlot = icons-ellipsis-horizontal, sheet = false)`; под шапкой белая карточка 358×52 r? (блок 390×84 r32 белый, паддинг 16): `EventRow(compact)` — обложка 52 r8, «WATT Conf 2026: AI продукты» 590/18, «Уже завтра в 10:00» 510/16@40 % (относительное время старта: «Уже завтра в 10:00», «Сегодня в 19:00», «14 марта в 10:00»), chevron → событие; при открытой клавиатуре карточка скрывается; лента — белый лист r32 (паддинг 16/24/16/112): разделители дат по центру «Вчера», «Сегодня», «14 марта» 510/14@40 %; чужое сообщение: имя 510/16@40 % над `ChatBubble`; своё — справа; `MessageBar` внизу.
**Состояния сообщения (своё):** отправляется (`icon-clock`, альфа 0.6) → доставлено (`icon-check`) → прочитано (`icon-double-check` синий) → ошибка: красная иконка `!` и под пузырём «Не отправлено · Повторить» 510/14 `#F84040` (тап — повтор). Фото: пузырь с картинкой 270×~200 r20; тап → `chat/{id}/photo/{i}` (чёрный фон, пинч-зум, свайп между фото, «назад»); скрепка → action sheet «Сделать фото / Выбрать из галереи» (мультивыбор до 5; при 5 — snackbar «Можно прикрепить не больше 5 фото»); пермишены как в A8. Пусто: `EmptyState(img3d_chat, «Чат еще пустой», «Напиши сообщение и начните общение с участниками»)`. Закрытый чат: `MessageBar` заменён подписью «Чат закрыт — событие завершилось» (текст существующий, допускается).
**Тексты:** «Чат события», «Уже завтра в {HH:mm}», «Сегодня в {HH:mm}», «Вчера», «Сегодня», «Сообщение», «Чат еще пустой», «Напиши сообщение и начните общение с участниками», «Не отправлено · Повторить», «Сделать фото», «Выбрать из галереи», «Можно прикрепить не больше 5 фото».
**Данные:** как сейчас (REST история + WS `send-text` с `front_id`, `read`); статусы: `read` по событию WS `read`/полю сообщения; фото — `POST /chat-media` затем `send-photo`/вложение по ответу (если WS-команды нет — `BACKEND-GAP: chat photo message`, показывать локально). **Приёмка:** [ ] карточка события в шапке и её скрытие при клавиатуре; [ ] имена авторов и разделители дат; [ ] 4 статуса + повтор; [ ] `MessageBar` стеклянный; [ ] пузыри по 4.5 №18; [ ] фото и просмотр; [ ] пустое состояние.

#### B12 · NEW · Блок «Куда иду» на карте — P0
Описан в B1 (шторка, блок 1). **Figma:** 693:70691, 693:71370 (0), 693:71855 (1), 693:72303 (2), 693:72751 (3), 693:73689 (4+). **Приёмка:** [ ] 5 состояний; [ ] переход в B13 и на событие.

#### B13 · NEW · Раздел «Куда иду» — P0
**Figma:** 693:76501, 693:41532, 693:74315, 693:61221, 693:74987, 693:76613 / 693:68861 / 693:75062 (событие). **Маршрут:** `going`.
**Редлайн:** `docs/figma/redlines.md` → раздел «693:76501» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Куда иду»)`; `SegmentedControl(«Предстоящие», «Прошедшие»)` на y=150; список по дням («Сегодня / четверг») `EventRow` с бейджем «Еда +1»; пусто: `EmptyState(img3d_ticket, «У вас пока нет событий», «Запишитесь на событие в афише — оно появится здесь»)` (текст подписи — по ноде 693:74315; если отличается, взять из ноды). Прошедшие — с пилюлей «Событие завершено» на карточке события.
**Данные:** `GET /events/mine` (attending; прошедшие — `ends_at < now`, если бэк отдаёт), `/users/me/calendars.agenda`. **Приёмка:** [ ] сегмент; [ ] дни; [ ] пусто.

#### B14 · Страница «Афиша города» — P1
**Figma:** 794:161851 (полный), 794:161397, 804:77055. **Маршрут:** `afisha/city`.
**Редлайн:** `docs/figma/redlines.md` → раздел «794:161851» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Афиша города»)`; hero — `Card event - large size` 320×360 r20 (обложка на весь размер, снизу градиент, бейдж «Событие», заголовок 590/18 белый «Киновечер в «Люмьер-Холл»», мета «9.3 км от вас · Завтра» 510/16 `#FFF@40%`, цена: «от 750₽» 510/16 белый на пилюле + зачёркнутая «1 000₽» @40 %), горизонтальный рельс; «Популярное в городе» 650/28 → рельс `AccentEventCard` с ценником на обложке (`SmallStrong` белый «от 750₽»); «Лучшие подборки» 650/28 → рельс карточек 160×200 r20 с обложкой и названием 590/16 белым («Мастерклассы на выходные», «Культурный город и театры»); «Рекомендуем» 650/28 → рельс `AccentEventCard`; «В ближайшие дни» 650/28 → `EventRow` списком.
**Данные:** hero и «Популярное» — `sort=popular`; подборки — `/public/collections` (+ `/stories` как источник обложек/подписей, если `collections` пуст); «Рекомендуем» — `/events/deck` первые 6 или `sort=soon`; «В ближайшие дни» — `sort=soon`. Тап по подборке → B9-подобный экран `collection/{slug}` (список событий подборки; шапка = название, lead 510/16@40 %). **Приёмка:** [ ] 5 блоков; [ ] ценники; [ ] подборки открываются.

### 6.C Создание события и кабинет организатора (Figma 579:52328, секция «NEW от 25 авг 2026»)

#### C1 · Новое событие (одна форма) — P0
**Figma:** 688:46485 (пусто), 736:90737 / 736:90845 (action sheet фото), 688:46745 / 688:46856 (пермишены), 736:91105 (отказ → настройки), 688:46969 (фото добавлено, «Добавить» ещё), 688:47083 / 736:92457 (удалить изображение), 688:47201 (название), 688:47315 (wheel-picker даты), 688:47432 (дата выбрана), 688:47545 (место выбрано), 688:47678 (описание 113/1000), 688:47811 (теги добавлены), 688:47950 (интересы + стоимость), 688:48113 / 688:48266 (платно + цена), 736:92678 (цена 0 → ошибка), 688:48419 / 688:48571 / 688:48711 (видимость, вместимость), 688:49658 (ввод количества), 736:93765 (0 → ошибка), 688:48864 / 688:49004 (только проверенные), 688:49800, 688:49942 (описание длиннее лимита), 736:92276 (заглушка обложки). **Сейчас:** `CreateEventScreen.kt` (мастер) — переписать. **Маршрут:** `create`.
**Редлайн:** `docs/figma/redlines.md` → раздел «688:46485» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Новое событие», sheet = true)`, лист с паддингом сверху 32, снизу 56, прокрутка; блоки:
1. Изображения (паддинг 16, gap 12): плейсхолдер 240×240 r24 `#F5F5F5` по центру с `icon-plus` 32 и подписью «Добавить изображения» 510/14@40 % (тап → action sheet «Cделать фото / Выбрать из галереи»); после добавления — карусель миниатюр 240×240 r24 с крестиком-удалением (стеклянный круг 32 справа сверху) и плиткой «Добавить» 510/14@40 % (до 5 фото). Если пользователь не дал доступ и не добавил — на странице события заглушка (градиент `CoverGradient` + 3D-иконка категории).
2. «Детали события» (лейбл 510/16@40 % с паддингом 12): поле «Название события» 358×48; контейнер 358×105 r24 `#F5F5F5` с двумя строками «Начало» 510/16@40 % … «27 июля 2026 г., 11:00» 510/16 (тап → `WheelDateTimePicker` дата+время) и «Конец» … «12:00» (тап → время; по умолчанию старт + 1 ч); поле-кнопка «Выбрать место» 358×48 с `icon-map-pin-fill` слева и chevron (→ C2); после выбора — карточка 358×~116 r24: «Нескучный сад, вход с Ленинского» 590/16, «Хамовники, Москва» 510/14@40 %, мини-карта 358×~60, строка «Показывать только участникам» 510/16@40 % + `PodusheSwitch` (адрес скрыт до записи — хранится локально, `BACKEND-GAP: hide address`); многострочное «Расскажите про событие» 358×~116 r24 со счётчиком «0/1000 · минимум 30 символов» (после ≥30 — «113/1000 символов»; при превышении 1000 счётчик красный, ввод блокируется); поле-кнопка «Добавить теги» 358×48 с `icon-hashtag` (→ шторка тегов), добавленные теги — чипы `FilterChip` со «×» под полем (максимум 5; при 5 поле-кнопка disabled).
3. Разделитель `#000@7%`.
4. «Выберите интересы» (лейбл) — `CategoryChip` по 2 в ряд (10 категорий, мультивыбор до 3).
5. «Доступ к событию» (лейбл): строка 358×48 r24 `#F5F5F5` «Требуется подтверждение» 510/16 + `PodusheSwitch` (→ `rsvp_mode = selective`, иначе `auto`); строка-селектор «Стоимость» … «Бесплатно» 510/16 `#007AFF` + `icon-chevron-up-down` (тап → `DropdownSheet` «Бесплатно / Платно»); при «Платно» — поле «Стоимость, ₽» 358×48 (цифры), ошибка «Укажите стоимость больше 0 ₽» 510/14 `#F84040` под полем при 0; строка «Видимость» … «Публичное» / «Приватное» (→ `visibility public | link`); строка «Вместимость» … «Не ограничено» / «Ограничено» + поле «Введите кол-во участников» (ошибка «Минимум — 1 участник» при 0); строка «Только проверенные» 510/16@40 % + `PodusheSwitch` (`BACKEND-GAP: verified_only`, хранить локально).
6. `BottomAction(«Создать»)` — disabled, пока нет названия (≥3), даты, места (или онлайн-ссылки — онлайн-события в макете нет, поле `is_online` не показывать), описания ≥30 и хотя бы одного интереса.
**Тексты:** «Новое событие», «Добавить изображения», «Добавить», «Cделать фото», «Выбрать из галереи», «Детали события», «Название события», «Начало», «Конец», «Выбрать место», «Показывать только участникам», «Расскажите про событие», «0/1000 · минимум 30 символов», «{n}/1000 символов», «Добавить теги», «Выберите интересы», «Доступ к событию», «Требуется подтверждение», «Стоимость», «Бесплатно», «Платно», «Стоимость, ₽», «Укажите стоимость больше 0 ₽», «Видимость», «Публичное», «Приватное», «Вместимость», «Не ограничено», «Ограничено», «Введите кол-во участников», «Минимум — 1 участник», «Только проверенные», «Создать», диалоги камеры/галереи как в A8, «Разрешите доступ к галерее», «Чтобы выбрать фотографию для профиля, разрешите приложению доступ к галерее в настройках устройства.» (в макете текст про профиль — оставить дословно).
**Шторка тегов (688:51945 / 688:52171 / 688:52056 / 688:52301 / 688:52427 / 688:52543):** «Введите тег» 650/28, поле с префиксом «#», под ним подсказки-чипы существующих тегов (локальный словарь из уже введённых + фиксированный стартовый список: Пикник, Собаки, Парк, Бег, Кофе, Настолки), «Создаем новый тег» при отсутствии; стоп-слова → ошибка «Такой тег нельзя добавить» (словарь стоп-слов локальный, `BACKEND-GAP: tags`); кнопки «Сбросить» / «Применить».
**Поведение:** автосохранение черновика как сейчас (`POST /events/draft`), теги/интересы/«только проверенные»/«показывать только участникам» — в локальном `FakeEventExtrasRepository` по `draft_id`; «Создать» → `POST /events/draft/publish` → замена стека на `event/{id}` в виде организатора (C3), без промежуточного экрана успеха; 409 `l2_required` для платных → snackbar с `detail` и подсветка блока «Стоимость».
**Данные:** `EventDraft` (visibility, ends_at, lat/long, cover_url — если есть загрузка через дейтинговую ручку фото, иначе первая картинка остаётся локальной с пометкой). **Приёмка:** [ ] одна форма, мастера и экрана «🎉» нет; [ ] все 6 блоков; [ ] wheel-picker; [ ] валидации с текстами; [ ] теги до 5; [ ] `visibility` уходит в черновик; [ ] чип «Без записи» удалён; [ ] после «Создать» — страница события.

#### C2 · Модалка «Место проведения» — P0
**Figma:** 688:50067 (поиск), 688:50317 (результаты), 688:50460 (некорректный поиск), 688:50170 (карта), 688:52171 / 688:52056 / 688:52301 / 688:52427 / 688:52543 — теги (описаны в C1). **Маршрут:** шторка `place`.
**Редлайн:** `docs/figma/redlines.md` → раздел «688:50067» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ModalBottomSheet` на ~90 %: «Место проведения» 650/28 + справа текстовая кнопка «На карте» 590/16@40 % (переключает на карту); `SearchField` «Поиск...»; результаты — строки 64: `icon-map-pin` 24, название 590/16, адрес 510/14@40 %; пусто — `EmptyState(«Ничего не найдено», «Попробуйте изменить запрос»)`; режим карты — MapLibre на всю шторку с пином по центру и карточкой снизу «Выбранное место / {адрес}» + «Выбрать это место» (как A5).
**Данные:** `GET /public/venues`? — нет списка; искать по `GET /public/events?q=` (venue из карточек, дедупликация по `venue.id`) — `BACKEND-GAP: venue search`; координаты пина → `lat/long` черновика, адрес — строка из ближайшей площадки или «{lat}, {long}». **Приёмка:** [ ] поиск и карта; [ ] выбранное место в форме.

#### C3 · Страница события — вид организатора — P0
**Figma:** 688:59349, 688:61155 (share-шторка), 688:61334, 736:92276, 695:77073. **Маршрут:** `event/{id}` при `is_organizer`.
**Редлайн:** `docs/figma/redlines.md` → раздел «688:59349» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Отличия от B10:** правая пара `GlassPair(icon-pencil → manage/{id}/edit | icon-share)` и отдельный стеклянный круг `icon-cog-6-tooth` слева от пары «Управлять» → `manage/{id}`; у «66 Идёт» справа пилюля «+2 заявки» 510/16 белая на `#FFF@15%` (pending из `/participants`); внизу вместо CTA — красная ссылка «Отменить событие» 510/16 `#F84040` (→ C13) и кнопка «Управлять» Brand; шторка «Поделиться» (688:61155): заголовок «Семейный пикник» 650/28, две стеклянные кнопки «Отправить» (системный share) и «Скопировать» (буфер + snackbar «Ссылка скопирована»), ниже QR (C4).
**Приёмка:** [ ] `is_organizer` из API; [ ] шестерёнка → Управление; [ ] пилюля заявок; [ ] шторка share.

#### C4 · QR-код события — P2
**Figma:** 688:59685. **Маршрут:** `manage/{id}/qr` (и в шторке share). **Макет:** белая карточка r32 с QR 240 (zxing, кодирует deep-link `polubvi://events?event_id=`), название 650/28, кнопки «Отправить» / «Скопировать». `BACKEND-GAP: qr semantics`. **Приёмка:** [ ] QR рисуется и сканируется в ссылку.
**Редлайн:** `docs/figma/redlines.md` → раздел «688:59685» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

#### C5 · Экран «Управление» — P0
**Figma:** 688:61525, 691:96476, 687:46358, 687:46236, 665:75071, 688:61644, 691:67469, 643:70075, 687:46097. **Сейчас:** `CrewScreen.kt` — заменить. **Маршрут:** `manage/{id}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «688:61525» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Управление», sheet = true)`, лист паддинг 16/24/16/56, gap 20: `CabinetRow(Event Details Icon, «Детали события», «Название, описание и дата»)` → C7; группа r20 из двух строк: `CabinetRow(Participants List Icon, «Список участников», «Управляйте гостями», badge = pending)` → C10 и `CabinetRow(Registration Questions Icon, «Вопросы при регистрации», «Собирайте информацию о гостях»)` → C9; `CabinetRow(Organizers Icon, «Организаторы», «Команда и права доступа»)` → C8; блок кнопок (gap 16): `ListRowButton(icon-user-plus, «Пригласить участников»)` → C11, `ListRowButton(icon-chat-bubble-left-ellipsis, «Начать чат с участниками»)` → C12, `ListRowButton(icon-x-mark, «Отменить событие», danger)` → C13.
**Тексты:** «Управление», «Детали события», «Название, описание и дата», «Список участников», «Управляйте гостями», «Вопросы при регистрации», «Собирайте информацию о гостях», «Организаторы», «Команда и права доступа», «Пригласить участников», «Начать чат с участниками», «Отменить событие».
**Приёмка:** [ ] 7 пунктов; [ ] бейдж заявок живой; [ ] при первом входе — C6.

#### C6 · Онбординг кабинета (4 поп-апа) — P2
**Figma:** 695:77451, 696:87380, 698:89406, 698:89849, 695:77150, 698:90971. **Макет:** над затемнённым «Управлением» — карточки-шторки со скриншотом-иллюстрацией (уменьшенная копия соответствующего экрана в рамке; допускается статичный PNG, экспортированный из ноды) и текстами: «Настраивайте свои событие» / «Редактируйте название, описание, дату и другие детали события в любое время.»; «Работайте с участниками» / «Просматривайте заявки, ответы на вопросы и управляйте списком.»; «Настраивайте регистрацию» / «Добавляйте вопросы и собирайте нужную информацию об участниках.»; «Соберите команду» / «Добавляйте организаторов, настраивайте доступ и приглашайте участников на событие.»; заголовки `Display` 760/42, подписи 510/18@40 %, кнопка «Перейти к управлению» (Brand) на последнем, между карточками — свайп влево, индикатор-точки. Показывать один раз (DataStore). **Приёмка:** [ ] 4 карточки свайпом; [ ] один раз.
**Редлайн:** `docs/figma/redlines.md` → раздел «695:77451» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

#### C7 · Редактирование события — P1
**Figma:** 642:65141, 642:65266 (wheel-picker), 642:65523, 642:65395. **Сейчас:** `EditEventScreen.kt` — расширить. **Маршрут:** `manage/{id}/edit`.
**Редлайн:** `docs/figma/redlines.md` → раздел «642:65141» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** та же форма C1 с заголовком «Редактирование события» и кнопкой «Сохранить». **Поведение:** редактируемые бэком поля (title, description, address, seats) уходят в `PATCH /events/{id}`; остальные (даты, цена, видимость, вместимость, интересы, теги, изображения) — редактируются в UI и сохраняются в `FakeEventExtrasRepository` с пометкой `BACKEND-GAP: PATCH fields`; при 4xx на неподдерживаемое поле — snackbar «Дату и цену изменить нельзя: на событие уже записались» (текст существующий). **Приёмка:** [ ] форма идентична C1; [ ] «Сохранить» → назад в Управление.

#### C8 · Организаторы (команда) — P1
**Figma:** 660:71961 (список), 684:33213 / 684:33609 / 684:33818 / 684:34122 (добавление: сегмент Друзья/Все пользователи, поиск), 684:34364 (некорректный запрос), 684:34481 / 686:40185 / 686:42928 / 686:43154 (роль), 686:42849 / 686:43088 / 686:43306 (добавлен), 686:43380 / 686:43508 / 686:43886 / 686:44058 (редактировать), 686:43689 / 686:44123 (удалить). **Маршрут:** `manage/{id}/organizers`, `/organizers/add`, `/organizers/{uid}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «660:71961» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Организаторы»)`; список `UserCard` с бейджем роли (`Badge organizator` 96×28 r20: «Создатель» на `#36B554@11%` зелёным, «Менеджер» `#007AFF@11%`, «Ведущий» `#DFA107@11%`, «Соорганизатор» нейтральный) и меню «⋯» (Больше → action sheet «Редактировать / Удалить»); внизу `ListRowButton(icon-plus, «Добавить организатора»)`. Добавление — шторка: «Добавить организатора» 650/28, `SegmentedControl(«Друзья», «Все пользователи»)`, `SearchField`, список `UserCard`; выбор → шторка роли: имя 650/28, строка «Показать на странице события» + `PodusheSwitch`, «Контроль доступа» 510/16@40 %, три радио-строки: «Менеджер» / «Полный доступ к управлению», «Соорганизатор» / «Нет доступа к управлению», «Ведущий» / «Доступен только список участников»; кнопка «Добавить» (Small full-width). Удаление — action sheet подтверждения.
**Данные:** `FakeTeamRepository` (`BACKEND-GAP: team roles`); кандидаты — `/events/people` (Друзья) и участники события (Все пользователи → `/participants.going`). **Приёмка:** [ ] список с ролями; [ ] добавление в 3 шага; [ ] редактирование и удаление.

#### C9 · Вопросы при регистрации (конструктор) — P1
**Figma:** 643:70579 (основные), 644:71019 / 643:70888 / 644:71130 / 644:71239 / 644:71354 (переключатели), 663:73370 (с дополнительными), 665:81483 / 665:81723 / 665:81996 (перетаскивание), 660:70613 (лимит 7), 663:72986 / 663:72855 (добавление, выбор типа), 660:28012 / 660:65641 / 660:52187 (текст), 660:51491 / 660:67342 / 660:67503 / 660:67759 / 660:67966 / 660:68228 / 660:68743 / 663:73672 (варианты ответа, множественный выбор, лимит ответов, валидация), 660:51669 / 660:68975 / 660:69211 / 660:69453 (ссылка), 660:51825 / 660:69984 / 660:69847 / 660:70225 (флажок), 660:52498 / 660:67080 / 660:66726 / 660:65499 / 660:66972 (редактирование, удаление), 736:94152 / 736:94425 / 736:94669 / 736:94905 (порядок вариантов). **Маршрут:** `manage/{id}/questions`, `/questions/new`, `/questions/{qid}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «643:70579» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Вопросы при регистрации»)`; «Основные вопросы» 510/16@40 % → три строки 358×64 r24 `#F5F5F5`: «ФИО» + `PodusheSwitch` (при включении — подстрока «Какие данные … Имя Фамилия» синим (dropdown: Имя / Имя Фамилия) и строка «Обязательно» + switch), «Телефон» + switch (+ «Обязательно»), «Email» + switch (+ «Обязательно»); «Дополнительные вопросы» 510/16@40 % → `QuestionCard` на каждый (drag-handle при ≥2, перетаскивание меняет порядок); `ListRowButton(icon-plus, «Добавить вопрос»)`; подпись «Можно добавить не более 7 дополнительных вопросов.» 510/14@40 % (при 7 кнопка disabled). Добавление/редактирование — экран «Добавление вопроса» / «Редактирование вопроса» 650/28: строка «Выберите тип вопроса» → шторка типов (Текст «Короткий ответ в одну строку», С вариантами ответа, Ссылка, Флажок); поле «Ваш вопрос» (лейбл) «Введите вопрос»; для вариантов — список полей «Вариант ответа» с «Добавить еще» и перетаскиванием, строка «Множественный выбор» + switch и «Ограничение по кол-ву ответов» + поле; строка «Обязательно» + switch; для редактирования — `ListRowButton(icon-trash, «Удалить вопрос», danger)` с action sheet подтверждения; кнопка «Добавить» / «Сохранить» (Small full-width); валидация: пустой вопрос/вариант → красная обводка и «Введите вопрос» / «Введите вариант ответа» 510/14 красным.
**Данные:** `FakeQuestionsRepository` (`BACKEND-GAP: registration questions`), по `event_id`. **Приёмка:** [ ] основные и дополнительные; [ ] 4 типа; [ ] drag-and-drop; [ ] лимит 7; [ ] валидации.

#### C10 · Список участников — P0
**Figma:** 643:67893 (Заявки), 665:75539 (Участвуют), 665:75747 (Отклонены), 666:83221 (пусто после «Одобрить всех»), 665:76023 (пусто участвуют), 665:80068 / 665:80818 (заявка → профиль с ответами), 665:81024 / 665:81405 (одобрить/отклонить из заявки), 665:82236 / 665:82428 / 666:83064 (исключить → подтверждение), 666:86803 / 666:87527 (одобрить в списке), 666:87618 / 666:87759 / 666:87940 / 666:88139 (участник → исключить), 666:88284 / 666:88344 / 666:88456 / 666:85725 / 665:82798 (отклонённые → одобрить), 665:75193 / 665:76129 / 665:76354 / 665:76595 (поиск, некорректный), 666:85976 / 666:86264 / 666:86386 / 665:79854 (переход на профиль). **Сейчас:** `CrewScreen.kt` — переписать. **Маршрут:** `manage/{id}/participants`, `/participant/{uid}`.
**Редлайн:** `docs/figma/redlines.md` → раздел «643:67893» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Список участников»)`; `SegmentedControl(«Заявки», «Участвуют», «Отклонены»)` на y=140 (при `rsvp_mode = auto` — без «Заявок»: сегмент из двух); `SearchField` «Поиск...» на y=200; список `UserCard` (gap 16): в «Заявках» справа два круга 32 стекло ✓ (`#36B554`) и ✕ (`#F84040`); в «Участвуют» — без кнопок (тап → карточка участника); в «Отклонены» — кнопка «Одобрить» Small; внизу `BottomAction` Small «Одобрить всех» (только в «Заявках» при ≥1) → snackbar «Заявки приняты» + пустое состояние `EmptyState(img3d_users, «Пока нет новых заявок», «Как только кто-то отправит заявку на участие, вы увидите её здесь.»)`; пусто в «Участвуют»: «Пока никого нет» / «Одобренные участники появятся здесь». Карточка участника (665:80818 = профиль I2 в режиме организатора): профиль + блок «Ответы на вопросы» (вопрос 510/14@40 % / ответ 510/16, `note` заявки как первый ответ) + кнопки «Одобрить» (Primary) / «Отклонить» (Secondary) для заявки или «Исключить» (Delete) для участника → action sheet «Исключить участника?» / «Исключить» / «Назад».
**Тексты:** «Список участников», «Заявки», «Участвуют», «Отклонены», «Поиск...», «Одобрить всех», «Заявки приняты», «Пока нет новых заявок», «Как только кто-то отправит заявку на участие, вы увидите её здесь.», «Одобрить», «Отклонить», «Исключить», «Исключить участника?», «Назад», «Ответы на вопросы», «{n} общих интереса» (склонение).
**Данные:** `GET /events/{id}/participants` (going/pending/declined/waitlisted), `POST …/decide {approve}`; «исключить» = `decide {approve:false}` для going (если бэк не принимает — `BACKEND-GAP: kick participant`); «общие интересы» — пересечение `event-prefs` (`BACKEND-GAP: common interests`, показывать только при данных). **Приёмка:** [ ] три вкладки; [ ] поиск; [ ] «Одобрить всех»; [ ] карточка участника; [ ] исключение с подтверждением; [ ] `UserCard` по 4.5 №21.

#### C11 · Приглашение участников — P1
**Figma:** 689:61761, 690:64234 / 690:64495 (сегмент), 690:64730 (выбрано 3), 690:65016 (просмотр выбранных), 690:65689 / 690:66096 («Снять выделение» / «Выбрать всех»), 690:66479 / 691:66958 / 691:67113 (текст приглашения), 691:67384 (уведомление), 690:64026 (пусто). **Маршрут:** `manage/{id}/invite`, `/invite/compose`.
**Редлайн:** `docs/figma/redlines.md` → раздел «689:61761» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Приглашение участников»)`; `SegmentedControl(«Друзья», «Участники»)`; `SearchField`; список `UserCard` с `PodusheCheckbox` справа; кнопки над списком «Выбрать всех» / «Снять выделение» (Secondary Small); `BottomAction` Small «Отправить приглашения» (текст «Выбрать 3 учас.» на кнопке при выборе — по ноде: «Отправить приглашения (3)»); экран compose: аватары выбранных, многострочное «Текст приглашения», «Отправить»; пусто: `EmptyState(img3d_users, «Пока некого приглашать», «Здесь появятся люди, которых вы сможете пригласить на событие.»)`.
**Данные:** `POST /events/company-invite` (event_id, user_ids, message — сверить тело по схеме `CompanyInvite`), кандидаты — `/events/people`. **Приёмка:** [ ] мультивыбор; [ ] compose; [ ] отправка через API; [ ] пусто.

#### C12 · Начать чат с участниками — P1
**Figma:** 691:93731 (пусто), 691:96323, 691:94983, 691:95186. **Маршрут:** `chat/{chatId}` из кабинета. **Поведение:** если чат события уже создан — открыть; иначе показать B11 в пустом состоянии с локальной лентой и пометкой `BACKEND-GAP: create event chat early` (бэк создаёт за 48 ч). **Приёмка:** [ ] переход из кабинета.
**Редлайн:** `docs/figma/redlines.md` → раздел «691:93731» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

#### C13 · Отмена события — P1
**Figma:** 691:97820, 691:97101. **Макет:** `PodusheAlert`(«Вы действительно хотите отменить событие?», «После отмены мы уведомим всех участников, что событие не состоится. Это действие нельзя будет отменить.», «Отменить событие» (destructive), «Назад»). **Поведение:** `POST /events/{id}/cancel` → назад на карту, snackbar «Событие отменено». **Приёмка:** [ ] системный диалог с текстами макета.
**Редлайн:** `docs/figma/redlines.md` → раздел «691:97820» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

### 6.D Взаимодействие участников (Figma 792:36278) — только NEW-секции

#### D3 · NEW · Регистрация на событие с вопросами — P1
**Figma:** 792:89661 (событие), 792:90605 (форма 390×1059), 792:141368 … 792:145679 (ввод, ошибки), 792:146881 / 792:146488 / 792:147277 (варианты, флажок, множественный), 792:147670 (подача), 792:147922 (одобрена), 792:147919 (отклонена), 794:161192 / 794:161289 (уведомления). **Маршрут:** `event/{id}/register`.
**Редлайн:** `docs/figma/redlines.md` → раздел «792:89661» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.
**Макет:** `ScreenScaffold(«Регистрация», sheet = true)`; поля по вопросам события: «Имя Фамилия», «Телефон» (маска), «Email», текст, ссылка, «С вариантами ответа» (радио-строки), «Флажок» (`PodusheCheckbox` + текст), множественный выбор (чекбоксы); валидация: «Некорректные имя и фамилия», «Некорректный номер», «Некорректная ссылка», «Обязательный вопрос»; `BottomAction(«Отправить заявку»)`.
**Поведение:** открывается вместо мгновенного RSVP, если у события есть вопросы (`FakeQuestionsRepository`) или `rsvp_mode = selective`; ответы — в `note` заявки (`POST /rsvp {note}` — сверить `RsvpRequest`) + локально; далее B10 в состоянии «Вы подали заявку». **Приёмка:** [ ] все типы вопросов; [ ] валидации; [ ] `note` уходит.

#### D4 · NEW · Приглашение в организаторы — P2
**Figma:** 792:149403 / 792:150392 / 792:150489 (уведомления: менеджер / соорганизатор / ведущий), 792:150586 / 792:150842 / 792:150991 / 792:151136 (событие с ролью), 792:150663 / 792:151213 (Управление по роли), 793:155847 / 793:155772 (участники — просмотр), 794:156130 (организаторы — просмотр), 794:156205 (чат). **Поведение:** уведомление вида `team_invite` (локальное, из `FakeTeamRepository`) → событие → шестерёнка → Управление, где по роли часть пунктов disabled (Соорганизатор — только показ в событии; Ведущий — только список участников без действий; Менеджер — всё). **Приёмка:** [ ] три варианта прав.
**Редлайн:** `docs/figma/redlines.md` → раздел «792:149403» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

#### D5 · NEW · Приглашение участвовать — P1
**Figma:** 794:156324 (уведомление «Пришло приглашение на участие»), 794:160909 / 794:159262 (событие → «Участвуем»). **Поведение:** `GET /events/company-invites` → уведомления с кнопками «Принять» / «Отклонить» в F1 (`POST …/respond`), тап → событие с CTA «Участвовать». **Приёмка:** [ ] приглашения приходят в ленту.
**Редлайн:** `docs/figma/redlines.md` → раздел «794:156324» (и разделы остальных нод состояния); внутренняя геометрия компонентов — `docs/figma/components.md`.

> **Экраны без выгруженного дерева нод (разделы E–J):** редлайнов в `redlines.md` нет; геометрия ниже — из рендеров профиля (раздел H) и компонентов раздела 4. Для остальных — открыть ноду и снять размеры до реализации (`FIGMA-CHECK`).

### 6.E Свайпы событий (Figma 579:52324)

> Рендеры и дерево нод этого раздела при аудите не были получены — размеры и тексты ниже взяты из имён фреймов, подписей переходов и текущего кода, который верстался по 233:30499. Перед реализацией открыть ноды и сверить: `FIGMA-CHECK`.

#### E1 · Страница со свайпами — P0
**Figma:** 233:30499 (база), 233:32565, 617:88179 («Первый вариант» — не источник). **Сейчас:** `SwipesScreen.kt` — частично. **Маршрут:** `swipes` (переключатель кнопкой `icon-cards` в таббаре; повторный тап возвращает на карту).
**Макет:** шапка как на карте: `GlassPair(icon-filter, icon-magnifying-glass)` и `GlassPair(icon-bell-alert, Avatar)` — **все четыре живые** (фильтры → B3 над свайпами, лупа → B6, колокольчик → F1, аватар → H1); карточка 358×624 r32 с обложкой на весь размер, прогресс-сегменты сверху (по числу карточек в порции), внизу градиент и: бейджи (`Badge(Dark)` по интересам; бейдж «Рядом» — только при `distance_m < 3000`), заголовок 650/28 белый (2 строки), «дата · цена» 510/16 `#FFF@40%`, место 510/16 `#FFF@40%`; ряд кнопок: `icon-arrow-reload` 40 (вернуться), ✕ 54 (`#F84040`), сердце 54 (`#FC5B5B`), `icon-information-circle` 40 — все стекло Clear; следующая карточка просвечивает под текущей (scale 0.96, смещение 16).
**Поведение:** свайп/кнопки как сейчас; **тап по карточке и вертикальный свайп вниз → событие** (233:29438); «вернуться» → `DELETE reaction`; фильтры (E3) применяются к колоде через `AfishaFilter` → `GET /events/deck?interest&district…` (если параметры не поддержаны — клиентская фильтрация с `BACKEND-GAP: deck filters`).
**Приёмка:** [ ] шапка кликабельна; [ ] тап по карточке открывает событие; [ ] бейдж «Рядом» по данным; [ ] сверено с 233:30499 (`FIGMA-CHECK` снят).

#### E2 · Свайп влево/вправо, следующая карточка — P1
**Figma:** 233:30728, 233:31513, 233:31299, 233:32184. Как сейчас; сверить цвета подсветки решения (зелёная/красная 35 %) и угол крена с нодами. **Приёмка:** [ ] `FIGMA-CHECK` снят.

#### E3 · Фильтрация на свайпах — P1
**Figma:** 233:32856, 233:33230, 617:91440. Та же шторка B3 над экраном свайпов («система фильтрации абсолютно аналогична»). **Приёмка:** [ ] шторка открывается с иконки фильтра; [ ] колода перезагружается.

#### E4 · «События отфильтрованы» / нет подходящих — P1
**Figma:** 233:34276. **Макет:** `EmptyState(img3d_watch, «Нет подходящих событий», «Попробуйте изменить параметры фильтра», action = «Изменить параметры» → B3)`; состояние «карточки закончились» (порция исчерпана) — отдельное: «Карточки закончились» / «Новые события появляются каждый день» / «Показать ещё раз» (существующие тексты, допускаются, если в ноде 233:34276 нет иных). **Приёмка:** [ ] два разных пустых состояния.

#### E5 · Событие из свайпов — P1
**Figma:** 233:29438 — B10 без изменений. **Приёмка:** [ ] назад возвращает на ту же карточку.

#### E6 · NEW · Гайд-тур при первом входе — P2
**Figma:** 699:93744, 702:94090 («Подсказка 1»), 703:94125 («Подсказка 2»), 703:94289 («Подсказка 3»), 704:94545. **Макет:** три оверлея-подсказки над колодой с затемнением, выделенной зоной (карточка / кнопки / жест) и тултипом (тёмная пилюля r20, текст 510/16 белый, кнопки «Дальше» / «Готово» текстом 590/16 `#007AFF`); тексты подсказок — из нод (`FIGMA-CHECK`); показывать один раз (DataStore). **Приёмка:** [ ] 3 шага, один раз.

### 6.F Уведомления (Figma 579:52326)

#### F1 · Лента уведомлений — P0
**Figma:** 287:29704 (база), 621:33597, 288:30381 (только непрочитанные), 288:30746 (пусто), 288:29990 (индикатор на карте), 288:30448 (переход на событие). **Сейчас:** `NotificationsScreen.kt` — частично. **Маршрут:** `notifications`.
**Макет:** `ScreenScaffold(«Уведомления», sheet = true)`; под шапкой `SegmentedControl(«Все», «Непрочитанные»)` (`FIGMA-CHECK`: в ноде 288:30381 — либо сегмент, либо чип-фильтр); строки — компонент UI-kit «Сard-notify» (`FIGMA-CHECK` в 175:164): обложка события 52 r8 (или аватар 44 для приглашений), заголовок 590/16, текст 510/14@40 %, время справа 510/14@40 %, синяя точка 8 у непрочитанных; тап → событие; пусто: `EmptyState(img3d_chat или колокольчик, «Пока тихо», «Здесь появятся напоминания и новости ваших событий»)`.
**Поведение:** непрочитанное помечается прочитанным **по тапу на строку** и по кнопке «Прочитать все» в правом слоте шапки (`icon-double-check`), а не автоматически при открытии экрана; бейдж на колокольчике карты/свайпов гаснет, когда `unread = 0`; приглашения (`company_invite`, `team_invite`) — строка с двумя Small-кнопками «Принять» / «Отклонить».
**Данные:** `GET /users/me/notifications`, `POST …/notifications {ids?}` (если ручка принимает только «все» — читать все по кнопке; по тапу — локально). **Приёмка:** [ ] фильтр непрочитанных; [ ] чтение по тапу; [ ] обложки событий; [ ] кнопки приглашений.

### 6.G Избранные (Figma 579:52327)

#### G1 · Избранные — события — P0
**Figma:** 293:25896 (база), 293:27341 (фильтр по интересу), 293:28258 (пусто), 293:27496 (событие), 293:28243 (уведомление после удаления), 293:25962 (вход с карты). **Сейчас:** `FavoritesScreen.kt` — частично. **Маршрут:** `main/favorites`.
**Макет:** заголовок «Избранные» 650/28 (паддинг 24/16) — экран корневой, без «назад»; `SegmentedControl(«События», «Люди»)`; ряд `FilterChip` по интересам избранного (горизонтальная прокрутка; «Все» + категории, которые встречаются); список `EventRow(trailing = сердце заполненное)`; пусто: `EmptyState(img3d_heart, «Пока пусто», «Лайкайте события в свайпах — они соберутся здесь»)`; снятие сердца → строка исчезает + snackbar «Удалено из избранного» с действием «Вернуть» (5 с). `FIGMA-CHECK` тексты по 293:28258 / 293:28243.
**Данные:** как сейчас. **Приёмка:** [ ] сегмент; [ ] фильтр по интересу; [ ] snackbar с отменой.

#### G2 · Избранные — люди — P1
**Figma:** 293:27869, 293:27728, 294:29225. **Макет:** список `UserCard(trailing = сердце)`; пусто: «Пока пусто» / «Здесь появятся люди, которых вы добавили в избранное». **Данные:** `FakeFavoritePeopleRepository` (`BACKEND-GAP: favorite people` — открытый вопрос владельцу: лайки дейтинга или подписки); добавление — с профиля I2 «Добавить в избранные». **Приёмка:** [ ] вкладка работает на Fake.

### 6.H Профиль (Figma 579:52329) — сверено по рендерам

#### H1 · Профиль (свой) — P0
**Figma:** 348:36951 (подтверждённый), 693:39392 (NEW — с плиткой «Куда иду» вместо «Истории»; **источник истины**), 348:37200 (тултип «Пользователь подтверждён»), 348:38322 (тултип репутации), 348:38925 (Средняя), 348:39176 (Низкая), 348:42676 (неподтверждённый User_0210), 736:100758 / 732:77991 (Средняя, без кольца), 348:38674 / 348:39592 (после редактирования). **Сейчас:** `ProfileScreen.kt` — переписать. **Маршрут:** `profile`.
**Геометрия (измерена по рендеру 693:39392, dp):** шапка 124; карточка-шапка белая r32 на (16, 132) 358×142; далее белые секции **во всю ширину** (x = 0, 390) с углами r32 и промежутком 4: статистика + «Обо мне» (0, 294) 390×232; плитки (0, 530) 390×200; «Мои интересы» (0, 734) 390×168; «Похожие интересы» + баннер (0, 906) 390×407; внутренние отступы секций 16, внутри секций карточки `#F5F5F5` r24. Всего высота экрана 1365.
**Макет (сверху вниз):** `ScreenScaffold(«Профиль», rightSlot = GlassPair(icon-qr-code → H4 | icon-cog-6-tooth → J1))`, контент на сером фоне:
1. Карточка-шапка (358×~140): `Avatar(84, ring = verified, verified)` слева; справа имя 650/28 (1 строка, эллипсис) + `icon-pencil` 24 (→ H5); чип локации 358−… r100 `#F5F5F5` с `icon-map-pin` 16 + «Хамовники, Китай го… +1» 510/14@40 % + chevron (→ H7-локация); чип статуса «+ Указать статус» (пунктирная обводка `#282A31@23%`, 510/14@40 %) → H7-статус; выбранный статус — пилюля с сердцем «Свободен» (`#F84040@11%`, текст `#F84040`), «В отношениях» (`#36B554@11%`, `icon-users`), «Все сложно» (`#8637C6@11%`) с chevron-down.
2. Карточка статистики (358×~100): три колонки с разделителями: «Подписки» 510/14@40 % / «64» 650/28; «Подписчики» / «12»; «Репутация ⓘ» / «Высокая» 650/28 `#36B554` (Средняя — `#DFA107`, Низкая — `#F84040`); ⓘ → тултип «Текст подсказка» (тёмная пилюля, `FIGMA-CHECK` текст); ниже в той же карточке блок «Обо мне» 590/16 + текст 510/14@40 % (без текста — «Расскажите о себе» ссылкой → H5).
3. Плитки (358×~180): слева «Мои события» 178×180 r24 с `img3d_ticket` (→ H2), справа две плитки 172×86: «Куда иду» с `img3d_watch`/календарь (→ B13) и «Скидки» с `img3d_sale` (→ H8); фон плиток — светлые градиенты (голубой / зелёный / розовый) из ноды.
4. «Мои интересы» 510/16@40 % → `CategoryChip`-подобные пилюли с 3D-иконкой 24 (нередактируемые; тап → J1-интересы или H5).
5. «Похожие интересы» 650/28 → список `UserCard` (имя, район, «3+ общих интереса») с разделителями, справа выглядывают аватары следующих; тап → I2.
6. Баннер «Связать с дейтингом» 358×80 r24 тёмный `#282A31`: иконка-сердце в белом квадрате 48 r16 слева, «Связать с дейтингом» 590/16 белый, «Находите взаимный интерес после событий» 510/14 `#FFF@40%`, chevron → `polubvi://` (deep-link в приложение «По любви», если установлено; иначе snackbar «Скоро»).
7. Таббар виден.
**Неподтверждённый:** аватар без кольца и бейджа (серый круг `icon-user` при отсутствии фото), имя «User_0210» (из `profile_info` или «Пользователь»), репутация «Низкая» красным; при входе — H6.
**Тексты:** «Профиль», «Указать статус», «Свободен», «В отношениях», «Все сложно», «Подписки», «Подписчики», «Репутация», «Высокая», «Средняя», «Низкая», «Обо мне», «Мои события», «Куда иду», «Скидки», «Мои интересы», «Похожие интересы», «{n}+ общих интереса», «Связать с дейтингом», «Находите взаимный интерес после событий», «Пользователь подтверждён».
**Данные:** `GET /users/profile` (имя, фото, `is_verified`, bio, пол/дата), `GET /users/me/reputation` (уровень: score ≥ 70 → Высокая, 40–69 → Средняя, иначе Низкая — константы в `ReputationLevel`, `BACKEND-GAP: reputation thresholds`), `GET /users/me/event-prefs` (интересы, районы), `GET /users/me/calendars` (подписки = items.size; подписчики — `BACKEND-GAP`, показывать «—»), `GET /events/people` («Похожие интересы»); статус — `FakeProfileExtrasRepository` (`BACKEND-GAP: relationship status`). Удалить «Иду / Организую / Заявки», «Я организую / Я иду», строки «Настройки / Выйти» (выход — в J1).
**Приёмка:** [ ] 7 блоков как в 693:39392; [ ] три уровня репутации цветом; [ ] неподтверждённое состояние; [ ] QR и шестерёнка в шапке; [ ] таббар виден.

#### H2 · Мои события — P0
**Figma:** 348:39844 (предстоящие), 348:41010 (прошедшие), 348:41099 (пусто), 348:40427 (своё событие: шапка `pencil | share`). **Маршрут:** `profile/my-events`.
**Макет:** `ScreenScaffold(«Мои события», sheet = true)`; `SegmentedControl(«Предстоящие», «Прошедшие»)` на y=~150; группы по дням («Сегодня / четверг», «24 июля / среда») `EventRow` (организатор — «Андрей К.» = я); пусто: `EmptyState(img3d_ticket, «У вас пока нет событий», «Создайте свое первое событие и пригласите людей разделить его вместе с вами.»)`. Тап → событие в виде организатора (C3).
**Данные:** `GET /events/mine` (organized; прошедшие — по `ends_at`). **Приёмка:** [ ] сегмент; [ ] дни; [ ] пусто.

#### H3 · История (прошедшие посещения) — P1
**Figma:** 348:41230 (список), 348:46412 (пусто), 348:42416 (событие завершено). **Маршрут:** `profile/history` (в NEW-профиле плитки «История» нет — экран доступен из B13 «Прошедшие»; реализовать как вкладку «Прошедшие» раздела «Куда иду», отдельный маршрут не нужен).
**Макет:** список по датам («20 июля / понедельник»…) `EventRow` с разделителями; пусто: `EmptyState(img3d_watch, «История пока пуста», «Посещайте новые события, и история ваших встреч будет сохраняться здесь.»)`; событие — B10 с пилюлей «✓ Событие завершено» (белая r20 на мини-карте) и CTA «Оставить отзыв» при `can_review`. **Приёмка:** [ ] прошедшие с отзывом.

#### H4 · QR-код пользователя — P2
**Figma:** 348:47356, 348:47958 (snackbar), 348:48261 (share). **Маршрут:** `profile/qr` (шторка).
**Макет:** `ModalBottomSheet` r32 белая: имя 650/28 по центру, QR 240 с аватаром 60 в центре, кнопки «Отправить» (Secondary, `icon-share`) / «Скопировать» (Secondary, `icon-square-2-stack`) → snackbar «Ссылка на профиль скопирована». **Данные:** QR кодирует `polubvi://users/{id}` (`BACKEND-GAP: public profile link`). **Приёмка:** [ ] QR + два действия.

#### H5 · Редактирование профиля — P1
**Figma:** 348:39427, 348:39528. **Маршрут:** `profile/edit`. **Макет:** `ScreenScaffold(«Редактирование», sheet = true)`: аватар 100 по центру с «Изменить фото», поля как A8 (имя, дата, пол, «Коротко о себе» 150), кнопка «Сохранить» (`FIGMA-CHECK` по 348:39427). **Данные:** `PATCH /users/profile`. **Приёмка:** [ ] изменения видны в H1 сразу.

#### H6 · Поп-апы верификации и довершение профиля — P1
**Figma:** 348:43102 («Повысьте доверие к своему профилю» → «Заполнить профиль»), 348:45576 / 348:45745 / 736:100436 («Подтвердите свой профиль» → «Пройти верификацию»; пермишен; отказ), 348:43323 / 348:43361 / 348:43502 / 348:43543 / 348:43584 / 348:43625 (три шага «Шаг 1 из 3» … «3 из 3»), 348:45937 / 348:45958 / 348:43154 / 348:43175 (селфи), 348:45991 / 348:43668 (success). **Маршрут:** `profile/complete`, `profile/selfie`.
**Макет:** шторки r32 с 3D-щитом `img3d_verification` 120 со свечением: «Повысьте доверие / к своему профилю» 650/28 + «Добавьте информацию о себе и подтвердите профиль. Это поможет другим пользователям чувствовать себя увереннее при знакомстве и встречах.» + кнопка «Заполнить профиль» (если имя пустое) **или** «Подтвердите свой профиль» + «Пройдите быструю верификацию, чтобы повысить доверие к вашему профилю, улучшить репутацию и сделать знакомство с другими участниками безопаснее.» + «Пройти верификацию» (если не подтверждён). Показывать при входе в профиль не чаще раза в сутки (DataStore). «Заполнить профиль» → три шага A8/A9/A10 с заголовками «Шаг N из 3» и прогрессом из 3 сегментов → A11 → A12.
**Приёмка:** [ ] обе шторки по условиям; [ ] три шага; [ ] раз в сутки.

#### H7 · Статус и местоположение — P1
**Figma:** 348:37495 (дропдаун статуса), 348:37789 / 348:55197 (статус выбран), 348:54063 / 348:54790 (шторка местоположения). **Маршрут:** шторки `status`, `location`.
**Макет:** статус — выпадающее меню под чипом (белая карточка r20 с тенью, три строки-пилюли: «Свободен» `#F84040@11%`, «В отношениях» `#36B554@11%`, «Все сложно» `#8637C6@11%`); местоположение — `ModalBottomSheet`: «Где вы обычно находитесь?» 650/28, «Выберите 1-2 района вашего города, чтобы лента подстроилась под вас» 510/16@40 %, мини-карта 358×120 r16, «Районы» + чипы (тёмные выбранные) + «Еще», кнопки «Сбросить» (Secondary, disabled пока нет изменений) / «Сохранить» (Brand; disabled до изменения). **Данные:** статус — Fake; районы — `PUT /users/me/event-prefs`. **Приёмка:** [ ] меню статуса; [ ] шторка районов сохраняет.

#### H8 · Скидки партнёров — P1
**Figma:** 348:46562, 348:46985 (snackbar). **Сейчас:** `DiscountsScreen.kt` — переписать, `QrScreen` удалить. **Маршрут:** `profile/discounts`.
**Макет:** `ScreenScaffold(«Скидки партнёров», sheet = true)`; «Забота о наших участниках» 650/28 + «Получайте приятные бонусы и специальные предложения от наших партнеров.» 510/16@40 %; карточки 358×~150 r20 `#F5F5F5`: логотип партнёра 56 круг слева, «Sport Master» 590/16, «-15% на беговую обувь» 510/14 `#36B554`, «Для участников забега» 510/14@40 %; разделитель; строка «Промокод» 510/14@40 % … «PODUSHE15» 590/16 + `icon-square-2-stack` 20 → буфер + snackbar «Промокод скопирован».
**Данные:** `FakeDiscountsRepository` с данными ноды (Sport Master −15 % PODUSHE15; World Class «7 дней клуба бесплатно» CLASS01; Ситимобиль −20 % TAKSI_EVENT; Декатлон −10 % DECATHLON10 — `FIGMA-CHECK` последний промокод) и логотипами-плейсхолдерами (цветной круг с буквой; `BACKEND-GAP: partners`). **Приёмка:** [ ] карточки как в 348:46562; [ ] копирование + snackbar; [ ] экрана QR скидки нет.

#### H9 · NEW · «Куда иду» из профиля — P0
= B13 по плитке «Куда иду». **Приёмка:** [ ] плитка ведёт в `going`.

### 6.I Чаты и другие пользователи (Figma 579:52330)

> Рендеров нет; сверить с нодами (`FIGMA-CHECK`). Чужой профиль виден в рендере 665:80818 (страница «Список участников»).

#### I1 · Список чатов — P0
**Figma:** 348:48943 (база), 392:57335 (только личные), 392:57450 (только событий), 348:48692 (вход из профиля). **Сейчас:** `ChatsScreen.kt` — частично. **Маршрут:** `main/chats`.
**Макет:** заголовок «Чаты» 650/28 (паддинг 24/16), корневой экран; ряд `FilterChip`: «Все», «Личные», «События»; строки — UI-kit «Card-chat» (`FIGMA-CHECK` в 175:164; ожидаемо: обложка/аватар 52 r8 или круг, название 590/16, последнее сообщение 510/14@40 % 1 строка, время 510/12@40 % справа сверху, счётчик непрочитанных — синий круг 20 с 590/12 белым; для событийного чата — «Чат откроется за 2 дня» / «Чат закрыт» подписью); пусто: `EmptyState(img3d_chat, «Чатов пока нет», «Чат события открывается за два дня до начала — запишитесь на что-нибудь!»)` (тексты существующие; `FIGMA-CHECK`).
**Данные:** `GET /api-chats/v1/event-chats` + `GET /api-chats/v1/user-chats` (парные из мэтчей; если пусто — вкладка «Личные» с пустым состоянием). **Приёмка:** [ ] фильтр; [ ] два источника; [ ] непрочитанные.

#### I2 · Профиль другого пользователя — P0
**Figma:** 348:49870 (полный), 348:50334 / 348:50534 / 348:50725 (подписаться, написать, избранное, поделиться), 348:51311 / 348:52150 / 392:58048 (жалоба: категории, отправка), 348:52375 / 348:51716 / 348:51932 (блокировка), 635:62298 / 635:62645, 665:80818 (рендер). **Маршрут:** `user/{id}`.
**Макет:** `ScreenScaffold(«Профиль», rightSlot = icon-share)`; карточка: `Avatar(84, ring, verified)`, имя 650/28 «Анна Петрова», «Хамовники» 510/14@40 % с `icon-map-pin`, статус «В отношениях» 510/14 `#36B554` на тинте; кнопки «Подписаться» (Primary тёмная 175×48; после — Secondary «Вы подписаны») и «Написать» (Secondary 175×48); статистика «Подписки 12 · Подписчики 199 · Репутация Высокая»; «Обо мне»; «Интересы» 590/16@40 % → чипы с 3D; «События пользователя» 650/28 → `EventRow`; внизу «Пожаловаться на пользователя» 510/16@40 % (→ шторка «Выберите причину» как в B10, `POST /users/{id}/report`) и «Заблокировать» 510/16 `#F84040` (→ `PodusheAlert`(«Заблокировать пользователя?», «Вы не будете видеть его события и сообщения», «Заблокировать», «Отмена») → J5, `BACKEND-GAP: block`).
**Данные:** `GET /public/organizers/{id}` (name, initials, reputation, events[]) — для непубликовавших 404 → показывать карточку из данных, пришедших с событием/участником (имя, инициалы, район), остальное скрыть; подписка — `POST /users/me/calendars {kind: organizer, value: user_id}`; «Написать» → `dm/{chatId}`, если парный чат есть, иначе snackbar «Написать можно после общего события». **Приёмка:** [ ] из события, участников, «Похожих интересов»; [ ] подписка через API; [ ] жалоба и блокировка.

#### I3 · Подписки / подписчики — P2
**Figma:** 348:53432, 348:53625. **Маршрут:** `user/{id}/subscriptions`. **Макет:** `ScreenScaffold(«Подписки»)` + `SegmentedControl(«Подписки», «Подписчики»)` + список `UserCard` (тап → I2). **Данные:** свои подписки — `/users/me/calendars.items` (kind organizer/topic); подписчики — `FakeSubscriptionsRepository` (`BACKEND-GAP: followers`). **Приёмка:** [ ] две вкладки.

#### I4 · Чат с пользователем (личный) — P2
**Figma:** 635:62536, 635:62876. **Маршрут:** `dm/{chatId}`. **Макет:** B11 без карточки события: шапка — аватар 36 + имя 590/18 + «Пишет…»/«был(а) недавно» 510/14@40 %. **Данные:** `/api-chats/v1/user-chats` (только существующие пары). **Приёмка:** [ ] открывается из I1 и I2.

#### I5 · Чат события из списка — P0
= B11.

### 6.J Настройки (Figma 634:54612)

> Рендеров нет — структура по именам фреймов и подписям переходов; тексты пунктов сверить с нодой 348:56975 (`FIGMA-CHECK`).

#### J1 · Настройки (один экран) — P0
**Figma:** 348:56975 (390×924), 350:57148, 350:57768, 634:54753; выпадающие списки 348:57113, 350:57447, 350:57608, 736:101473; пермишен пушей 634:54752. **Сейчас:** `SettingsScreens.kt` (меню из 5 подразделов) — переписать в один экран. **Маршрут:** `settings`.
**Макет (по переходам):** `ScreenScaffold(«Настройки», sheet = true)`, группы строк 358×~56 в контейнерах r24 `#F5F5F5` с заголовками-лейблами 510/16@40 %:
- «Приватность»: строка «Кому виден профиль» … значение синим («Всем» / «Только участникам моих событий» / «Никому») + chevron → `DropdownSheet`; строка «Кто может писать» … («Все» / «Только участники» / «Никто») → `DropdownSheet`.
- «Уведомления»: «Push-уведомления» + switch (при включении — системный `POST_NOTIFICATIONS`, 634:54752); «Email» + switch; «SMS» + switch.
- «Поддержка» → J2; «Политика конфиденциальности» → J3; «Пользовательское соглашение» → J3; «Заблокированные пользователи» → J5.
- «Выйти из аккаунта» (текст `#F84040`) → `PodusheAlert`(«Выйти из аккаунта?», «Выйти», «Отмена») → велком; «Удалить аккаунт» (красный) → `PodusheAlert`(«Удалить аккаунт?», «Профиль и данные будут удалены через 30 дней. Отменить можно, войдя снова.», «Удалить», «Отмена») → `POST /users/me/delete` (если 404 — Fake + `BACKEND-GAP`) → велком.
Существующие подэкраны «Уведомления», «Приватность», «Документы» удаляются; «Мои интересы» переезжает в H1/H5 (в макетных настройках его нет).
**Данные:** приватность и push/email/sms — локально (`FakeSettingsRepository`, `BACKEND-GAP: settings`); интересы — как сейчас. **Приёмка:** [ ] один экран; [ ] два дропдауна; [ ] пермишен пушей; [ ] подтверждения выхода и удаления; [ ] тексты сверены (`FIGMA-CHECK`).

#### J2 · Поддержка — P1
**Figma:** 350:57906 (форма), 350:58241 (отправляем), 350:58956 (отправлено), 350:59123 (ответ). **Маршрут:** `settings/support`.
**Макет:** `ScreenScaffold(«Поддержка», sheet = true)`: «Мы на связи» 650/28, «Расскажите, что случилось — ответим в течение дня.» 510/16@40 %, поле «Email» и многострочное «Опишите проблему» (r24, счётчик 0/1000), кнопка «Отправить» (disabled до валидного email и ≥10 символов; во время запроса — «Отправляем…»); после 204 — состояние «Обращение отправлено» с 3D-галочкой и текстом «Ответим на {email}»; история обращений и ответы — локальный список (`FakeSupportRepository`, `BACKEND-GAP: support thread`) с карточкой «Ответ поддержки» 510/16 при появлении.
**Данные:** `POST /feedback {email, message}`. **Приёмка:** [ ] форма → 204 → состояние «отправлено».

#### J3 · Политика / Пользовательское соглашение — P1
**Figma:** 350:58077, 635:55170. = A3 с заголовками «Политика конфиденциальности» / «Пользовательское соглашение». **Приёмка:** [ ] оба открываются из J1.

#### J4 · Подтверждение выхода/удаления → велком — P0
**Figma:** 350:59260, 350:59420, 736:101694. Описано в J1. **Приёмка:** [ ] после выхода стек очищен, велком.

#### J5 · Заблокированные пользователи — P2
**Figma:** 635:55200, 635:61741 (пусто), 635:61798 / 635:62021 (разблокировать). **Маршрут:** `settings/blocked`. **Макет:** `ScreenScaffold(«Заблокированные»)`; `UserCard(trailing = кнопка «Разблокировать» Small Secondary)`; пусто: `EmptyState(img3d_users, «Никого нет», «Заблокированные пользователи появятся здесь»)` (`FIGMA-CHECK`). **Данные:** `FakeBlockRepository` (`BACKEND-GAP: block`). **Приёмка:** [ ] блок из I2 попадает сюда; [ ] разблокировка.

### 6.K Дизайн-система — сквозные требования (Figma 175:164)
Полностью описано в разделе 4; здесь — правила приёмки, применимые к каждому экрану:
- [ ] ни одного `Icons.Default.*` там, где в макете иконка из набора (после экспорта ассетов); ни одного эмодзи как иконки;
- [ ] ни одного пустого состояния без `EmptyState`; ни одного подтверждения без snackbar, если оно есть в макете;
- [ ] все кнопки основного действия 48; все заголовки шапок по центру; белый лист r32 там, где он в макете;
- [ ] все тексты — из `strings.xml`, дословно как в макете (включая «ё» и типографские кавычки «»);
- [ ] запросы разрешений только по месту (гео — A5/B1, камера — A8/A11/C1/B11, галерея — A8/C1/B11, уведомления — A13/J1), отказ — с веткой «Перейти в настройки».


---

## 7. Порядок работ: вехи, задачи, файлы, зависимости

Одна задача = один коммит = один экран или компонент, проверяемый отдельно. «Файлы» — что создаётся (`+`) или меняется (`~`); «После» — задачи, без которых нельзя начинать. Оценка в часах — ориентир для планирования одного агента, не обязательство.

### M0 · Дизайн-система и каркас — **закрыта, кроме T-01**
| ID | ✓ | Задача | Файлы | После | ч |
|---|---|---|---|---|---|
| T-01 | ⛔ | Экспорт ассетов по `assets.json` (69 SVG → `ic_*`, 20 PNG ×2/×3 → `img3d_*`, фото велкома, логотипы партнёров); временные замены с `FIGMA-ASSET`, если квоты нет | `+ res/drawable/ic_*.xml`, `+ res/drawable-xxhdpi/img3d_*.png`, `+ res/drawable-nodpi/welcome_*.jpg` | — | 3 |
| T-02 | ✓ | Токены: цвета 4.1, стили 4.2 | `~ ui/theme/Color.kt`, `~ ui/theme/Type.kt` | — | 1 |
| T-03 | ✓ | `strings.xml` + `plurals`: вынести все литералы, добавить тексты макета по разделу 6 и `strings.md` | `+ res/values/strings.xml`, `~ все ui/*.kt` | — | 3 |
| T-04 | ✓ | `PodusheButton` (Large 48 / Small 32, стили, `leadingIcon`), `ListRowButton`, `GlassPair` | `~ ui/components/Buttons.kt`, `+ ui/components/GlassPair.kt` | T-02 | 2 |
| T-05 | ✓ | `PodusheTextField` (label, error, multiline r24, counter, inner shadow), `SearchField`, `OtpField` с ошибкой; удалить `NumPad` | `~ ui/components/Fields.kt`, `+ ui/components/SearchField.kt`, `~ ui/screens/auth/AuthScreens.kt` | T-02 | 3 |
| T-06 | ✓ | `FilterChip` (trailing), `CategoryChip`, `Badge` (icon, tone, size) | `~ ui/components/Buttons.kt`, `+ ui/components/Chips.kt`, `~ ui/components/EventCards.kt` | T-01, T-02 | 2 |
| T-07 | ✓ | `ScreenScaffold`, `BottomAction`, `StepProgress`, `EmptyState`, `PodusheSnackbar` + `SnackbarController` в корневом скаффолде | `+ ui/components/Scaffold.kt`, `+ ui/components/EmptyState.kt`, `+ ui/components/Snackbar.kt` | T-02, T-04 | 3 |
| T-08 | ✓ | `UserCard`, `CabinetRow`, `QuestionCard`, `Avatar` | `+ ui/components/People.kt`, `+ ui/components/Cabinet.kt` | T-01, T-06 | 3 |
| T-09 | ✓ | `PodusheCheckbox`, `PodusheRadio`, `PodusheSlider`, `CalendarGrid`, `WheelDateTimePicker`, `PodusheActionSheet`, `PodusheAlert`, `DropdownSheet` | `+ ui/components/Controls.kt`, `+ ui/components/Calendar.kt`, `+ ui/components/Pickers.kt`, `+ ui/components/Sheets.kt` | T-02 | 6 |
| T-10 | ✓ | `ChatBubble`, `MessageBar`, `EventRow` (18, расстояние, организатор, compact, trailing), `AccentEventCard` (бейдж, мета), `EventMarker` кластер «+ ещё N» | `+ ui/components/ChatComponents.kt`, `~ ui/components/EventCards.kt`, `~ ui/components/EventMap.kt` | T-06, T-08 | 4 |
| T-11 | ✓ | Таббар по 4.5 №10; галерея `UiKitGallery` (debug) со всеми компонентами во всех состояниях | `~ ui/components/TabBar.kt`, `+ ui/debug/UiKitGallery.kt` | T-04…T-10 | 3 |
| T-12 | ✓ | Navigation Compose: `Routes.kt`, `AppNavHost`, `AppScaffold`; перенос существующих экранов на маршруты 1:1 без изменения вида; удаление флагов из `MainActivity` | `+ ui/navigation/Routes.kt`, `+ ui/navigation/AppNavHost.kt`, `+ ui/navigation/AppScaffold.kt`, `~ MainActivity.kt` | T-07, T-11 | 5 |
| T-13 | ✓ | Модели 3.7.2 (переименование `MockEvent → EventCard`), DTO по контракту (`going_preview`, `is_organizer`, `distance_m`, `visibility`, `ends_at`, `declined/waitlisted/cancelled`, `ReportScreen`, `Rsvp.my_queue_position`), интерфейсы 3.7.3 с `Network`/`Fake`; юнит-тесты маппинга | `+ data/model/*.kt`, `~ data/api/Dtos.kt`, `~ data/api/PodusheApi.kt`, `+ data/repo/*.kt`, `+ app/src/test/…/MappingTest.kt`, `~ AppContainer.kt` | — | 6 |
| T-14 | ✓ | `PermissionGate` (запрос, rationale, отказ → action sheet «Перейти в настройки»), `OnboardingFlags` (DataStore) | `+ ui/permissions/PermissionGate.kt`, `+ data/prefs/OnboardingFlags.kt` | T-09 | 2 |
| T-15 | ✓ | `Formats.kt` (4.7) + юнит-тесты форматтеров и plurals | `+ ui/format/Formats.kt`, `+ app/src/test/…/FormatsTest.kt` | T-03 | 2 |
**DoD M0:** ✓ галерея `debug/uikit` снята скриншотами (через Paparazzi, обход B-2); ✓ старые экраны открываются по маршрутам; ✓ `MappingTest` (15) и `FormatsTest` (20) зелёные; ✓ кириллических литералов в `ui/` нет. Открыто: T-01 (блокер B-1).

### M1 · Вход и регистрация (раздел A) — после M0 · **8 из 14**
| ID | ✓ | Экран | Файлы | ч |
|---|---|---|---|---|
| T-20 | ✓ | A1 велком | `~ ui/screens/auth/WelcomeScreen.kt` (разнести `AuthScreens.kt` по файлам) | 2 |
| T-21 | ✓ | A2 номер (4 состояния) | `~ ui/screens/auth/PhoneScreen.kt`, `~ ui/AuthViewModel.kt` | 2 |
| T-22 | ✓ | A3 политика/соглашение | `+ ui/screens/legal/PolicyScreen.kt`, `+ res/raw/policy.md`, `+ res/raw/terms.md` | 1 |
| T-23 | ✓ | A4 код (таймер, лимит, ошибка) | `~ ui/screens/auth/OtpScreen.kt`, `~ ui/AuthViewModel.kt`, `+ test OtpStateMachineTest` | 3 |
| T-24 | ✓ | A14 ветвление | `~ ui/AuthViewModel.kt`, `~ ui/navigation/AppNavHost.kt` | 1 |
| T-25 | ✓ | A5 локация (гео, мини-карта, районы, карта с пином) | `+ ui/screens/registration/LocationStepScreen.kt`, `+ ui/screens/registration/MapPickScreen.kt`, `+ ui/screens/registration/RegistrationViewModel.kt` | 6 |
| T-26 | ✓ | A6 поиск города | `+ ui/screens/registration/CitySearchScreen.kt` | 2 |
| T-27 | ✓ | A7 поиск районов | `+ ui/screens/registration/DistrictSearchScreen.kt` | 2 |
| T-28 | — | A8 о себе (фото, пикеры, валидация) | `+ ui/screens/registration/AboutStepScreen.kt`, `+ ui/screens/registration/PhotoPicker.kt`, `+ test AboutValidatorsTest` | 5 |
| T-29 | — | A9 интересы | `~ ui/screens/registration/InterestsStepScreen.kt` (из `OnboardingScreen.kt`) | 2 |
| T-30 | — | A10 верификация-интро | `+ ui/screens/registration/VerifyStepScreen.kt` | 2 |
| T-31 | — | A11 селфи (CameraX) | `+ ui/screens/registration/SelfieScreen.kt`, `~ app/build.gradle.kts` (camera-core/camera2/lifecycle/view) | 5 |
| T-32 | — | A12 success-шторка | `+ ui/screens/registration/VerifySuccessSheet.kt` | 1 |
| T-33 | — | A13 пермишен уведомлений | `~ ui/screens/afisha/AfishaMapScreen.kt` (однократный запрос) | 1 |
**DoD M1:** сценарии 1–13 раздела 8.5. Закрыты логикой и снимками: 1, 2, 3, 4, 5, 13 (`OtpStateMachineTest`, `AuthBranchingTest`, `ValidatorsTest`, снимки A1–A7). Ждут экранов T-28…T-33: 6, 7, 8, 9, 10, 11, 12. Ручной прогон недоступен — блокер B-3.

### M2 · Афиша (раздел B без события и чата) — после M0
| ID | ✓ | Экран | Файлы | ч |
|---|---|---|---|---|
| T-40 | — | B1 карта: peek 216, чипы, локация, блок интересов, дни, «Куда иду», кластеры | `~ ui/screens/afisha/AfishaMapScreen.kt`, `~ ui/AfishaViewModel.kt`, `~ ui/components/EventMap.kt` | 8 |
| T-41 | — | B2 радиус | `+ ui/screens/afisha/RadiusSheet.kt` | 2 |
| T-42 | — | B3 шторка фильтров | `+ ui/screens/afisha/FiltersSheet.kt`, `~ data/model/AfishaFilter.kt` | 6 |
| T-43 | — | B4 город/районы из фильтра | переиспользование T-26/T-27 с `returnTo` | 1 |
| T-44 | — | B5 календарь | `+ ui/screens/afisha/CalendarSheet.kt` | 3 |
| T-45 | — | B6 поиск | `~ ui/screens/afisha/SearchScreen.kt` (переписать), `~ ui/SearchViewModel.kt`, `+ data/prefs/RecentQueries.kt` | 4 |
| T-46 | — | B7 список | `+ ui/screens/afisha/AfishaListScreen.kt` | 5 |
| T-47 | — | B9 категория (+ подписка) | `+ ui/screens/afisha/CategoryScreen.kt` | 3 |
| T-48 | — | B13 «Куда иду» | `+ ui/screens/going/GoingScreen.kt`, `+ ui/GoingViewModel.kt` | 3 |
| T-49 | — | B14 «Афиша города» + `collection/{slug}` | `+ ui/screens/afisha/CityAfishaScreen.kt`, `+ ui/screens/afisha/CollectionScreen.kt` | 5 |
**DoD M2:** сценарии 14–23; старый `SearchScreen` удалён; все переходы раздела B из `flows.md` воспроизводимы.

### M3 · Событие и чат — после M0 (T-50 после T-42 для шторок)
| ID | ✓ | Экран | Файлы | ч |
|---|---|---|---|---|
| T-50 | — | B10 событие: 8 состояний CTA, шторки (жалоба, отмена участия, share), пейджер похожих, блюр-фон, без хардкода | `~ ui/screens/event/EventScreen.kt` (переписать), `~ ui/EventDetailViewModel.kt`, `+ ui/screens/event/ReportSheet.kt`, `+ test CtaStateTest` | 8 |
| T-51 | — | D3 регистрация с вопросами | `+ ui/screens/event/RegisterScreen.kt` | 4 |
| T-52 | — | B11 чат: карточка события, авторы, даты, статусы, повтор, фото, просмотр, пусто | `~ ui/screens/chats/ChatScreen.kt` (переписать), `~ ui/ChatViewModel.kt`, `~ data/chat/ChatSocket.kt`, `+ ui/screens/chats/PhotoViewerScreen.kt` | 8 |
| T-53 | — | I4 личный чат | `+ ui/screens/chats/DirectChatScreen.kt` (на базе T-52) | 2 |
| T-54 | — | I1 список чатов с фильтром | `~ ui/screens/chats/ChatsScreen.kt` | 3 |
**DoD M3:** сценарии 24–32; `EventScreen.kt` не содержит литералов данных.

### M4 · Создание и кабинет (раздел C, D4–D5) — после M0
| ID | ✓ | Экран | Файлы | ч |
|---|---|---|---|---|
| T-60 | — | C1 форма (все блоки, валидации, теги, wheel-picker, изображения) | `~ ui/screens/create/CreateEventScreen.kt` (переписать), `~ ui/CreateEventViewModel.kt`, `+ ui/screens/create/TagsSheet.kt`, `+ data/repo/FakeDraftExtrasRepository.kt`, `+ test DraftValidatorsTest` | 10 |
| T-61 | — | C2 место | `+ ui/screens/create/PlaceSheet.kt` | 4 |
| T-62 | — | C3 вид организатора + share-шторка | `~ ui/screens/event/EventScreen.kt`, `+ ui/screens/event/ShareSheet.kt` | 2 |
| T-63 | — | C5 «Управление» | `+ ui/screens/manage/ManageScreen.kt`, `+ ui/ManageViewModel.kt` | 3 |
| T-64 | — | C6 онбординг кабинета | `+ ui/screens/manage/ManageOnboarding.kt` | 3 |
| T-65 | — | C7 редактирование | `~ ui/screens/event/EditEventScreen.kt` → `manage/EditEventScreen.kt` на базе T-60 | 3 |
| T-66 | — | C10 участники (3 вкладки, поиск, карточка, исключение, «Одобрить всех») | `+ ui/screens/manage/ParticipantsScreen.kt`, `+ ui/screens/manage/ParticipantCardScreen.kt` (удалить `CrewScreen.kt`) | 6 |
| T-67 | — | C9 вопросы (список, конструктор, drag-and-drop) | `+ ui/screens/manage/QuestionsScreen.kt`, `+ ui/screens/manage/QuestionEditorScreen.kt`, `+ data/repo/FakeQuestionsRepository.kt` | 8 |
| T-68 | — | C8 организаторы | `+ ui/screens/manage/OrganizersScreen.kt`, `+ ui/screens/manage/OrganizerAddSheet.kt`, `+ ui/screens/manage/RoleSheet.kt`, `+ data/repo/FakeTeamRepository.kt` | 6 |
| T-69 | — | C11 приглашения | `+ ui/screens/manage/InviteScreen.kt`, `+ ui/screens/manage/InviteComposeScreen.kt` | 4 |
| T-70 | — | C12 чат из кабинета | `~ ui/screens/manage/ManageScreen.kt`, `~ ui/ChatViewModel.kt` (локальная пустая лента) | 1 |
| T-71 | — | C13 отмена (alert) | `~ ui/screens/manage/ManageScreen.kt` | 1 |
| T-72 | — | C4 QR события | `+ ui/screens/manage/EventQrScreen.kt`, `~ app/build.gradle.kts` (zxing core) | 2 |
| T-73 | — | D4/D5 приглашения в ленте и роли доступа | `~ ui/screens/notifications/NotificationsScreen.kt`, `~ ui/screens/manage/ManageScreen.kt` | 3 |
**DoD M4:** сценарии 33–46; `CrewScreen.kt`, `CreateDoneScreen`, `MockDiscount/QrScreen` удалены.

### M5 · Профиль и настройки (H, I, J) — после M0
| ID | ✓ | Экран | Файлы | ч |
|---|---|---|---|---|
| T-80 | — | H1 профиль (7 блоков, состояния) | `~ ui/screens/profile/ProfileScreen.kt` (переписать), `~ ui/ProfileViewModel.kt` | 6 |
| T-81 | — | H2 мои события | `+ ui/screens/profile/MyEventsScreen.kt` | 3 |
| T-82 | — | H3 прошедшие + отзыв «как прошло» | `~ ui/screens/going/GoingScreen.kt`, `+ ui/screens/event/ReviewSheet.kt` | 3 |
| T-83 | — | H4 QR профиля | `+ ui/screens/profile/ProfileQrSheet.kt` | 2 |
| T-84 | — | H5 редактирование | `+ ui/screens/profile/ProfileEditScreen.kt` (на базе T-28) | 3 |
| T-85 | — | H6 шторки верификации + довершение (3 шага) | `+ ui/screens/profile/VerifyPromptSheet.kt`, маршруты `ProfileComplete` | 3 |
| T-86 | — | H7 статус, местоположение | `+ ui/screens/profile/StatusMenu.kt`, `+ ui/screens/profile/LocationSheet.kt` | 3 |
| T-87 | — | H8 скидки | `~ ui/screens/profile/DiscountsScreen.kt` (переписать), `+ data/repo/FakeDiscountsRepository.kt` | 2 |
| T-88 | — | I2 чужой профиль (подписка, написать, жалоба, блок) | `+ ui/screens/user/UserScreen.kt`, `+ ui/UserViewModel.kt` | 5 |
| T-89 | — | I3 подписки/подписчики | `+ ui/screens/user/SubscriptionsScreen.kt` | 2 |
| T-90 | — | J1 настройки (один экран, дропдауны, пермишен push, выход/удаление) | `~ ui/screens/settings/SettingsScreens.kt` (переписать в `SettingsScreen.kt`), `~ ui/SettingsViewModel.kt`, `+ data/repo/FakeSettingsRepository.kt` | 4 |
| T-91 | — | J2 поддержка (форма → 204) | `+ ui/screens/settings/SupportScreen.kt` | 2 |
| T-92 | — | J3 документы | переиспользование T-22 | 0.5 |
| T-93 | — | J5 заблокированные | `+ ui/screens/settings/BlockedScreen.kt`, `+ data/repo/FakeBlockRepository.kt` | 2 |
**DoD M5:** сценарии 47–57.

### M6 · Свайпы, избранное, уведомления (E, F, G) — после M2 (фильтры)
| ID | ✓ | Экран | Файлы | ч |
|---|---|---|---|---|
| T-100 | — | E1 свайпы: шапка живая, тап/скролл → событие, бейдж «Рядом» по данным, `FIGMA-CHECK` | `~ ui/screens/swipes/SwipesScreen.kt`, `~ ui/DeckViewModel.kt` | 3 |
| T-101 | — | E3 фильтры на свайпах | `~ ui/screens/swipes/SwipesScreen.kt` (FiltersSheet) | 1 |
| T-102 | — | E4 два пустых состояния | `~ ui/screens/swipes/SwipesScreen.kt` | 1 |
| T-103 | — | E6 гайд-тур | `+ ui/screens/swipes/SwipesTour.kt` | 3 |
| T-104 | — | F1 уведомления (фильтр, чтение по тапу, приглашения, обложки) | `~ ui/screens/notifications/NotificationsScreen.kt`, `~ ui/NotificationsViewModel.kt` | 3 |
| T-105 | — | G1 избранные (сегмент, фильтр, snackbar с отменой) | `~ ui/screens/favorites/FavoritesScreen.kt`, `~ ui/FavoritesViewModel.kt` | 3 |
| T-106 | — | G2 люди | `~ ui/screens/favorites/FavoritesScreen.kt`, `+ data/repo/FakeFavoritePeopleRepository.kt` | 2 |
**DoD M6:** сценарии 58–64; все `FIGMA-CHECK` разделов E/F/G/I/J сняты или объяснены.

### M7 · Финиш
| ID | ✓ | Задача | ч |
|---|---|---|---|
| T-110 | — | Снять все `FIGMA-ASSET` (настоящие ассеты), `FIGMA-CHECK` (сверка с нодами) | 3 |
| T-111 | — | Чек-лист 6.K по каждому из 75 экранов; скриншоты всех состояний в `docs/figma/evidence/` | 6 |
| T-112 | — | Итоговый отчёт `docs/figma/REPORT.md` (8.3) | 2 |
| T-113 | — | Удалить мёртвый код: данные `MockEvents.kt` → `FakeEventsRepository`, старые экраны и флаги, неиспользуемые строки | 2 |
Итого ≈ 230 ч работы одного агента; вехи M1, M2, M4, M5 независимы друг от друга и могут идти параллельно разными агентами после M0 (M3 — после T-42, M6 — после M2).

---

## 8. Проверка и отчётность

### 8.1 Критерии приёмки задачи (все обязательны)
1. `./gradlew :app:assembleDushaDebug` зелёный; `lintDushaDebug` без новых ошибок; юнит-тесты зелёные.
2. **Обход B-2 (решение Р-2):** эмулятора в окружении нет (нет KVM), поэтому
   снимки рендерятся на JVM через Paparazzi на кадре 390 dp —
   `./gradlew :app:recordPaparazziDushaDebug`, результат в
   `app/src/test/snapshots/images/`. Compose-тесты `androidTest` по той же
   причине недоступны. Ниже — исходное требование.
2. Скриншот экрана (эмулятор с шириной 390 dp, например Pixel 6a/7 или Resizable 390×844) в **каждом** состоянии из блока экрана — `docs/figma/evidence/<ID>-<state>.png`; в отчёте задачи рядом ссылка на ноду. Допуск по размерам ±2 dp, по цветам — точное совпадение токенов.
3. Все тексты экрана из `strings.xml`, посимвольно как в макете: `grep -rn '"[А-Яа-яЁё]' app/src/main/java/ru/podushe/app/ui` пуст.
4. Поведение — по правилам блока экрана и `flows.md`; каждый сценарий 8.5 своей вехи прогнан руками и записан «Given/When/Then → результат».
5. Ничего вне `android-events/`; сетевые вызовы — только из 3.4; всё остальное — `Fake*` с `BACKEND-GAP`.
6. `FIGMA-CHECK` снят после сверки с нодой, либо в отчёте написано, что сверить не удалось и почему.
7. Никаких `Icons.Default.*` без `FIGMA-ASSET`; никаких эмодзи как иконок.

### 8.2 Тесты (минимум)
- Юнит (`app/src/test`): `MappingTest` (DTO → модели: `going_preview`, `distance_m`, `visibility`, `rsvp.my_status` → `RsvpStatus`, `declined/waitlisted`, статус события по датам и `cancel`), `FormatsTest` (все строки 4.7 и plurals), `ValidatorsTest` (телефон, имя, дата рождения 14–100 лет, ссылка, цена > 0, места ≥ 1, описание 30–1000, теги ≤ 5 и стоп-слова), `CtaStateTest` (таблица 4.8 — каждая строка), `OtpStateMachineTest` (таймер, 5 попыток, лимит 15 мин), `ReputationLevelTest`, `AuthBranchingTest` (A14).
- Compose UI (`app/src/androidTest`, по одному на P0-экран): рендер каждого состояния с проверкой ключевых текстов/кнопок по семантике; для форм — disabled → enabled.
- Команды: `./gradlew :app:testDushaDebugUnitTest`; `./gradlew :app:connectedDushaDebugAndroidTest` при наличии эмулятора.

### 8.3 Итоговый отчёт агента (`docs/figma/REPORT.md`)
Таблица по 75 экранам: ID · статус (сделано / частично / не сделано) · скриншоты · `FIGMA-CHECK` снят? · `BACKEND-GAP` (что осталось на Fake) · отклонения от спеки с причиной. Отдельно: что не удалось сверить; вопросы владельцу (пороги репутации, семантика QR, тексты политики, 9 vs 5 причин жалобы, «Избранные люди», тело `company-invite`).

### 8.4 Что считается нарушением спеки
Экран «примерно похож» без размеров из разделов 4/6; фиктивные данные в живом экране; тексты «своими словами»; пропущенное состояние (ошибка, пусто, пермишен, подтверждение); Material-иконка там, где есть ассет; изменённый бэкенд; новый экран или маршрут, которого нет в разделе 5.


### 8.5 Сценарии приёмки (Given / When / Then)

Каждый сценарий агент прогоняет вручную на эмуляторе и фиксирует в отчёте вехи: шаги, результат, скриншот. Сценарии с пометкой **[Fake]** проверяются на Fake-данных.

**M1 — вход и регистрация**
1. Given приложение без токена · When запуск · Then велком 158:9505 с коллажем; «Войти» → ввод номера.
2. Given ввод номера · When введено 9 цифр · Then кнопка disabled; When 10 цифр → enabled; When номер с недопустимым префиксом (например 000) → «Некорректный номер» красным под полем.
3. Given ввод номера · When бэк отвечает «заблокирован» (эмулировать через Fake `AuthRepository` в debug) · Then текст «Аккаунт заблокирован» + кнопка «Связаться с поддержкой» → экран поддержки.
4. Given ввод кода · When 50 с прошло · Then строка таймера стала кнопкой «Отправить код повторно»; When тап → новый запрос OTP, таймер 0:50.
5. Given ввод кода · When 5 раз неверный код · Then «Лимит отправок исчерпан», кнопка «Повторить попытку через 14:59» disabled, «Изменить номер» ведёт назад.
6. Given новый аккаунт · When код верный · Then системный запрос геопозиции; When «Разрешить» → шаг 1 с городом; When «Не разрешать» → «Поиск города».
7. Given шаг 1, Москва · When выбраны 2 района · Then третий чип не выбирается; When «Весь город» → остальные снимаются.
8. Given шаг 1 · When тап по мини-карте · Then полноэкранная карта с пином; When пин сдвинут → адрес в шторке меняется; «Выбрать это место» → возврат.
9. Given шаг 2 · When «Добавить фото» → «Выбрать из галереи» без разрешения · Then системный диалог; When отказ дважды → action sheet «Перейти в настройки».
10. Given шаг 2 · When имя «ыдфссщтмзтмшуц», дата 11.11.1111, «о себе» «лсцщь…» и «Продолжить» · Then три ошибки с текстами макета, поля с красной обводкой.
11. Given шаг 3 · When выбраны 2 категории · Then «Продолжить» disabled; When 3 → enabled, чипы тёмные с галочкой.
12. Given шаг 4 · When «Сделать селфи» и разрешение дано · Then камера с овалом; When снимок → «Получилось селфи?»; When «Да, далее» и бэк вернул ошибку → «Не удалось распознать лицо»; When успех → «Профиль подтвержден» над картой → системный запрос уведомлений.
13. Given существующий аккаунт (профиль с именем) · When код верный · Then сразу карта, регистрации нет; повторный запуск — тоже карта.

**M2 — афиша**
14. Given карта · Then шторка в покое показывает «Афиша города» и верх рельса (peek 216), чипы «Неделя · Рядом ▾ · Популярные · Новые · Проверенные», кнопка локации при разрешённой гео.
15. Given карта · When тап «Рядом ▾» · Then шторка радиуса; When ползунок 10 и «Применить» → чип «Рядом · до 10 км», список отсортирован по расстоянию.
16. Given карта · When тап фильтров · Then шторка «Фильтрация» с 7 блоками; When выбраны район и «Платные», «Применить» → запрос с `district`, `price=paid`, шторка закрыта; When снова открыть и «Сбросить» → значения по умолчанию.
17. Given шторка фильтров · When «Период» · Then календарь с точками на днях с событиями; When две даты и «Применить» → чип «Период» показывает диапазон.
18. Given карта · When тап лупы · Then экран поиска со стартовым состоянием (недавние, популярные категории); When ввод «Пик» → результаты с датами; When крестик → стартовое; When «Отмена» → карта.
19. Given карта · When «Смотреть списком» · Then лист поднимается на весь экран поверх карты с шапкой город/район и календарём; When протянуть вниз → карта.
20. Given список · When тап календаря и выбор дат · Then блок «Подобрано для вас» перестроен и экран проскроллен к нему.
21. Given бейдж «Спорт» на карточке · When тап · Then страница категории с «Категория “Спорт”», счётчиками, «Подписаться»; When «Подписаться» → «Вы подписаны» (запрос `POST /users/me/calendars`).
22. Given у меня 4 записи · Then блок «Куда иду» на карте показывает первую и «+ еще 3 события»; When тап → раздел «Куда иду» с сегментом.
23. Given «Афиша города» · Then 5 блоков; When тап по подборке → список событий подборки.

**M3 — событие и чат**
24. Given событие с `rsvp.mode = auto`, `seats_left > 0` · Then CTA «Участвовать»; When тап → «Вы идёте» + «Перейти в чат» (если чат есть); When тап «Вы идёте» → action sheet «Отменить участие?»; When «Отменить участие» → «Участвовать».
25. Given событие `selective` · When «Подать заявку» → (при вопросах) форма D3 → «Отправить заявку» · Then «Вы подали заявку» disabled.
26. Given `seats_left = 0` · Then «Достигнут лимит участников» disabled; Given `status = Cancelled` · Then красная «Событие отменено».
27. Given событие · Then блок «66 Идёт» показывает аватары и имена из `going_preview`, а не «АК/ДК»; расстояние — из `distance_m` или скрыто.
28. Given событие · When сердце · Then snackbar «Добавлено в избранные»; When повторно → без snackbar.
29. Given событие · When «Пожаловаться» · Then шторка «Выберите причину» со списком из `GET /report`; When причина и «Отправить» → snackbar «Жалоба отправлена», пункт стал «Жалоба отправлена».
30. Given чат · Then карточка события в шапке; When фокус в поле → карточка скрыта; When отправка → часы → галочка → двойная галочка после `read`; When сеть отключена и отправка → «Не отправлено · Повторить» через 10 с; When «Повторить» при сети → доставлено.
31. Given чат · When скрепка → галерея → 6 фото · Then snackbar «Можно прикрепить не больше 5 фото»; When 2 фото и отправка → пузырь с фото; When тап → просмотр с зумом и свайпом.
32. Given пустой чат · Then «Чат еще пустой» с 3D-иконкой.

**M4 — создание и кабинет**
33. Given «+» · Then одна форма «Новое событие» с плейсхолдером 240×240; «Создать» disabled.
34. Given форма · When «Начало» · Then wheel-picker; When выбрано 31 июля 13:00 → «Конец» = 14:00 по умолчанию.
35. Given форма · When «Выбрать место» → поиск «Нескучный» · Then результаты; When выбор → карточка места с мини-картой и switch «Показывать только участникам».
36. Given форма · When описание 20 символов · Then «20/1000 · минимум 30 символов», «Создать» disabled; When 30+ → «{n}/1000 символов».
37. Given форма · When «Добавить теги» и 5 тегов · Then кнопка тегов disabled; When тег из стоп-листа → «Такой тег нельзя добавить». **[Fake]**
38. Given форма · When «Платно» и цена 0 · Then «Укажите стоимость больше 0 ₽»; When «Ограничено» и 0 → «Минимум — 1 участник».
39. Given форма заполнена · When «Создать» · Then страница события в виде организатора (шестерёнка, «Отменить событие»), экрана «🎉» нет; в черновике ушли `visibility` и `ends_at`.
40. Given своё событие · When шестерёнка · Then при первом входе 4 карточки онбординга свайпом → «Перейти к управлению» → меню из 7 пунктов с бейджем заявок.
41. Given «Список участников», `selective`, 2 заявки · Then сегмент «Заявки / Участвуют / Отклонены», кнопка «Одобрить всех»; When ✓ у первой → она в «Участвуют»; When «Одобрить всех» → snackbar «Заявки приняты» и пустое состояние «Пока нет новых заявок».
42. Given «Участвуют» · When тап по участнику → «Исключить» · Then action sheet; When подтвердить → участник исчез.
43. Given «Вопросы при регистрации» · When «Добавить вопрос» → тип «С вариантами ответа», 3 варианта, «Множественный выбор» · Then карточка вопроса в списке; When 7 вопросов → кнопка disabled и подпись про лимит. **[Fake]**
44. Given «Организаторы» · When «Добавить организатора» → «Все пользователи» → выбор → роль «Ведущий» → «Добавить» · Then карточка с бейджем «Ведущий»; When «⋯ → Удалить» → action sheet → удалён. **[Fake]**
45. Given «Пригласить участников» · When выбрать 3 → «Отправить приглашения» → текст → «Отправить» · Then запрос `company-invite`, возврат в Управление, snackbar «Приглашения отправлены».
46. Given Управление · When «Отменить событие» · Then системный alert с текстом макета; When «Отменить событие» → карта, snackbar «Событие отменено».

**M5 — профиль и настройки**
47. Given подтверждённый профиль · Then 7 блоков как в 693:39392: кольцо и бейдж на аватаре, статистика с уровнем репутации цветом, «Обо мне», плитки «Мои события / Куда иду / Скидки», интересы, «Похожие интересы», баннер дейтинга; QR и шестерёнка в шапке.
48. Given неподтверждённый · Then серый аватар, «Низкая» красным; When вход в профиль → шторка «Подтвердите свой профиль» → «Пройти верификацию» → камера.
49. Given профиль без имени · When вход · Then шторка «Повысьте доверие…» → «Заполнить профиль» → «Шаг 1 из 3» → … → «Профиль подтвержден».
50. Given профиль · When «Указать статус» → «Свободен» · Then пилюля «Свободен» красной тональности. **[Fake]**
51. Given профиль · When чип локации → выбрать районы → «Сохранить» · Then чип показывает районы, `PUT /users/me/event-prefs` ушёл с `districts`.
52. Given «Мои события» · Then сегмент; When «Прошедшие» → группы по датам; Given нет событий · Then «У вас пока нет событий» с 3D-билетом.
53. Given «Скидки партнёров» · When копировать промокод · Then буфер содержит «PODUSHE15», snackbar «Промокод скопирован». **[Fake]**
54. Given QR профиля · When «Скопировать» · Then snackbar «Ссылка на профиль скопирована».
55. Given чужой профиль из события · Then имя, район, кнопки «Подписаться / Написать», события; When «Подписаться» → `POST /users/me/calendars {organizer}` → «Вы подписаны»; When «Заблокировать» → alert → пользователь в «Заблокированных». **[Fake для блокировки]**
56. Given настройки · Then один экран с дропдаунами и тумблерами; When «Push» вкл на Android 13 → системный запрос; When «Выйти» → alert → велком; When «Удалить аккаунт» → alert → `POST /users/me/delete` (или Fake) → велком.
57. Given поддержка · When email и текст ≥ 10 символов, «Отправить» · Then 204 → состояние «Обращение отправлено».

**M6 — свайпы, избранное, уведомления**
58. Given свайпы · When тап по карточке (не по кнопкам) · Then страница события; назад — та же карточка.
59. Given свайпы · When иконка фильтра · Then шторка фильтров; When «Применить» → колода перезагружена.
60. Given свайпы, порция закончилась · Then «Карточки закончились / Показать ещё раз»; Given фильтр без совпадений · Then «Нет подходящих событий / Изменить параметры».
61. Given первый вход в свайпы · Then три подсказки гайд-тура; повторно не показываются.
62. Given 3 непрочитанных · Then бейдж на колокольчике; When открыть ленту · Then бейдж не гаснет; When тап по строке → прочитано, бейдж 2; When «Прочитать все» → 0.
63. Given уведомление-приглашение · Then кнопки «Принять / Отклонить»; When «Принять» → `respond`, строка обновилась.
64. Given избранное с 3 событиями разных интересов · Then чипы интересов; When чип → список отфильтрован; When снять сердце → snackbar «Удалено из избранного · Вернуть»; When «Вернуть» → вернулось.

**M0/M7 — сквозное**
65. Given `debug/uikit` · Then все 32 компонента во всех состояниях; скриншот совпадает с нодами Ui-kit.
66. Given любой экран · Then `grep -rn '"[А-Яа-яЁё]' app/src/main/java/ru/podushe/app/ui` пуст; `grep -rn 'Icons\.Default' …/ui` пуст (кроме помеченных `FIGMA-ASSET`).
67. Given любой экран с системным диалогом · When повернуть/свернуть приложение · Then состояние не теряется (ViewModel), диалог восстанавливается.

---

## Приложение A. Тексты
Полный каталог — `docs/figma/strings.md` (677 уникальных строк трёх страниц, по секциям, с весами и ссылками на ноды). Правила: дословно, включая «ё», пробелы перед «₽» отсутствуют («750₽»), тире в диапазонах времени — дефис с пробелами как в макете («10:00 - 18:00»), кавычки «ёлочки», многоточие «...» тремя точками там, где так в макете («Расскажите о себе...», «Поиск...»).

## Приложение B. Ассеты
`docs/figma/assets.json` — 69 иконок и 20 3D-иллюстраций с id инстансов. Экспорт: `GET https://api.figma.com/v1/images/Bwjfrippxq86431WDzDDP7?ids=<id,...>&format=svg` (иконки) и `&format=png&scale=3` (3D); мастер-компоненты — на странице Ui-kit `175:164` (секции `icons`, `3d-icon`). Фото коллажа велкома и логотипы партнёров — image-fills соответствующих нод (`158:9505`, `348:46562`).

## Приложение C. Инвентарь, редлайны, компоненты, переходы
`docs/figma/screens.json` — 75 логических экранов с нодами, состояниями, находками аудита; `docs/figma/redlines.md` — точная геометрия 172 экранов разделов A–C (каждый блок: координаты, размеры, радиусы, заливки, паддинги и gap auto-layout, стили текста; входящие и исходящие переходы); `docs/figma/components.md` — внутренняя геометрия 46 компонентов UI-kit по инстансам; `docs/figma/flows.md` — 381 переход «экран → экран» с подписями. Использовать как чек-лист: каждый переход из `flows.md` с подписью должен быть воспроизводим в приложении или явно помечен как `BACKEND-GAP`.

## Приложение D. Контракт API — краткая карта
См. 3.4. Ручки, которых нет и которые **не делать**: теги, вопросы регистрации, команда/роли, «только проверенные», радиус, статус отношений, подписчики, блокировки, QR-механика, загрузка изображений событий, ответы поддержки. Для каждой — `Fake*Repository` + `// BACKEND-GAP: …`.

## Приложение E. Известные ловушки (из kb и аудита)
- `EventDraftRsvpModeEnum` = `auto | selective` — не отправлять `off`.
- `/events/deck` отдаёт координаты строками; `/cities` — `id` строкой.
- `DELETE /events/{id}/rsvp` → 200 с телом; 409 `{code}` — `rsvp_closed`, `l2_required`.
- WebSocket чата: ≤ 5 соединений на человека; держать сокет только на открытом экране чата.
- Открытие карты внутри экрана события — только отложенно (после первого кадра), иначе MapLibre блокирует показ на секунды.
- В `local`-флейворе нет dramatiq-воркера: чаты событий и напоминания не создаются автоматически — для демо чат создаётся из django shell (`ensure_event_chat`).
- Вход по OTP = регистрация на бэке: тестовые номера дают чистые аккаунты; ветвление A14 держать на признаках профиля, а не на факте `verify`.
- `distance_m` может быть `null` — метку расстояния скрывать, не показывать «0 м».