# Desktop overlays · Style Guide extension v1.0 **PROPOSED_NOT_OWNER_APPROVED · 28.10.2026 · UI design / Astra High** Рекомендация: **«Просторный 40»** — единая левая линия содержимого 40 px, отдельная читаемая шапка, X в 24 px от верхнего и правого края. Это предложение для согласования целиком; отдельное согласование каждого CSS-пикселя не требуется. ## Основание и пределы - Исходники прочитаны из `/home/agentagency-dev/mode-a-365-uat-repair`, HEAD `fb255124e2b7a3064b9daa078f4137119db089ba`, tree `d32e67eea3fac5a2ad692a41bbfbcc28fadc18ab`; рабочее дерево чистое до работы. - DEV `r40764084636-a1 ACCEPTED` и совпадение tree с merge `a5c2554311ed8b36a5cb368f6addd75a351cd0a3` — сведения trusted assignment. Объекта merge в локальном Git нет; live/network не проверялись. - Сначала прочитаны frozen `/opt/gobadminton-clean-qa/style-guide/src/App.tsx` и `src/index.css`: ConstructionKit, Foundation, Modal/Drawer/BottomSheet/Popover, surfaces/forms/actions. - Repo `docs/decisions/OWNER_DECISION_FRONTEND_STYLE_GUIDE_FIRST_V001.md`: позднее t420u допускает ограниченные расширения приблизительного ориентира. - Расширение касается desktop **от 1024 CSS px**. Mobile, жесты, мобильный X, обычная страница `PublicPlayerProfile`, права/данные/API и правила UNKNOWN остаются прежними. - Принятый результат #370: компактная игра, настоящая обложка, обычная/тренерская композиция сохраняются. Radar refresh/TTL исследует PMA отдельно; этот дизайн не меняет expiry, consent или видимость игроков. - Это локальный versioned proposal. Frozen copy, опубликованный Figma.site и репозиторий не изменены. Не новый framework или глобальный регламент. ## Что действительно видно и откуда взята величина отступа Открыты как изображения: три `owner-365-desktop-map/01…03` и четыре снимка `mode-a-365-uat-browser/after-live-trainer-game-1440x900.png`, `after-live-uploaded-venue-1440x900.png`, `after-live-mobile-expanded-390.png`, `current-player-component-1440x900.png`. Последний — actual components/CSS с **синтетическим** Radar/profile fixture; это не наблюдение живого игрока. `player-design-layout.json` прочитан. Owner UAT 184057/184112 отсутствуют и не просмотрены. | Факт | Причина в исходниках / вывод | |---|---| | В player fixture панель x=970, край hero x≈1010; имя x=1026 | `venue-popup.css`: outer 24 + `radar-geographic.css`: expanded preview 16 = **40 до карточки**; `players-profiles.css`: ещё 16 внутри hero = **56 до имени**, подтверждено JSON (1026−970). | | Заголовок безопасности x=1037 | 40 + 1 border + 26 padding = 67. Это внутренний отступ смысловой карточки, не основной gutter окна. | | У venue/game содержимое начинается x≈994 | Только outer 24. Не следует объявлять понравившиеся 56/67 px общим gutter: это вложенные карточки. | | Player X у самого верха и далеко от правого края панели | `unified-preview__sheet-head` находится внутри expanded article, padding-top=0; X выравнивается относительно вложенного содержимого. | | Venue X выше заголовка | В desktop info header padding-top=16, X absolute top=8/right=0 относительно внутренней шапки; info унаследовал специальную композицию мобильной обложки. | | Game Back, venue title и X конкурируют в одной строке | Flex header с текстовым Back, 18 px title, ellipsis; на снимке адрес усечён. | | Modal/picker/filters имеют иной ритм | `ProfileDialog`: padding 28, h2 28, скролл всего dialog; picker: 20/22 и h2 20; filters: 10/16 и h2 18. | **Интерпретация предпочтения Owner:** принять измеренные 40 px до поверхности профиля как общую линию тела. Внутренние padding карточек остаются своими. Симметрию справа задаёт оболочка; случайные скроллбары/дополнительные обёртки fixture не копируются. ## Ограниченный реестр фактических семейств Пути далее относительно `apps/web/src/components/consumer/`; CSS — `apps/web/src/styles/`. Найденные потребители сгруппированы по контракту, а не по каждой комбинации данных. | Семейство / фактические состояния | Source | Предлагаемое применение | |---|---|---| | Карта: venue info, schedule, game regular/trainer, create и preview | `games/{DesktopMapInspector,VenuePopup,VenueSheet,DesktopInspectorGame}.tsx`, `pages/CreateGamePage.tsx`; `venue-popup.css` | 450 view / 640 create, общая шапка; немодальная панель. | | Radar player: профиль / ближайшие публичные игры, loading/unavailable | `radar/UnifiedPlayerPreview.tsx`, `players/{PublicPlayerProfile,PlayerProfileContent}.tsx`; `radar-geographic.css`, `players-profiles.css` | Тот же inspector; chrome вне profile body; обычный PublicPlayerProfile переиспользуется без переделки внутренних блоков. | | Radar list / geographic panel | `radar/{GeographicRadarMap,RadarComposition,RadarNearbyList}.tsx`; `radar.css`, `radar-geographic.css` | Сохранить текущую отдельную list surface и её ширину 360; общий X/header без принудительных 450. `RadarPanel` — также существующая декларация, не обещание её активности в live. | | Обычный native `ProfileDialog`, в т.ч. wrapper «Участник Radar» | `players/ProfileComposition.tsx`, `radar/UnifiedPlayerPreview.tsx` | Normal 560; `showModal`, Escape, backdrop и возврат фокуса сохраняются. Отдельного компонента `PlayerProfileDialog` не найдено; публичный профиль также имеет обычную page-route. | | AuthChooser / email flows | `players/{ProfileComposition,EmailCodeForm}.tsx`, `pages/LoginMethodsPage.tsx` | Normal 560, длинное раскрытие email в body, доступные способы/ошибки/успех остаются из фактического состояния. | | Profile edit / looking for game / cover edit | `pages/ProfilePage.tsx`, `players/{CoverEditor,AvatarEditor}.tsx` | ProfileDialog 560; avatar editor внутри формы. Не создавать дополнительный modal для inline editor. | | Выбор площадки: поиск/loading/error/empty/selected | `games/VenuePickerDialog.tsx`; `games.css` | Сохранить 680 и portal/modal/focus contract; header+search вне единственного списка-scroll. | | Фильтры карты | `games/MapLayerFilters.tsx`; `radar-geographic.css` | Сохранить 480, существующий modal; единые шапка/кнопки, один body-scroll. | | Report / block / friend removal / Radar consent | `players/PublicPlayerProfile.tsx`, `messages/ConversationSafety.tsx`, `friends/FriendActions.tsx`, `radar/RadarSessionProvider.tsx` | Короткие подтверждения 400; жалоба и длинное согласие 560. Busy/uncertain guard остаётся у caller. | | Message/contact/invitation/member list | `radar/RadarContact.tsx`, `messages/{InvitationChooser,GameConversation}.tsx` | 560 ProfileDialog; текст/textarea/list в body. `NewContact`, `DirectConversation`, `ContactAction` — также inline/page content, не превращать в новые popups. | | Game actions и share | `games/{VenueGameActions,GameActionBar,GameShare}.tsx` | Footer существующего inspector; auth и fallback share используют ProfileDialog. Native OS share остаётся OS; status notice остаётся inline. Не добавлять «пригласить друга» вместо share. | | Контекстный Radar popover | `radar/RadarSessionProvider.tsx`; `foundation.css` | Сохранить 280, привязку к trigger и немодальность. Padding 18, свой компактный контекст; новый X не навязывается popover без X. | | Media / fullscreen | `players/CoverEditor.tsx`, `games/{GameCover,GameCoverEditor,GamePhotoSelection,GamesMapViewport}.tsx`, `venues/{VenueCover,VenueCoverEditor,SelectedVenueCover}.tsx` | Cover preview внутри существующего dialog; остальные редакторы inline. Отдельного photo lightbox/gallery modal в consumer source не найдено — не изобретать. Fullscreen map — viewport/route с «К списку», не media modal. | | Меню, select, подсказки | `RootNav.tsx`, `discovery/DiscoveryComposition.tsx`, поля форм, `GameShare.tsx` | Сохраняются native select/title и существующие disclosure/навигация; не превращать в 450 px dialog. | ## Каноническая база → предлагаемое расширение **Уже база:** Onest; ink `#13221A`, secondary `#536159`, white `#FFF`, paper `#F5F6F2`, line `#DFE4DC`, lime `#A7F447`, purple `#7945DE`; радиусы 12/18/28; шаги 4/8/12/16/24/32/48; существующие `Button`, `CloseButton`, `SurfacePanel`, `ContentContainer`, `SectionHeader`, native `ProfileDialog`; паттерн `StickyActionBar` из Guide (не заявление о наличии одноимённого React export). **STYLE_GUIDE_GAP / extension v1.0:** Guide показывает семейства, но не задаёт единый desktop anchor X, сумму вложенных inset и плотность narrow map rail. Добавляются только локальные параметры композиции: | Параметр | Предложение | |---|---| | Content inset | **40 px** в header/body/footer normal/view/wide; 400 small — 32 px; 280 popover — прежние 18 px. 40 = 24+16 из существующей шкалы. | | X | Существующий круг 44×44, glyph 24; **top/right 24 px от внутреннего края shell**, включая small/normal/wide/list. Focus ring не обрезается. | | Header | White; верх 24, низ 16. Первая строка min 44: Back или короткий kicker, справа независимый X. Между строками 8; следующая строка — title. Никакого пустого дополнительного profile-header внутри body. | | Title | 22/26 px, 700 (small 20/26); section 16/22, 600–700; основной текст 14/20, helper 12/17. Не уменьшать текст ради fit. | | Body | Верх 16, межсекционный gap 16 (form group 24), низ 16. `min-height:0`, один vertical scroll owner. Insets назначает один контейнер. | | Footer | White, 1 px line; сверху 12, снизу 16; inset совпадает с body. Game actions 44 min, формы 48 min; gap 8, helper 12/17. Высота auto, кнопки могут переносить текст. | | Surface | Radius 28, inner cards 18, fields 12, прежняя elevated shadow. Без новых декоративных рамок вокруг каждого текстового блока. | | Ширины | View 450 / create 640 при ≥1280; существующие 400/520 при 1024–1279 сохраняются. Normal 560, small 400, filters 480, picker 680, Radar list 360, popover 280. Всё ограничено доступной шириной. | | Высота | Inspector: текущие top/bottom/right 20 относительно map viewport. Modal: max-height `calc(100dvh - 48px)`, центрирование; picker сохраняет собственный limit. | ### Шапка, Back и X 1. Верхняя строка занимает y=24…68. X никогда не выравнивается по последней строке заголовка, аватару или медиа. 2. Back слева — существующий callback и доступная подпись («К площадке», «К профилю»); область не менее 44 high. Для текста Back зарезервировать правую область X + gap 12. Длинная подпись переносится/сокращается визуально с полной accessible name. 3. Title начинается ниже строки управления, обычно y=76. Поэтому название может занимать всю ширину тела: текстовая строка Back не съедает ширину venue title. 4. Для длинного имени допускается две строки шапки; полное имя/адрес остаются доступными в body. Не скрывать условия, warning или юридически значимый текст через ellipsis. Предельно длинный content не должен раздувать фиксированную шапку: короткий тип окна + полное название в body. 5. Header/footer — отдельные flex/grid строки оболочки, body — `minmax(0,1fr)`. Сам shell не скроллится. Не создавать второй `overflow:auto` внутри PublicPlayerProfile или game body. 6. Если ниже header есть закреплённый поиск picker, считать его частью верхней области; список остаётся единственным scroll owner. Горизонтальная строка дат сохраняется, это не второй вертикальный скролл. ### Ритм контента и медиа - У площадки заголовок, изображение, адрес, контакты и empty/schedule идут по одной линии x=40. Реальная cover имеет приоритет, fallback не выдаётся за фото конкретной площадки. 16:9, максимум 200 high, прежние credit/provenance сохранены. - В игре оставить **88×88** GameCover и date badge, snapshot padding 14/gap 14; сохранить организатора, trainer + «Самостоятельно указано», места, адрес, settlement и rental note, описание и существующее раскрытие >160 символов. - Facts в rail можно распределить 96 px label + 10 gap + остаток value вместо max120: это ограниченная настройка текста, помогает тренерским условиям при 40 px gutter. При большом шрифте labels/values становятся одной колонкой. - У игрока сохранить hero, avatar, profile cards, safety и публичные игры. Удалить только дублирование **внешнего** padding в desktop host; hero text padding16 и смысловые profile cards не обнулять глобально. - В формах сохранить grouping/labels/validation; поля в create640 в две колонки, длинные поля на всю ширину. Не ставить новый border/padding поверх существующего FormGroup без причины. - Footer содержит только уже существующие действия. У venue переносится существующая CTA («Показать игры…» либо «Создать игру…» согласно данным); не добавлять обе одновременно. Player action proximity сохраняется в его body; отдельный пустой footer не нужен. ### Высотный бюджет — проектная оценка, не результат app-теста | Сценарий | Доступная геометрия / решение | |---|---| | 1440×900 и 1920×900 | Inspector450×860. Header ≈118 (однострочный title), game footer ≈148; body ≈594. Обычный game и trainer с **72-char** описанием должны помещаться без body-scroll при штатном шрифте. | | Trainer standard | Snapshot ≈146, facts ≈250–280 (включая settlement/rental note), описание ≈78, gaps+body padding ≈64: ≈538–568. Запас ≈26–56; увеличение header/title расходует его и включает scroll. | | 1366×768 | Panel450×728, body ≈462; допустим единственный body-scroll, X/Back и действия остаются доступны. Не скрывать тренера/условия, не уменьшать controls. | | Длинный title/terms/description | Header до 2 строк; body-scroll и существующее раскрытие описания. Длинные слова/URL переносятся, footer растёт по содержимому. | | 200% text scale | Текст растёт, facts/forms становятся одной колонкой, action buttons могут стать столбцом. При недостатке места для header+footer перейти к одному общему scroll с sticky control row и доступным footer в конце; не оставлять body высотой 0 и не добавлять вложенный scroll. | Нормальный вариант не должен требовать отдельной «плотной» карточки тренера. Единственная запасная альтернатива **«Компактный 32»**: inset32 вместо40, всё остальное то же. Рассматривать только если Owner предпочитает больше ширины текста; автоматически выбирать его по типу окна не следует. В preview есть переключатель сравнения. ## Состояния и поведение | Состояние | Визуальное правило / сохранённый контракт | |---|---| | Loading | Шапка и X остаются на месте; нейтральный status в body. Не рисовать вымышленные game/player данные. | | Empty | Короткое объяснение и только доступное действие. Нет игр ≠ ошибка загрузки; нет контактов ≠ разрешение их выдумать. | | Error | Сообщение рядом с проблемой, retry только там, где caller его допускает. Geolocation/Radar error не превращается в OFF без фактического состояния. | | Access denied / expired / unavailable | Данные скрывает существующая логика, состояние показывается в body; не удерживать просроченный профиль ради визуальной стабильности. | | Busy | Размеры shell/actions стабильны; progress/disabled отражают реальное состояние. Сохраняются guards X/Escape/backdrop и submit, не разрешать повторную мутацию стилем. | | UNKNOWN | Прежний notice о неопределённом результате + заблокированный повтор, где это предусмотрено. Не рисовать success и не добавлять кнопку «Повторить» по умолчанию. | | Long text / font scale | `overflow-wrap:anywhere`, `min-width:0`; условия полностью читаемы в scroll. Tab-фокус не скрывается под header/footer; scroll-padding соответствует chrome. | | Media/no image/load failure | Существующая cover pipeline/fallback и crop preview; фото не создаёт второй X. Отсутствие обложки не резервирует пустую fullscreen-галерею. | | Keyboard | Существующие focus-visible 3 px, control44, DOM order. Native dialog сохраняет top-layer, focus containment/return, cancel. Inspector остаётся немодальным, Tab доступен карте. | | Nested modal | Auth/share/report поверх inspector сохраняют свой backdrop/focus contract; underlying Escape не должен срабатывать первым. Число modal engine не увеличивается. | Обычный inspector без затемнения и focus trap. Create сохраняет **текущее** затемнение и dismiss (это не повод превращать его в native modal). View→Back/X/Escape/browser history остаются разными существующими переходами. Визуальная унификация не меняет эти обработчики. ## Preview и передача после решения Owner `desktop-overlay-preview.html` — один offline HTML, CSS-only выбор сценария/viewport/32 vs40/крупного текста; никакого JS, API или авторизации. Показаны обычная/тренерская игра, venue, player, create, confirmation, AuthChooser, filters, picker, schedule, player-games, share/contact/cover. Все данные обозначены **design example**, public demo trainer text взят с предоставленного снимка. Исходные фото из repo встроены без изменения; Onest применяется при локальном наличии, иначе Arial (точные font metrics NOT_TESTED). Минимальный порядок будущей реализации, только после согласования: 1. Зафиксировать v1.0 extension в repository guide/docs и затем опубликованном Guide через trusted process; не править frozen `/opt`. 2. `venue-popup.css`, `VenueSheet.tsx`, `UnifiedPlayerPreview.tsx`, `radar-geographic.css`: общая desktop anatomy, gutter ownership, header вне body; при необходимости тонкая локальная композиция в `DesktopMapInspector.tsx`/`VenuePopup.tsx` без изменения lifecycle. 3. `players/ProfileComposition.tsx` + `players.css`: desktop slots/body-scroll и ограниченные size variants существующего ProfileDialog; `CloseButton.tsx` переиспользовать без изменения поведения. Caller form-actions адаптировать точечно, сохранив form association/guards. 4. `VenuePickerDialog.tsx` + `games.css`, `MapLayerFilters.tsx` + `radar-geographic.css`: те же anchors; `GeographicRadarMap.tsx` — только list chrome. Не переписывать все consumers и не менять Radar controller. 5. `DesktopInspectorGame.tsx`/его CSS и `CreateGamePage.tsx` трогать только в пределах slots/spacing/footer; source для cover, settlement, privacy не менять. Адресная последующая проверка: regular+trainer72 на 1440/1920×900; 1366×768 long title/terms; один font-scale sample; venue/player X; modal/picker focus и один mobile regression ниже1024. Это **будущая** проверка содержательного кандидата, не уже пройденные QA/CI/DEV smoke. При реализации mandatory gates остаются обязательными. **Для Owner — одно решение по предложению, четыре аспекта:** (1) 40 px player-like inset; (2) шапка с Back отдельной строкой и X 24/24; (3) постоянные действия внизу и один body-scroll; (4) разные функциональные ширины при общей визуальной системе. Рекомендуется принять основной вариант целиком; 32 px только как явно выбранная альтернатива.