From 5e4e4835f296859b76f4ddcea97e27ffe75fa5da Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 7 Sep 2026 15:24:41 +0300 Subject: [PATCH 1/5] =?UTF-8?q?UI:=20=D1=88=D0=B0=D0=BF=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=B4=D0=B2=D0=B0=D0=BB=20=D0=B2=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=BE=D0=BA=D1=80=D1=83=D1=82=D0=BA=D0=B8?= =?UTF-8?q?=20=E2=80=94=20=D0=BA=D0=B0=D1=80=D0=BA=D0=B0=D1=81=20=D0=BD?= =?UTF-8?q?=D0=B0=20100dvh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Подвал был position: fixed, шапка — position: sticky при html/body с height: 100%. В мобильных браузерах со скрывающейся адресной строкой layout viewport меняет высоту при прокрутке: fixed-подвал за ним дёргается, а у sticky-шапки нет однозначного прокручиваемого предка. Каркас переведён на 100dvh с единственной прокручиваемой областью (.app-main) — шапка и подвал стали обычными элементами флекс-колонки и дёргаться им больше нечем. Safe-area ограничена потолком 24px: при viewport-fit=cover часть браузеров (DuckDuckGo, Firefox Focus) отдаёт inset поверх уже смещённого вьюпорта, из-за чего шапка раздувалась вдвое, а надпись и колокольчик съезжали вниз. Заодно шапка защищена от распирания длинным названием на узких экранах. #4 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk --- frontend/src/styles/global.css | 19 +++++++++++++++---- frontend/src/styles/layout.css | 28 ++++++++++++++++------------ frontend/src/styles/tokens.css | 7 +++++-- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 432081c..16d510f 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -11,6 +11,11 @@ html, body, #root { margin: 0; } +/* Высота каркаса — по видимой области (dvh): на мобильных 100% берётся от layout + viewport, который меняется при сворачивании адресной строки. Правило выше остаётся + фоллбэком для движков без dvh. */ +#root { height: 100dvh; } + body { background: var(--bg); color: var(--text); @@ -18,6 +23,7 @@ body { Arial, sans-serif; -webkit-font-smoothing: antialiased; overscroll-behavior-y: none; + overflow: hidden; /* прокручивается только .app-main */ } a { color: var(--accent-2); text-decoration: none; } @@ -190,7 +196,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } .toast { position: fixed; left: 50%; - bottom: calc(var(--bottombar-height) + 80px); + bottom: calc(var(--bottombar-height) + var(--safe-bottom) + 16px); transform: translateX(-50%); background: var(--surface-2); border: 1px solid var(--border); @@ -206,7 +212,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } /* ─── Уведомления ─────────────────────────────────────────────────────────── */ /* Зоны верхней панели: слева back/заголовок, по центру название, справа колокольчик */ -.top-bar-left { display: flex; align-items: center; min-width: 0; } +.top-bar-left { display: flex; align-items: center; min-width: 0; flex: 0 1 auto; } .top-bar-center { flex: 1; text-align: center; @@ -215,7 +221,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } white-space: nowrap; padding: 0 8px; } -.top-bar-right { display: flex; align-items: center; } +.top-bar-right { display: flex; align-items: center; flex: none; } .top-bar-home { background: none; border: none; @@ -223,6 +229,11 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } color: var(--text); font-weight: 700; font-size: 17px; + /* На узких экранах длинное название иначе распирает шапку по горизонтали. */ + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .notif-bell-wrap { position: relative; } @@ -258,7 +269,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } top: calc(100% + 8px); right: 0; width: min(320px, 86vw); - max-height: 70vh; + max-height: 70dvh; overflow-y: auto; background: var(--surface); border: 1px solid var(--border); diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index f0597ae..49e7137 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -2,17 +2,16 @@ position: relative; max-width: var(--app-max-w); margin: 0 auto; - min-height: 100%; + height: 100%; display: flex; flex-direction: column; background: var(--bg); box-shadow: 0 0 40px rgba(0, 0, 0, 0.5); } +/* Шапка вне прокручиваемой области по построению — sticky ей больше не нужен. */ .top-bar { - position: sticky; - top: 0; - z-index: 10; + flex: none; display: flex; align-items: center; justify-content: space-between; @@ -22,20 +21,24 @@ } .top-bar .title { font-weight: 700; font-size: 17px; } +/* Единственная прокручиваемая область приложения. */ .app-main { flex: 1; + min-height: 0; /* без этого flex-элемент не сжимается и прокрутка уходит в body */ overflow-y: auto; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; padding: 14px; - padding-bottom: calc(var(--bottombar-height) + var(--fab-height) + var(--safe-bottom) + 28px); + /* Подвал теперь в потоке — компенсировать нужно только выступающий купол FAB. */ + padding-bottom: 24px; } +/* Панель — обычный элемент флекс-колонки, не fixed: в мобильных браузерах со + скрывающейся адресной строкой fixed-подвал дёргается при прокрутке. + position: relative обязателен — на нём держится absolute-позиционирование .fab. */ .bottom-bar { - position: fixed; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 100%; - max-width: var(--app-max-w); + position: relative; + flex: none; height: calc(var(--bottombar-height) + var(--safe-bottom)); padding-bottom: var(--safe-bottom); background: var(--surface); @@ -131,6 +134,7 @@ /* Вкладки админки: горизонтальная карусель (не ломают вёрстку при переполнении) */ .admin-tabs { + flex: none; display: flex; gap: 8px; padding: 10px 14px; @@ -218,7 +222,7 @@ position: fixed; top: 0; right: 0; - height: 100%; + height: 100dvh; width: min(300px, 84vw); background: var(--surface); border-left: 1px solid var(--border); diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 44a25ae..586dee3 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -20,8 +20,11 @@ --fab-height: 62.5px; --radius: 14px; --radius-sm: 10px; - --safe-bottom: env(safe-area-inset-bottom, 0px); - --safe-top: env(safe-area-inset-top, 0px); + /* Safe-area с потолком: часть мобильных браузеров (DuckDuckGo, Firefox Focus) при + viewport-fit=cover отдаёт inset поверх уже смещённого вьюпорта — без ограничения + шапка раздувается вдвое, а её содержимое съезжает вниз. */ + --safe-bottom: min(env(safe-area-inset-bottom, 0px), 24px); + --safe-top: min(env(safe-area-inset-top, 0px), 24px); /* Брейкпоинты (для справки) */ --bp-lg: 768px; -- 2.54.0 From 81ad2a04031620c54b9ce7537a8ca3e5776c50b7 Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 7 Sep 2026 15:35:09 +0300 Subject: [PATCH 2/5] =?UTF-8?q?UI:=20=D0=BF=D0=BE=D0=B4=D0=B2=D0=B0=D0=BB?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D1=83=D0=BF=D0=BE=D0=BB=20FAB=20=D0=B2=D0=BF?= =?UTF-8?q?=D0=BB=D0=BE=D1=82=D0=BD=D1=83=D1=8E=20=D0=BA=20=D0=BD=D0=B8?= =?UTF-8?q?=D0=B6=D0=BD=D0=B5=D0=BC=D1=83=20=D0=BA=D1=80=D0=B0=D1=8E=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Подвал держал внутренний отступ в safe-area снизу, а купол FAB был дополнительно поднят на 8px — у нижней кромки оставалась пустая полоса. Отступ снят: высота панели равна --bottombar-height, база купола на нулевой отметке. Токен --safe-bottom больше нигде не используется и удалён, отступ сверху (--safe-top) остаётся у шапки. #4 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk --- frontend/src/styles/global.css | 2 +- frontend/src/styles/layout.css | 5 ++--- frontend/src/styles/tokens.css | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 16d510f..b329cbf 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -196,7 +196,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } .toast { position: fixed; left: 50%; - bottom: calc(var(--bottombar-height) + var(--safe-bottom) + 16px); + bottom: calc(var(--bottombar-height) + 16px); transform: translateX(-50%); background: var(--surface-2); border: 1px solid var(--border); diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index 49e7137..995e49b 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -39,8 +39,7 @@ .bottom-bar { position: relative; flex: none; - height: calc(var(--bottombar-height) + var(--safe-bottom)); - padding-bottom: var(--safe-bottom); + height: var(--bottombar-height); background: var(--surface); border-top: 1px solid var(--border); display: flex; @@ -70,7 +69,7 @@ position: absolute; left: 50%; transform: translateX(-50%); - bottom: calc(var(--safe-bottom) + 8px); + bottom: 0; width: var(--fab-width); height: var(--fab-height); border: 1px solid var(--accent); diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 586dee3..00a64c8 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -22,8 +22,8 @@ --radius-sm: 10px; /* Safe-area с потолком: часть мобильных браузеров (DuckDuckGo, Firefox Focus) при viewport-fit=cover отдаёт inset поверх уже смещённого вьюпорта — без ограничения - шапка раздувается вдвое, а её содержимое съезжает вниз. */ - --safe-bottom: min(env(safe-area-inset-bottom, 0px), 24px); + шапка раздувается вдвое, а её содержимое съезжает вниз. Снизу отступ не нужен: + подвал и купол FAB прижаты вплотную к нижнему краю экрана. */ --safe-top: min(env(safe-area-inset-top, 0px), 24px); /* Брейкпоинты (для справки) */ -- 2.54.0 From 2a4c5d9d2575bbd0e43f705d1ee5ea62b6ac178f Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 7 Sep 2026 15:40:51 +0300 Subject: [PATCH 3/5] =?UTF-8?q?UI:=20=D1=88=D0=B0=D0=BF=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=BF=D0=BB=D0=BE=D1=82=D0=BD=D1=83=D1=8E=20=D0=BA=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=85=D0=BD=D0=B5=D0=BC=D1=83=20=D0=BA=D1=80?= =?UTF-8?q?=D0=B0=D1=8E=20=E2=80=94=20=D1=81=D0=BD=D1=8F=D1=82=20viewport-?= =?UTF-8?q?fit=3Dcover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В DuckDuckGo шапка держала отступ от адресной строки — тот же safe-area, что раньше отодвигал подвал снизу. Insets браузер отдаёт именно из-за viewport-fit=cover: он передаёт управление безопасной зоной странице. После отказа от отступов сверху и снизу компенсировать нечего, поэтому cover снят — вьюпорт снова размещает браузер, а env() везде даёт ноль. Следом убран --safe-top и три его использования (.top-bar, .side-menu, .toast-top-wrap): без cover переменная всегда нулевая. Токенов safe-area в проекте больше нет. #4 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk --- frontend/index.html | 2 +- frontend/src/styles/global.css | 2 +- frontend/src/styles/layout.css | 3 +-- frontend/src/styles/tokens.css | 5 ----- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 874a03a..3748545 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ Forbidden Stars — учёт партий diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index b329cbf..e5cd70b 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -448,7 +448,7 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); } /* Всплывашки сверху экрана в момент прихода */ .toast-top-wrap { position: fixed; - top: calc(var(--safe-top) + 10px); + top: 10px; left: 50%; transform: translateX(-50%); width: min(var(--app-max-w), 94vw); diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index 995e49b..f2c98e9 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -15,7 +15,7 @@ display: flex; align-items: center; justify-content: space-between; - padding: calc(var(--safe-top) + 12px) 16px 12px; + padding: 12px 16px; background: var(--surface); border-bottom: 1px solid var(--border); } @@ -231,7 +231,6 @@ z-index: 61; display: flex; flex-direction: column; - padding-top: var(--safe-top); } .side-menu.open { transform: translateX(0); } diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 00a64c8..ae0ef8b 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -20,11 +20,6 @@ --fab-height: 62.5px; --radius: 14px; --radius-sm: 10px; - /* Safe-area с потолком: часть мобильных браузеров (DuckDuckGo, Firefox Focus) при - viewport-fit=cover отдаёт inset поверх уже смещённого вьюпорта — без ограничения - шапка раздувается вдвое, а её содержимое съезжает вниз. Снизу отступ не нужен: - подвал и купол FAB прижаты вплотную к нижнему краю экрана. */ - --safe-top: min(env(safe-area-inset-top, 0px), 24px); /* Брейкпоинты (для справки) */ --bp-lg: 768px; -- 2.54.0 From 09be0f57c6054316c976daf404c207e73d27341a Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 7 Sep 2026 15:41:30 +0300 Subject: [PATCH 4/5] =?UTF-8?q?UI:=20=D0=BA=D1=83=D0=BF=D0=BE=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=B0=D1=80=D1=82=D0=B8=D0=B8?= =?UTF-8?q?=20=E2=80=94=20140x70?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пропорция полукруга сохранена (ширина = 2x высоты), над кромкой панели купол снова выступает — на 6px при высоте подвала 64px. Прежние 125x62.5 после прижатия базы к нижнему краю утапливали купол в панель. #4 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk --- frontend/src/styles/tokens.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index ae0ef8b..71d0bdc 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -16,8 +16,8 @@ /* Размеры */ --app-max-w: 480px; --bottombar-height: 64px; - --fab-width: 125px; - --fab-height: 62.5px; + --fab-width: 140px; + --fab-height: 70px; --radius: 14px; --radius-sm: 10px; -- 2.54.0 From 534ba640cca4fb87f5d848224effe63072aca3e9 Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 7 Sep 2026 15:51:19 +0300 Subject: [PATCH 5/5] =?UTF-8?q?UI:=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B9=20=D1=83=20=D0=BA=D1=83=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=B0=20FAB=20=E2=80=94=20=D0=BF=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D1=83=D1=8E=20=D0=B3=D0=B5=D0=BE=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit База купола теперь на нижнем крае экрана, а не смещена относительно кромки панели, и размеры стали 140x70. Комментарий описывал прежнее положение. #4 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ps52xzuUXnWrJ5SRnEeaLk --- frontend/src/styles/layout.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index f2c98e9..6854a04 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -63,8 +63,9 @@ .bottom-bar .slot .name { color: var(--text); font-weight: 600; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .bottom-bar .spacer { width: var(--fab-width); flex: 0 0 var(--fab-width); } -/* Круг создания партии: полукруг-купол, по центру разделяет слоты, смещён вниз - (база у нижней кромки панели) и поднят на передний план (z-index выше панели). */ +/* Круг создания партии: полукруг-купол, по центру разделяет слоты, базой стоит на + нижнем крае экрана и поднят на передний план (z-index выше панели). Ширина вдвое + больше высоты (140x70) — над кромкой панели купол выступает на 6px. */ .fab { position: absolute; left: 50%; -- 2.54.0