# #458 · Компактные фильтры · design handoff Автор: Astra / gpt-6-astra, HIGH, одна прямая Owner-assigned design call. Новый UI — дизайн-кандидат, **не Owner-approved**. Внешняя браузерная проверка выполнена после завершения авторского вызова. [Независимый отчёт](independent-review.md) фиксирует отдельную проверку. Owner UAT остаётся NOT_TESTED. Прямое поручение Owner охватывает дизайн и последующую реализацию этого delta; дополнительный approval gate не вводится. Изменены только файлы прототипа. Старая техническая поставка #458 alpha.23, PR #477, SHA cc771c3700accae431e7bff9131433cd7be694ae, receipt r41825585671-a1 сохраняется; этот дизайн не объявляется частью alpha.68. Открыть [index.html](index.html) локально или через статическую раздачу **только этой public/**. Без сборки, зависимостей, внешних шрифтов, запросов API, local/session storage. [capture-plan.json](capture-plan.json) — восемь основных кадров; [prototype-manifest.json](prototype-manifest.json) — allowlist пакета. Не раздавать родительский каталог: в нём private inputs и служебные артефакты оркестратора. ## Решение Одна кнопка 44×44 с существующим `filter.svg` заменяет исходный `span.gb-discovery-sort` справа от чипов. Визуальной подписи «По времени» больше нет. Accessible name всегда «Фильтры»; `aria-haspopup="dialog"`, `aria-expanded`, `aria-controls`; `aria-describedby` сообщает активный уровень. Активность уровня отмечена контрастной точкой и рамкой/фоном. Точка не счётчик результатов или всех фильтров. Город, дата и тип по-прежнему сами показывают свой выбор. CLOSED: никакого большого LevelGuide и feed eyebrow «Выходите на корт». Между строкой чипов/значка и заголовком списка нет зарезервированного места панели. Hero, поиск, create banner, список и sidebar сохраняются. Исчезает только указанный feed eyebrow, не предложение в существующем hero. OPEN: dialog «Фильтры»; короткая read-only строка текущего города/даты/типа; существующий native select уровня с «Без фильтра по уровню»; текущий выбор; точное ограничение загруженного списка и шкалы; свёрнутая справка. «К списку» только закрывает, ничего не применяет. Нет draft, Apply, сортировки, диапазона или новых типов игр. ACTIVE: немедленный выбор `ntrp v1`; точка на исходном значке; одна короткая строка «Уровень 3,0 · только загруженные игры (ntrp v1)». Счётчик сохраняет source distinction: найденные в loaded subset, `+` при nextCursor и «из N загруженных». Никакого придуманного общего количества. Расширенное пояснение о других шкалах остаётся внутри панели/справки и в level-empty. RESET: «Сбросить уровень» и select «Без фильтра по уровню» делают только `setLevelFilter("")`. Уровень выключен, город/дата/тип/персональный тренер остаются. Scenario RESET показывает дату 11 ноября и «С тренером» при сброшенном уровне. При невыбранном уровне reset disabled; после нажатия фокус переводится на select, а не теряется на disabled button. HELP: native `details/summary`; 13 исходных определений 1,0–7,0 с шагом 0,5 из frozen `level-guide.ts`, без новых категорий. Кнопка определения выбирает уровень сразу, остаётся в справке и отмечается `aria-pressed`. Открытие справки ничего не выбирает и не пишет в профиль. Выбор только явный; профиль не подставляется автоматически. Другие scaleId/version не совместимы даже при одинаковом числовом levelId. ## Границы и разрешённая неоднозначность «Показать существующие настройки» трактуется как доступ к скрытому уровню, со сводкой остального контекста. Полезные city/date/type controls остаются на исходных местах, без второго экземпляра в dialog. Во время modal они inert; для смены даты/типа пользователь закрывает панель. Это сохраняет непосредственную фильтрацию и исходные маршруты, не создаёт новую форму. Индикатор у кнопки означает только скрытый level filter: дата/тип уже видны, trainer context сохраняет отдельный исходный helper и «Все тренировки». Нет глобального «сбросить всё» с неоднозначной судьбой города. В production не менять `filterActive`, feed cache key, URL/day/trainer handling, sorting или запросы. ## 390 / 1280, фокус и dismiss - 1280: filters-family width 480, radius 28; header/body/footer inset 40, X 44×44 top/right 24, title below chrome row, max-height `100dvh - 48px`. - 390: bottom sheet width 100%, radius 28 сверху; inset 20, X 44×44 top/right 16; max-height `100dvh - 24px`, footer учитывает safe-area. Native top layer перекрывает bottom nav и map CTA; фон inert. Не добавлять к sheet второй nav reserve. - `showModal()` обеспечивает нативную modal/focus isolation. Начальный фокус на level select. Tab / Shift+Tab — нативные controls, затем summary/definition buttons, reset и close action. Нативный select сохраняет стандартную клавиатуру; Escape сначала может закрыть его OS menu, следующий — dialog. - Escape (`cancel` по умолчанию), X, «К списку» и click по backdrop закрывают без reset. Backdrop проверяет и pointerdown, и click вне rect, чтобы drag из тела не закрывал panel. `close` возвращает фокус исходной кнопке с `preventScroll`. - Один scroll owner — `.dialog-body`; header/footer не прокручиваются, все определения достижимы. Фон блокирует прокрутку. Все действия ≥44 px; select text 16 px на mobile, фокус purple. Нет обязательной анимации. Открытие/закрытие не меняет scroll списка и фильтры. - Реализация должна подключить существующие project overlay/history conventions, без нового глобального modal engine. Прототип проверяет только native Escape/close/backdrop, не симулирует production browser history controller. ## Минимальная реализация в существующих исходниках Base HEAD `568fe0443064a54e3dfe198f20a706258245cbb9`; accepted DEV68 tree alias `2c33ce49d5b8ad704bf78524b8149c94e4d3e62e` принят из trusted assignment. Все 10 frozen file hashes проверены локально. Git не вызывался, live runtime не проверялся. 1. `apps/web/src/components/consumer/pages/GamesDiscoveryPage.tsx`: около 338 заменить один span на button; dialog ref + trigger ref + локальная open-state синхронизация. Существующий controlled `levelFilter` остаётся у страницы и не уничтожается вместе с dialog. Переместить единственный discovery LevelGuide в dialog, убрать feed heading small. Сохранить showCards, filtering, pagination, error/stale, URL context, trainer helper и map honesty; активный длинный scope helper сократить как показано, полный scope оставить внутри dialog. 2. `apps/web/src/components/consumer/LevelGuide.tsx`: добавить опциональную discovery-only compact presentation (default существующее поведение). Использовать текущий select/options, typed `value/onChange`, helpers, details, descriptions и disclaimer. Для compact убрать внешний eyebrow/большой h3, позволить заголовку dialog именовать section. Reset label уточнить до «Сбросить уровень» только здесь. Ничего не менять в profile presentation, keep/pilot/save logic. 3. `apps/web/src/styles/discovery.css`: стили единственной trigger, active dot, compact helper и scoped dialog chrome. Существующий mobile `.gb-discovery-sort {display:none}` не должен скрыть новую кнопку: использовать отдельный новый class. Чипы могут прокручиваться горизонтально на экстремально узких экранах, trigger остаётся отдельным flex item 44 px. 4. `apps/web/src/styles/level-guide.css`: только compact discovery modifier, без воздействия на профиль. Повторно использовать project `CloseButton`/overlay chrome при реализации; DOM prototype — спецификация поведения, не готовый React patch. 5. **Без изменений**: `apps/web/src/lib/level-guide.ts`, `apps/web/src/components/consumer/discovery/DiscoveryGameCard.tsx`, `apps/web/src/components/consumer/discovery/DiscoveryComposition.tsx`, `apps/web/src/components/consumer/games/GamesViewAction.tsx`, backend/API/создание игры. ## STYLE_GUIDE_PARITY Прочитаны approved local kit `src/App.tsx` Menus & overlays, `src/index.css` tokens/overlay families, source `docs/product/SITEWIDE_UI_STYLE_GUIDE.md` и `docs/product/DESKTOP_OVERLAY_STYLE_GUIDE.md`. Reuse: Onest, canonical palette/radii, chips/select/button, filters modal / mobile bottom sheet, Spacious40 chrome, single-scroll body. OWNER_VISUAL_OVERRIDE: только явное удаление default LevelGuide, указанного feed eyebrow и визуального «По времени». STYLE_GUIDE_GAP: нет; собраны существующие primitives. Feed photo CSS и trainer/organizer band берутся из accepted68 snapshot, приоритет прямого текущего scope над более ранней формулировкой max-square в sitewide guide. Карточки не являются redesign target. ## Прототип и проверки Синтетические данные только здесь: четыре игры, без реальных имён людей. Роли «Организатор А», «Тренер Б»; фото — существующий asset, не заявляется фотографией конкретной площадки. Card DOM иллюстративно воспроизводит текущие regular/trainer layouts: desktop square media, mobile 210px, отдельная trainer people band. Production card component не менять. Навигация shell иллюстративна. City button показывает пояснение существующего `/location`; реального выбора города/API нет. Переход карты — только stub для проверки текстов level/trainer honesty и возврата, не новый дизайн карты. Create/details/share вне scope открывают честное пояснение. Refresh только перерисовывает fixtures. Фактически выполнено: SHA-256 frozen inputs, JS syntax check через локальный Node, комплектность относительных assets/JSON. Bare `node` отсутствовал в PATH; повтор через разрешённый абсолютный Node path успешен. **Исторический статус на завершение авторского вызова — browser/keyboard/independent QA ещё не выполнялись.** Актуальные внешние результаты приведены ниже. Outer acceptance checks, пропорционально delta: - Восемь кадров из capture plan: 390/1280 × CLOSED/OPEN/ACTIVE/HELP. Осмотреть отсутствие overflow/обрезания, позицию icon, нет дубликатов, cards unchanged. Верхний контекст страницы доступен прокруткой вверх. - Keyboard: trigger Enter → select; Tab/Shift+Tab остаются в modal; Escape / X / backdrop → trigger; выбор 3,0 сохраняется при всех закрытиях. Проскроллить HELP до 7,0, выбрать, затем закрыть. - RESET из ACTIVE при уже выбранных day/type/trainer сохраняет их; empty-state reset также восстанавливает фокус. - Scenario EMPTY: нет ложного all-city empty/total; «Показать ещё» остаётся и добавляет compatible 7,0 fixture. Other-scale fixture с 3.0 не входит в level 3,0. В production reuse существующих helper tests, не переписывать логику. - TRAINER → map → list: city/day/activity context остаётся, карта не обещает применить level/trainer; filters сохраняются. Production pagination/stale/error gates и тесты неизменённого кода переиспользовать. Это завершённый design artifact для следующего автора; не DEV release, не новая приёмка #458. ## Внешняя проверка артефакта 09.11.2026, оператор выполнил 14 проверок геометрии и 10 интерактивных проверок на 390/1280. Ошибок JavaScript, горизонтального переполнения, обрезания dialog и повторных filter triggers не обнаружено. Восемь PNG сохранены в screenshots/. Реально проверены Enter/open, Escape/focus return, backdrop dismissal, выбор/сброс уровня с сохранением даты/типа, прокрутка справки до 7,0 и level-empty. [Результат](operator-browser-result.json). Отдельная независимая проверка описана в [independent-review.md](independent-review.md). Runtime прототипа после авторского terminal не менялся. Проверка DEV и пользовательская приёмка этим отчётом не заявляются.