# #522 — спецификация дизайна для React Native / Expo Статус: design candidate, DESIGN_APPROVED=PENDING. Редактируемые исходники: `index.html`, `prototype.css`, `prototype.js`. HTML не переносится в RN как готовый компонент. Общая модель маршрутов и тексты — вход для реализации. ## Система и визуальное решение Сохраняются palette и Onest из Consumer Style Guide. Для первого запуска используется короткий Hero с существующим фото кортов, основной CTA выбора города и равноправно доступный вход вторым действием. В каталоге главная единица — GameCard с временем, местом, уровнем и условиями. Создание игры и подробная настройка профиля не занимают первый кадр. **STYLE_GUIDE_PARITY (описательная запись):** переиспользованы Buttons primary/outline/ghost, ContentCard, ListRow, GameCard, FormGroup, InformationalBanner, Empty/Error/Skeleton, Navigation и legal reading pattern. Из current source — действительные поля карточки, тексты безопасности, LevelGuide и public profile defaults. Новое bounded mobile composition: welcome → city, внешняя browser handoff state, возврат к свежим условиям, device-binding status. Это STYLE_GUIDE_GAP для native orchestration, а не новый бренд. OWNER_VISUAL_OVERRIDE не заявлен. По t420u макеты/Guide — ориентир; пиксельное равенство не gate. ## Токены | Токен | Значение / роль | |---|---| | Ink / secondary | `#13221a` / `#536159` | | Page / card / line | `#f5f6f2` / `#ffffff` / `#dfe4dc` | | Strong border | `#cbd3ca`; для смыслового outline при a11y использовать достаточный контраст и focus, не полагаться только на бледную линию | | Primary / soft | `#a7f447` / `#eaffcd`; тёмный текст, не белый | | Accent / soft | `#7945de` / `#eee6ff` | | Error | Текст `#a13c32`, фон `#fff0ed`; raw danger `#ed5c4d` не используется для мелкого текста | | Font | Локальный Onest variable TTF, weights 400/500/600/650/700; OS fallback при ошибке загрузки | | Type | H1 30/34 px (welcome 34/38), H2 20/25, body 16/23, metadata 14/20, малый legal 12/18. Native — scalable logical units; не фиксировать `maxFontSizeMultiplier=1` | | Space | 4, 8, 12, 16, 24, 32, 48; gutter 20 в основном scroll, 16 в header/compact large text | | Radius | Control 12, card 18, sheet 28; круг только avatar/icon container | | Targets | iOS ≥44×44 pt, Android ≥48×48 dp. Основные кнопки min-height 48; Label целиком hit area checkbox | | Motion | 160 ms transition, ease-out / `cubic-bezier(.2,.8,.2,1)`; не ждать animation для запроса. Reduce Motion → без transform/перехода. Skeleton статичный | Проверяемые контрастные пары перечислены в `checks/author-checks.json`, вычисление WCAG sRGB. Значение линии не является обещанием контраста текста. Фокус — сплошной фиолетовый outline 3 px с отступом, не только полупрозрачная тень. Смысл статуса всегда дублируется текстом. ## Компоненты и поведение | Компонент | Contract / состояния | Native реализация | |---|---|---| | AppShell | Safe top/bottom; scroll + navigation; отдельный bootstrap | SafeAreaProvider, Stack/Tabs, bounded error boundary, offline reading state | | WelcomeIntro | Смысл сервиса, guest primary, login secondary | Обычный экран, не blocking tutorial; только первый вход без известной цели | | CitySearch / CityRow | Query, results, loading/error, empty/unsupported | API cities, debounce/cancel stale query, полный список из API; 3 города в HTML — только fixtures | | DiscoveryTabs | Игры / площадки / игроки | Один selected-city context; никакого profile city write | | GameCard / GameConditions | Timezone, places, cost, level rule, venue/host | DTO/SDK + query layer; hidden/private fields не добавлять клиентом | | AuthChooser | Только доступные VK/Яндекс/email; loading/empty/error | Providers endpoint; system browser, never embedded provider WebView | | BrowserAuthScreens | Email/code, expiry/cancel, recovery | Предлагаемый web-native handoff; native email contract отсутствует. Код не логировать | | ActionReview | Loading fresh object, changed, full, unavailable, ready | Reread + expectedVersion + participation state. Submit только explicit | | ParticipationStatus | pending, confirmed, fail, unknown | Server-driven; failure не скрывать успехом; unknown сначала reconcile | | ProfileEnrichment | Имя, город, уровень, фото опциональны | Native controls; full LevelGuide helper/blurbs и сохранение других шкал; не копировать DOM | | PermissionContext | Геолокация, picker, камера, push | Доступные нативные модули на согласованной Expo baseline; OS states reread при foreground | | PushSettings | Preferences / permission / binding / delivery независимы | Backend flags/events + native device registration, не Web Push | | PrivacySettings | directory, contact requests, Radar отдельно | Versioned server writes; сохранить существующие user opt-outs | | LegalDestination | terms/privacy/rules, actual availability | Существующие URL `/legal/terms`, `/legal/privacy`, `/legal/rules`; опубликованных текстов сейчас нет | ## Native presentation и навигация Прототип показывает full-height экраны вместо слишком тесного login bottom sheet на 375×667. Реализация может использовать modal stack presentation с явной кнопкой закрытия; iOS swipe-to-dismiss и Android back делают тот же cancel, сохраняя безопасную цель. Провайдер открывается только системным браузером. Внешняя панель прототипа — инструмент review, в продукт не переносится. На iOS safe area нормального телефона 47/34 в макете, компактного SE 20/0. Android иллюстрирует 24/24; runtime insets нужно получать из ОС, не хардкодить. Рабочие размеры: 375×667, 390×844, 360×740, 412×915 logical units. Android размеры — распространённые compact/normal logical configurations, не заявление об одной конкретной модели. Нет tablet redesign. Bottom tabs переиспользуют «Игры», «Игроки», «Мои игры» и вводят «Профиль» для первого ограниченного native-релиза; сообщения предлагаются отдельным M4-срезом. Это рекомендация состава, не скрытая смена web navigation. Если Owner включает Messages в MVP, M4 должен утвердить 4/5 tab composition и small-device a11y; нельзя просто добавить пятый сжатый ярлык. Back возвращает в сохранённый каталог. External game city не меняет city фильтра. После logout private stack удаляется, токены и caches очищаются; нельзя «назад» вернуться в старый private screen. Deep link до auth сохраняется как validated destination, не произвольная строка URL. ## Клавиатура и текст - KeyboardAvoidingView / insets выбранного RN stack; ScrollView с `keyboardShouldPersistTaps="handled"`. На Android проверить adjustResize/edge-to-edge; на iOS — keyboard frame и safe-bottom без двойного inset. - Кнопки формы находятся в том же scroll content. Sticky CTA только если по факту не перекрывает клавиатуру; в large text допустим inline CTA. При keyboard nav можно скрывать, но submit/cancel доступны прокруткой. - Email keyboard, capitalization OFF; OTP single field с one-time-code autocomplete, paste поддерживается. Не шесть разрозненных focus traps. - Form labels постоянны, placeholder не единственная подпись. Ошибка связана с input (`aria-describedby` / native accessibilityHint); после неудачного verify focus возвращается на code и error объявляется один раз. - 200% текста: строки/кнопки растут, providers/tabs перестраиваются в колонку, summary dt/dd — один столбец. Нет fixed-height text, truncation ключевых условий, горизонтального scroll в телефоне. Декоративная welcome-photo скрывается на large text; смысл не теряется. - В HTML keyboard preview — только 244 px reserved area, не проверка реального IME. `setScenario(...,{keyboard:true,textScale:2})` позволяет проверить геометрию review-сборщиком. ## VoiceOver / TalkBack Каждый экран имеет озвучиваемый заголовок; после navigation focus переводится на него. После закрытия системного picker/браузера — к управляющему действию или к понятному результату. Native требует AccessibilityInfo announcements и проверки реального focus order; HTML focus не доказывает native behavior. Buttons имеют role, label и state; выбранные tabs — selected/current. Иконки рядом с текстом декоративны, логотип озвучивается один раз. Строка города — один target, не текст плюс мелкая стрелка. Checkbox доступен через всю подписанную строку. Количество мест, pending/confirmed и изменённое время передаются текстом. При loading использовать busy/status, error — polite/alert по важности, не читать весь экран повторно. System sheet скрывает background accessibility tree; HTML использует full screens и не создаёт fake modal trap. ## Источник, адаптация, новое, внешнее | Категория | Конкретно | Граница | |---|---|---| | Есть и переиспользуем | HTTPS API cities/venues/games/public profiles, participation/capacity/rights; account UUID; defaults/privacy; event policies/outbox; shared contracts/SDK; tokens, local Onest/logo/photo | Backend остаётся источником статусов. Не переносить ORM, DB-access или secrets | | Есть в web, адаптируем | AuthChooser texts, optional ProfileSetup city semantics, LevelGuide helper, game fields, allowed return paths, map assets, push preferences | Next/DOM/CSS не native components; cookie+CSRF web endpoints не portable bearer API | | Новая mobile реализация | Expo project, Router/shell, query/cache namespaces, secure session, broker exchange/refresh, app links, permissions, native media, push binding | `apps/mobile` сейчас README-only. Все эти пункты не SOURCE_READY | | Owner / external | Концепция/MVP, iOS 4.8 choice, страны/каналы/operator, developer roles/builders/signing, legal texts/account deletion | UNKNOWN доступа не равно «нет аккаунта»; без покупки и без поиска credentials | ## Уточнения для инженера 1. Web city-only setup не переносить механически как server join requirement. В native город каталога остаётся локальным; profile city редактируется отдельно. Прямой game link может дойти до join без заполнения профиля, если actual backend permits. 2. `social-profile-defaults.ts` — более точный источник безопасного public name, чем ранняя фраза addendum о provider displayName. Public auto-name всегда «Игрок». Identity/account fields приватны. 3. Текущий LevelGuide содержит 13 half-step значений и сохранение другой шкалы; макет показывает все значения и компактную выдержку помощи. Реализация переиспользует весь helper и тексты, не только видимые примеры. 4. Terms/privacy/rules существуют как routes с состоянием «Не опубликовано». Нельзя из макета вывести готовность юридического согласия. Publish legal + требуемый consent/version capture — отдельный результат до реальной открытой регистрации. 5. Имя организатора в create — собственное поле 1–160, не обязательное имя social profile. Chat/privacy/public profile rights перепроверяются сервером. ## Что передаётся QA Основной journey, отказ geo, cancel OAuth, изменившиеся условия, смена аккаунта — обязательный содержательный review; параметры четырёх viewports и 200% открыты через API. Parent запускает установленный Chromium для screenshots после этой terminal-сессии. Здесь выполнены syntax/state/asset checks; browser layout, реальные экраны, VoiceOver/TalkBack, клавиатура ОС и binary — NOT_TESTED. Это авторская проверка, не независимый PASS.