Доточить позиционирование UI

This commit is contained in:
2026-08-22 16:35:41 +03:00
parent ea19e511c0
commit 63b66c48d8
6 changed files with 427 additions and 13 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
<link rel="apple-touch-icon" href="./img/logo.jpg" />
<title>СИЯНИЕ</title>
<script>
window.__SHINE_BUILD_HASH__ = '20260822132200';
window.__SHINE_BUILD_HASH__ = '20260822140000';
window.__SHINE_CLIENT_VERSION__ = '1.2.10';
</script>
<script>
+1 -1
View File
@@ -91,7 +91,7 @@ import * as channelThreadView from './pages/channel-thread-view.js';
import * as addChannelView from './pages/add-channel-view.js';
import * as addPersonalPublicChatView from './pages/add-personal-public-chat-view.js';
import * as networkView from './pages/network-view.js?v=202608221226';
import * as notificationsView from './pages/notifications-view.js?v=202608221226';
import * as notificationsView from './pages/notifications-view.js?v=202608221354';
const CONVERSATION_CLEAR_NOTICE_TEXT = 'История переписки очищена с этого места';
const DM_UNSUPPORTED_FORMAT_TEXT = 'Формат сообщения не поддерживается';
+1 -9
View File
@@ -97,14 +97,10 @@ function compareChatRows(a, b) {
export function render({ navigate, chrome }) {
const screen = document.createElement('section');
screen.className = 'stack dm-screen dm-list-screen';
const login = String(state.session.login || '').trim();
const head = document.createElement('header');
head.className = 'dm-head';
head.innerHTML = `
<div class="dm-head-brand">
<div class="dm-head-avatar-slot"></div>
</div>
<div class="dm-head-brand" aria-hidden="true"></div>
<h1 class="dm-head-title">Контакты</h1>
<div class="dm-head-menu-wrap">
<button type="button" class="dm-head-menu-btn" aria-label="Меню контактов" aria-haspopup="menu" aria-expanded="false">
@@ -120,10 +116,6 @@ export function render({ navigate, chrome }) {
</div>
</div>
`;
const headAvatarSlot = head.querySelector('.dm-head-avatar-slot');
if (headAvatarSlot) {
headAvatarSlot.append(createDmAvatar(login, { className: 'dm-head-avatar' }));
}
const menuButton = head.querySelector('.dm-head-menu-btn');
menuButton?.append(createOverflowDots());
+3
View File
@@ -499,6 +499,9 @@ export function render({ navigate, route, chrome } = {}) {
{ label: 'Найти', onClick: openSearchModal },
],
});
// «Связи» используют тот же общий topbar, что и остальные страницы.
// Отдельный класс нужен только для page-specific fade графа, не для геометрии header.
header.classList.add('network-topbar');
// Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
screen.cleanup = () => {
+1 -1
View File
@@ -134,7 +134,7 @@ async function enrichItem(item, activeTab) {
function renderEmpty(activeTab) {
const card = document.createElement('article');
card.className = 'card stack';
card.className = 'card stack notification-empty-state';
const title = document.createElement('strong');
title.textContent = activeTab === 'events' ? 'Пока нет событий' : 'Пока нет ответов';
const text = document.createElement('p');
+420 -1
View File
@@ -9075,7 +9075,7 @@ body.chat-topbar-overlay .app-shell.keyboard-open .scroll-to-bottom-btn {
/* ===== DM + Channels polish (2026-08-22 13:22) ===== */
/* «Личные»: аватар основной вкладки стоит ровно на той же горизонтальной позиции,
* что и аватар собеседника в открытом чате: после 40px back-slot + 8px gap. */
.topbar-slot .dm-list-screen .dm-head-brand {
.topbar-slot .dm-head .dm-head-brand {
margin-left: 48px;
}
@@ -9132,3 +9132,422 @@ body.chat-topbar-overlay .app-shell.keyboard-open .scroll-to-bottom-btn {
0 0 5px var(--app-topbar-blue-glow),
0 0 12px var(--app-topbar-blue-glow-soft);
}
/* ===== DM avatar + channel row visual alignment fix (2026-08-22 13:29) ===== */
/* Шапка «Личных» переносится в topbar-slot отдельно от .dm-list-screen,
* поэтому позиционируем аватар по реальной DOM-структуре. 48px = back 40px + gap 8px
* в открытом чате, что даёт одинаковую абсолютную X-позицию. */
.topbar-slot .dm-head .dm-head-brand {
margin-left: 48px;
}
/* Троеточие внутри строки канала использует тот же чистый overflow-стиль:
* без рамки/подложки, белые точки с мягким голубым свечением. */
.channels-screen--list .channel-menu-trigger {
width: 34px;
height: 34px;
min-width: 34px;
min-height: 34px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
color: var(--app-topbar-gold);
text-shadow: none;
}
.channels-screen--list .channel-menu-trigger .app-overflow-dots i {
background: var(--app-topbar-gold);
box-shadow:
0 0 5px var(--app-topbar-blue-glow),
0 0 10px var(--app-topbar-blue-glow-soft);
}
/* Название канала и техническая строка (login / channel) — одна типографическая
* система с основным интерфейсом: белый текст + голубое свечение. */
.channels-screen--list .channel-row-title,
.channels-screen--list .channel-row-technical {
font-family: var(--font-main);
color: var(--app-topbar-gold);
text-shadow:
0 0 5px var(--app-topbar-blue-glow),
0 0 12px var(--app-topbar-blue-glow-soft);
}
.channels-screen--list .channel-row-title {
font-weight: 700;
}
/* ===== Channels: final overflow + unread polish (2026-08-22 13:34) ===== */
/* Внутреннее троеточие карточки канала должно выглядеть ровно как остальные
* overflow-кнопки интерфейса. Более высокая специфичность намеренно перекрывает
* старые локальные стили .channel-menu-trigger. */
.channels-screen--list .channel-row .channel-row-controls > button.channel-menu-trigger {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
min-height: 40px !important;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
border-radius: 0 !important;
outline: 0 !important;
background: transparent !important;
box-shadow: none !important;
color: #F7FBFF !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
font: inherit !important;
line-height: 1 !important;
appearance: none !important;
-webkit-appearance: none !important;
}
.channels-screen--list .channel-row .channel-row-controls > button.channel-menu-trigger:hover,
.channels-screen--list .channel-row .channel-row-controls > button.channel-menu-trigger:focus,
.channels-screen--list .channel-row .channel-row-controls > button.channel-menu-trigger:focus-visible,
.channels-screen--list .channel-row .channel-row-controls > button.channel-menu-trigger:active {
border: 0 !important;
outline: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
.channels-screen--list .channel-row .channel-menu-trigger > .app-overflow-dots {
width: 8px !important;
height: 24px !important;
margin: 0 !important;
color: #F7FBFF !important;
filter:
drop-shadow(0 0 3px rgba(92, 190, 255, 0.72))
drop-shadow(0 0 7px rgba(72, 145, 255, 0.34));
}
.channels-screen--list .channel-row .channel-menu-trigger > .app-overflow-dots > i {
width: 4px !important;
height: 4px !important;
flex-basis: 4px !important;
border: 0 !important;
background: #F7FBFF !important;
box-shadow: none !important;
}
/* Счётчик непрочитанных: без видимой границы. Сам овал — светящийся синий объём,
* цифра остаётся обычной белой. */
.channels-screen--list .channel-row-count,
.channels-screen--list .unread.channel-row-count {
border: 0 !important;
outline: 0 !important;
background: rgba(74, 169, 255, 0.36) !important;
color: #FFFFFF !important;
box-shadow:
0 0 8px rgba(92, 190, 255, 0.82),
0 0 18px rgba(72, 145, 255, 0.54),
0 0 30px rgba(72, 145, 255, 0.24) !important;
text-shadow: none !important;
}
/* ===== Transparent topbar + scroll fade (2026-08-22 13:40) =====
* Верхняя шапка не рисует отдельную плашку. Контент прокручивается под ней и
* постепенно исчезает по alpha-mask: у самого верхнего края полностью прозрачен,
* к нижней кромке topbar — полностью видим. */
:root {
--app-topbar-fade-end: var(--topbar-height, 64px);
}
.topbar-slot {
z-index: 20;
background: transparent !important;
isolation: isolate;
}
/* Лёгкое затемнение только для читаемости текста шапки, без видимой границы. */
.topbar-slot::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
background: linear-gradient(
180deg,
rgba(5, 7, 10, 0.32) 0%,
rgba(5, 7, 10, 0.16) 52%,
rgba(5, 7, 10, 0) 100%
);
}
.topbar-slot .page-header,
.topbar-slot .dm-head,
.topbar-slot .channels-top-bar {
background: transparent !important;
border: 0 !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
/* Обычные прокручиваемые экраны начинаются физически от верхнего края app-shell.
* В исходном положении padding держит первый элемент под шапкой; при прокрутке
* этот padding уезжает, и контент проходит под прозрачным topbar. */
.screen-content:not(.no-app-chrome):not(.network-scroll-lock):not(:has(> .dm-chat-screen)) {
top: var(--call-minimized-bar-height, 0px);
padding-top: calc(var(--topbar-height, 64px) + 14px);
-webkit-mask-image: linear-gradient(
to bottom,
transparent 0,
rgba(0, 0, 0, 0.12) 12px,
rgba(0, 0, 0, 0.48) calc(var(--app-topbar-fade-end) - 24px),
rgba(0, 0, 0, 0.82) calc(var(--app-topbar-fade-end) - 8px),
#000 var(--app-topbar-fade-end),
#000 100%
);
mask-image: linear-gradient(
to bottom,
transparent 0,
rgba(0, 0, 0, 0.12) 12px,
rgba(0, 0, 0, 0.48) calc(var(--app-topbar-fade-end) - 24px),
rgba(0, 0, 0, 0.82) calc(var(--app-topbar-fade-end) - 8px),
#000 var(--app-topbar-fade-end),
#000 100%
);
}
/* DM-чат скроллится не через .screen-content, а через .dm-chat-wrap.
* Расширяем его до верхнего края и переносим безопасный отступ внутрь самого
* scroll-контейнера — так сообщения реально заходят под шапку и затухают. */
body.chat-topbar-overlay .screen-content:has(> .dm-chat-screen) {
top: var(--call-minimized-bar-height, 0px) !important;
padding-top: 0 !important;
}
body.chat-topbar-overlay .dm-chat-screen > .dm-chat-wrap {
box-sizing: border-box;
padding-top: calc(var(--topbar-height, 64px) + 12px);
-webkit-mask-image: linear-gradient(
to bottom,
transparent 0,
rgba(0, 0, 0, 0.12) 12px,
rgba(0, 0, 0, 0.48) calc(var(--app-topbar-fade-end) - 24px),
rgba(0, 0, 0, 0.82) calc(var(--app-topbar-fade-end) - 8px),
#000 var(--app-topbar-fade-end),
#000 100%
);
mask-image: linear-gradient(
to bottom,
transparent 0,
rgba(0, 0, 0, 0.12) 12px,
rgba(0, 0, 0, 0.48) calc(var(--app-topbar-fade-end) - 24px),
rgba(0, 0, 0, 0.82) calc(var(--app-topbar-fade-end) - 8px),
#000 var(--app-topbar-fade-end),
#000 100%
);
}
/* «Контакты»: верхняя аватарка убрана полностью. Пустая левая колонка сохраняет
* строгий центр заголовка относительно правого overflow-меню. */
.topbar-slot .dm-head .dm-head-brand {
margin-left: 0 !important;
min-width: 40px;
width: 40px;
height: 40px;
}
/* ===== Robust topbar fade overlay (2026-08-22 13:44) =====
* Mask-image on scrolling containers is not consistently composited by every
* Chromium/WebView path used by the app. The fade is therefore rendered as a
* dedicated overlay layer above scrolled content and below topbar controls.
* Content remains physically scrollable under the header and visually dissolves
* into the app background before reaching the very top edge. */
.topbar-slot {
overflow: visible !important;
isolation: isolate;
}
.topbar-slot::before {
content: '';
position: absolute;
top: 0;
left: -12px;
right: -12px;
height: calc(100% + 48px);
z-index: 0;
pointer-events: none;
background: linear-gradient(
to bottom,
rgba(5, 7, 10, 1) 0%,
rgba(5, 7, 10, 0.98) 16%,
rgba(5, 7, 10, 0.88) 32%,
rgba(5, 7, 10, 0.66) 52%,
rgba(5, 7, 10, 0.38) 70%,
rgba(5, 7, 10, 0.15) 84%,
rgba(5, 7, 10, 0) 100%
);
}
.topbar-slot > * {
position: relative;
z-index: 1;
}
/* The overlay above is the source of truth for the fade. Disable the previous
* alpha masks so browser-specific mask compositing cannot cancel the effect. */
.screen-content:not(.no-app-chrome):not(.network-scroll-lock):not(:has(> .dm-chat-screen)),
body.chat-topbar-overlay .dm-chat-screen > .dm-chat-wrap {
-webkit-mask-image: none !important;
mask-image: none !important;
}
/* ===== Bottom chat fade + Connections topbar fade + Notifications borderless cards (2026-08-22 13:49) ===== */
/* Личный чат: область сообщений продолжается под composer. Внутренний нижний
* padding сохраняет последнее сообщение над полем ввода в конечной позиции,
* но во время прокрутки сообщения могут проходить под нижним fade-слоем. */
body.chat-topbar-overlay .app-shell:not(.keyboard-open) .screen-content:has(> .dm-chat-screen) {
bottom: calc(var(--toolbar-height, 78px) + env(safe-area-inset-bottom)) !important;
}
body.chat-topbar-overlay .app-shell.keyboard-open .screen-content:has(> .dm-chat-screen) {
bottom: var(--keyboard-offset, 0px) !important;
}
body.chat-topbar-overlay .dm-chat-screen > .dm-chat-wrap {
padding-bottom: calc(var(--composer-height, 0px) + 52px);
}
/* Нижнее затухание — зеркальная версия верхнего эффекта. Слой находится над
* сообщениями и под самим composer, поэтому текст плавно исчезает перед полем ввода. */
body.chat-topbar-overlay .composer-slot {
isolation: isolate;
overflow: visible !important;
}
body.chat-topbar-overlay .composer-slot::before {
content: '';
position: absolute;
left: -12px;
right: -12px;
top: -52px;
bottom: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(
to bottom,
rgba(5, 7, 10, 0) 0%,
rgba(5, 7, 10, 0.14) 18%,
rgba(5, 7, 10, 0.38) 38%,
rgba(5, 7, 10, 0.66) 58%,
rgba(5, 7, 10, 0.88) 76%,
rgba(5, 7, 10, 0.98) 90%,
rgba(5, 7, 10, 1) 100%
);
}
body.chat-topbar-overlay .composer-slot > * {
position: relative;
z-index: 1;
}
/* «Связи»: граф теперь физически продолжается под общей верхней шапкой.
* Глобальный .topbar-slot::before даёт тот же fade, что и на лентах. */
.screen-content.network-scroll-lock {
top: var(--call-minimized-bar-height, 0px) !important;
}
/* Верхние фильтры графа остаются ниже шапки и не попадают в зону затухания. */
.network-stage > .fg-filter-bar.app-top-tabs {
top: calc(var(--topbar-height, 64px) + var(--app-primary-tabs-top-gap, 14px)) !important;
}
/* Уведомления: информационные блоки без рамок. Hover/focus не возвращает
* обводку — различение сохраняется фоном и лёгкой реакцией на нажатие. */
.notifications-screen .notifications-list > .notification-card,
.notifications-screen .notifications-list > .notification-card:hover,
.notifications-screen .notifications-list > .notification-card:focus,
.notifications-screen .notifications-list > .notification-card:focus-visible,
.notifications-screen .notifications-list > .notification-card:active {
border: 0 !important;
outline: 0 !important;
}
/* ===== Connections: restore canonical shared topbar (2026-08-22 14:00) =====
* The toolbar itself is the standard renderHeader() mounted in #topbar-slot.
* Network-specific positioning must never move .screen-content under it.
* The graph fade lives inside .network-stage instead, so canvas layout cannot
* change toolbar height/position or overlap its controls. */
.topbar-slot:has(> .network-topbar)::before {
/* Keep the generic toolbar background/fade inside the toolbar bounds only. */
height: 100% !important;
}
/* Undo the previous Connections-only full-bleed-under-toolbar geometry. */
.screen-content.network-scroll-lock {
top: calc(var(--call-minimized-bar-height, 0px) + var(--topbar-height, 0px)) !important;
left: 0;
right: 0;
bottom: calc(var(--toolbar-height, 78px) + var(--composer-height, 0px));
padding: 0 !important;
}
.screen-content.network-scroll-lock .network-screen,
.screen-content.network-scroll-lock .network-stage {
width: 100%;
height: 100%;
min-height: 0;
margin: 0;
}
/* Filters return to the same content offset they had before the toolbar-overlay experiment. */
.network-stage > .fg-filter-bar.app-top-tabs {
top: var(--app-primary-tabs-top-gap, 14px) !important;
}
/* Fade belongs to the graph surface, not to the toolbar. It sits above graph
* nodes/edges but below filter chips (filter bar z-index is 11). */
.network-stage::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 58px;
z-index: 10;
pointer-events: none;
background: linear-gradient(
to bottom,
rgba(5, 7, 10, 1) 0%,
rgba(5, 7, 10, 0.86) 28%,
rgba(5, 7, 10, 0.48) 58%,
rgba(5, 7, 10, 0.16) 82%,
rgba(5, 7, 10, 0) 100%
);
}
/* Explicitly keep the Connections header on the canonical topbar contract.
* No network-only absolute positioning, dimensions or padding. */
.topbar-slot > .page-header.app-topbar-shell.network-topbar {
position: relative !important;
inset: auto !important;
width: 100% !important;
height: calc(var(--app-topbar-base-height) + env(safe-area-inset-top)) !important;
min-height: calc(var(--app-topbar-base-height) + env(safe-area-inset-top)) !important;
max-height: calc(var(--app-topbar-base-height) + env(safe-area-inset-top)) !important;
margin: 0 !important;
padding: env(safe-area-inset-top) 0 0 !important;
display: grid !important;
grid-template-columns: minmax(44px, 1fr) minmax(0, auto) minmax(44px, 1fr) !important;
align-items: center !important;
gap: 8px !important;
border: 0 !important;
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}