From 3e4583bbe9c97d0889ba4b75f847312f6f320796b75c539101e8e5ff8db24e10 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sat, 29 Aug 2026 15:56:01 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20UI=20=D0=B4=D0=BB=D1=8F=20=D0=BC=D0=B5=D0=BD=D1=8E=20?= =?UTF-8?q?=D0=B8=20=D1=8F=D0=B7=D1=8B=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 2 +- shine-UI/assets/profile-icon-profile.svg | 4 +- shine-UI/assets/profile-icon-settings.svg | 6 +- shine-UI/assets/profile-icon-wallet.svg | 7 +- shine-UI/index.html | 23 ++- shine-UI/js/components/dropdown-menu.js | 11 +- shine-UI/js/components/toolbar.js | 2 +- shine-UI/js/pages/channels-list.js | 12 +- shine-UI/js/pages/entry-settings-view.js | 10 +- shine-UI/js/pages/language-view.js | 48 ++++-- shine-UI/js/pages/messages-list.js | 11 +- shine-UI/styles/components.css | 199 ++++++++++++++++++++-- 12 files changed, 282 insertions(+), 53 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index fada4d9b..9e157774 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.7.5 +client.version=1.7.6 server.version=1.6.2 diff --git a/shine-UI/assets/profile-icon-profile.svg b/shine-UI/assets/profile-icon-profile.svg index 5d7244e4..f507bc3a 100644 --- a/shine-UI/assets/profile-icon-profile.svg +++ b/shine-UI/assets/profile-icon-profile.svg @@ -1,4 +1,4 @@ - - + + diff --git a/shine-UI/assets/profile-icon-settings.svg b/shine-UI/assets/profile-icon-settings.svg index 160fcc17..2419d0b9 100644 --- a/shine-UI/assets/profile-icon-settings.svg +++ b/shine-UI/assets/profile-icon-settings.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/shine-UI/assets/profile-icon-wallet.svg b/shine-UI/assets/profile-icon-wallet.svg index 03cccb21..fc9b38a1 100644 --- a/shine-UI/assets/profile-icon-wallet.svg +++ b/shine-UI/assets/profile-icon-wallet.svg @@ -1,6 +1,5 @@ - - - - + + + diff --git a/shine-UI/index.html b/shine-UI/index.html index b785dcf4..ed3ca249 100644 --- a/shine-UI/index.html +++ b/shine-UI/index.html @@ -229,9 +229,17 @@ window.__SHINE_BUILD_HASH__ = '20260826145335'; document.getElementById(menuId)?.remove(); } catch {} }; + const dismissError = () => { + removeMenu(); + try { + document.getElementById('boot-error-overlay')?.remove(); + setState(null); + } catch {} + }; const openMenu = () => { try { removeMenu(); + document.dispatchEvent(new CustomEvent('shine:dropdown-open', { detail: { owner: 'boot-error' } })); const report = buildReport(); const root = document.getElementById('modal-root') || document.body; const shell = document.createElement('div'); @@ -246,7 +254,7 @@ window.__SHINE_BUILD_HASH__ = '20260826145335'; - + `; @@ -308,7 +316,18 @@ window.__SHINE_BUILD_HASH__ = '20260826145335'; close(); }); - shell.querySelector('[data-action="close"]')?.addEventListener('click', close); + shell.querySelector('[data-action="dismiss"]')?.addEventListener('click', () => { + close(); + try { + document.getElementById('boot-error-overlay')?.remove(); + setState(null); + } catch {} + }); + const onPeerDropdownOpen = (event) => { + if (event?.detail?.owner === 'boot-error') return; + close(); + }; + document.addEventListener('shine:dropdown-open', onPeerDropdownOpen, { once: true }); dialog?.focus?.(); window.addEventListener('keydown', onKeyDown); } catch (error) { diff --git a/shine-UI/js/components/dropdown-menu.js b/shine-UI/js/components/dropdown-menu.js index 2131101e..012d5f07 100644 --- a/shine-UI/js/components/dropdown-menu.js +++ b/shine-UI/js/components/dropdown-menu.js @@ -5,6 +5,7 @@ export function createDropdownMenu({ minWidth = 210, offset = 7, align = 'right', + leftShift = 0, onOpen = null, onClose = null, } = {}) { @@ -23,7 +24,8 @@ export function createDropdownMenu({ const rect = anchorEl.getBoundingClientRect(); const margin = 10; const menuWidth = portal.offsetWidth || minWidth; - const desiredLeft = align === 'left' ? rect.left : rect.right - menuWidth; + const baseLeft = align === 'left' ? rect.left : rect.right - menuWidth; + const desiredLeft = baseLeft - Number(leftShift || 0); const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, desiredLeft)); let top = rect.bottom + offset; const menuHeight = portal.offsetHeight || 180; @@ -36,6 +38,7 @@ export function createDropdownMenu({ const open = () => { if (!anchorEl || portal) return; + document.dispatchEvent(new CustomEvent('shine:dropdown-open', { detail: { owner: anchorEl } })); const menu = document.createElement('div'); menu.className = `dm-head-menu dm-head-menu--portal shared-dropdown-menu ${className}`.trim(); menu.setAttribute('role', 'menu'); @@ -99,6 +102,10 @@ export function createDropdownMenu({ if (portal.contains(event.target) || anchorEl?.contains(event.target)) return; close(); }; + const onPeerDropdownOpen = (event) => { + if (!portal || event?.detail?.owner === anchorEl) return; + close(); + }; const onKeydown = (event) => { if (event.key !== 'Escape' || !portal) return; close(); @@ -110,6 +117,7 @@ export function createDropdownMenu({ anchorEl?.addEventListener('click', onAnchorClick); document.addEventListener('click', onOutsideClick); document.addEventListener('keydown', onKeydown); + document.addEventListener('shine:dropdown-open', onPeerDropdownOpen); window.addEventListener('resize', position, { passive: true }); window.addEventListener('scroll', position, { passive: true, capture: true }); @@ -123,6 +131,7 @@ export function createDropdownMenu({ anchorEl?.removeEventListener('click', onAnchorClick); document.removeEventListener('click', onOutsideClick); document.removeEventListener('keydown', onKeydown); + document.removeEventListener('shine:dropdown-open', onPeerDropdownOpen); window.removeEventListener('resize', position); window.removeEventListener('scroll', position, true); }, diff --git a/shine-UI/js/components/toolbar.js b/shine-UI/js/components/toolbar.js index 6b800f2b..ca57f43b 100644 --- a/shine-UI/js/components/toolbar.js +++ b/shine-UI/js/components/toolbar.js @@ -84,7 +84,7 @@ export function renderToolbar(currentPageId, navigate) { `; } else if (isNetwork) { - btn.innerHTML = iconHtml(item); + btn.innerHTML = `${iconHtml(item)}${item.label}`; btn.setAttribute('aria-label', item.label); btn.title = item.label; } else { diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index 09815df2..08dddfc7 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -966,6 +966,7 @@ function openTopChannelsMenu({ onFindChannel, }) { closeTopChannelsMenu(listState); + document.dispatchEvent(new CustomEvent('shine:dropdown-open', { detail: { owner: anchorEl } })); const root = document.getElementById('modal-root'); if (!root || !anchorEl) return; @@ -975,7 +976,9 @@ function openTopChannelsMenu({ left = Math.max(12, Math.min(left, window.innerWidth - menuWidth - 12)); const estimatedHeight = 250; - let top = rect.bottom + 8; + const titleAnchor = document.querySelector('.channels-filter-title'); + const titleRect = titleAnchor?.getBoundingClientRect?.(); + let top = (titleRect?.bottom || rect.bottom) + 7; if (top + estimatedHeight > window.innerHeight - 10) { top = Math.max(12, rect.top - estimatedHeight - 8); } @@ -1022,13 +1025,19 @@ function openTopChannelsMenu({ const onOverlayClick = (event) => { if (event.target === overlay) closeTopChannelsMenu(listState); }; + const onPeerDropdownOpen = (event) => { + if (event?.detail?.owner === anchorEl) return; + closeTopChannelsMenu(listState); + }; const onWindowResize = () => closeTopChannelsMenu(listState); overlay.addEventListener('click', onOverlayClick); window.addEventListener('resize', onWindowResize); + document.addEventListener('shine:dropdown-open', onPeerDropdownOpen); listState.topMenuCleanup = () => { overlay.removeEventListener('click', onOverlayClick); window.removeEventListener('resize', onWindowResize); + document.removeEventListener('shine:dropdown-open', onPeerDropdownOpen); }; } @@ -1368,6 +1377,7 @@ export function render({ navigate, route, chrome }) { const channelsFilterMenu = createDropdownMenu({ anchorEl: topTitle, align: 'left', + leftShift: 72, minWidth: 220, items: [ { label: 'Все каналы', iconHtml: channelMenuIcon('all'), action: () => navigate(buildChannelsViewRoute(CHANNELS_VIEW_ALL)) }, diff --git a/shine-UI/js/pages/entry-settings-view.js b/shine-UI/js/pages/entry-settings-view.js index d37e60c7..20a0974f 100644 --- a/shine-UI/js/pages/entry-settings-view.js +++ b/shine-UI/js/pages/entry-settings-view.js @@ -50,7 +50,7 @@ export function render({ navigate }) { controls.className = 'row wrap-row'; const checkButton = document.createElement('button'); - checkButton.className = 'ghost-btn server-check-btn'; + checkButton.className = 'shine-btn shine-btn--settings server-check-btn'; checkButton.type = 'button'; checkButton.textContent = 'Проверить'; @@ -132,7 +132,7 @@ export function render({ navigate }) { if (isLocalDemoAvailable()) { const localDemoButton = document.createElement('button'); - localDemoButton.className = 'ghost-btn preauth-local-demo-btn'; + localDemoButton.className = 'shine-btn shine-btn--settings preauth-local-demo-btn'; localDemoButton.type = 'button'; localDemoButton.textContent = 'Открыть локальный тестовый режим'; localDemoButton.addEventListener('click', () => { @@ -143,7 +143,7 @@ export function render({ navigate }) { } const serverUiButton = document.createElement('button'); - serverUiButton.className = 'ghost-btn'; + serverUiButton.className = 'shine-btn shine-btn--settings'; serverUiButton.type = 'button'; serverUiButton.textContent = 'Настроить свой сервер'; serverUiButton.addEventListener('click', () => { @@ -152,13 +152,13 @@ export function render({ navigate }) { }); const cancelButton = document.createElement('button'); - cancelButton.className = 'ghost-btn'; + cancelButton.className = 'shine-btn shine-btn--settings'; cancelButton.type = 'button'; cancelButton.textContent = 'Отмена'; cancelButton.addEventListener('click', () => navigate('start-view')); const saveButton = document.createElement('button'); - saveButton.className = 'primary-btn'; + saveButton.className = 'shine-btn shine-btn--primary'; saveButton.type = 'button'; saveButton.textContent = 'Сохранить'; saveButton.addEventListener('click', async () => { diff --git a/shine-UI/js/pages/language-view.js b/shine-UI/js/pages/language-view.js index 30da1a4d..44487543 100644 --- a/shine-UI/js/pages/language-view.js +++ b/shine-UI/js/pages/language-view.js @@ -12,6 +12,7 @@ export function render({ navigate }) { const screen = document.createElement('section'); screen.className = 'stack language-screen'; const returnPage = resolveReturnPage(); + let pendingLanguage = state.entrySettings.language === 'en' ? 'en' : 'ru'; screen.append( renderHeader({ @@ -27,31 +28,42 @@ export function render({ navigate }) { card.className = 'card stack language-choice-card'; card.innerHTML = `

Выберите язык интерфейса

-
- - +
+ + + +
+
+ +
`; + const syncSelection = () => { + card.dataset.language = pendingLanguage; + card.querySelectorAll('[data-language]').forEach((item) => { + const selected = item.dataset.language === pendingLanguage; + item.classList.toggle('is-selected', selected); + item.setAttribute('aria-checked', String(selected)); + }); + }; + syncSelection(); + card.querySelectorAll('[data-language]').forEach((button) => { button.addEventListener('click', () => { - const language = String(button.dataset.language || 'ru'); - saveEntryLanguage(language); - card.querySelectorAll('[data-language]').forEach((item) => { - const selected = item === button; - item.classList.toggle('is-selected', selected); - item.setAttribute('aria-checked', String(selected)); - }); - sessionStorage.removeItem('shine-language-return-page'); - navigate(returnPage); + pendingLanguage = button.dataset.language === 'en' ? 'en' : 'ru'; + syncSelection(); }); }); + card.querySelector('[data-action="cancel"]')?.addEventListener('click', () => { + sessionStorage.removeItem('shine-language-return-page'); + navigate(returnPage); + }); + card.querySelector('[data-action="ok"]')?.addEventListener('click', () => { + saveEntryLanguage(pendingLanguage); + sessionStorage.removeItem('shine-language-return-page'); + navigate(returnPage); + }); screen.append(card); return screen; diff --git a/shine-UI/js/pages/messages-list.js b/shine-UI/js/pages/messages-list.js index 2772a3d0..299ff489 100644 --- a/shine-UI/js/pages/messages-list.js +++ b/shine-UI/js/pages/messages-list.js @@ -227,6 +227,7 @@ export function render({ navigate, chrome }) { const chatFilterMenu = createDropdownMenu({ anchorEl: filterTitle, align: 'left', + leftShift: 72, minWidth: 225, items: [ { label: 'Все чаты', action: () => { currentChatFilter = 'all'; filterTitle.textContent = filterLabels.all; reloadForFilter(); } }, @@ -259,11 +260,13 @@ export function render({ navigate, chrome }) { const menuWidth = menuPortal.offsetWidth || 206; const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, rect.right - menuWidth)); menuPortal.style.left = `${Math.round(left)}px`; - menuPortal.style.top = `${Math.round(rect.bottom + 7)}px`; + const titleRect = filterTitle?.getBoundingClientRect?.(); + menuPortal.style.top = `${Math.round((titleRect?.bottom || rect.bottom) + 7)}px`; }; const openHeadMenu = () => { if (!menuButton || menuPortal) return; + document.dispatchEvent(new CustomEvent('shine:dropdown-open', { detail: { owner: menuButton } })); const portal = document.createElement('div'); portal.className = 'dm-head-menu dm-head-menu--portal'; portal.setAttribute('role', 'menu'); @@ -304,6 +307,10 @@ export function render({ navigate, chrome }) { if (menuPortal.contains(event.target) || menuButton?.contains(event.target)) return; closeHeadMenu(); }; + const onPeerDropdownOpen = (event) => { + if (!menuPortal || event?.detail?.owner === menuButton) return; + closeHeadMenu(); + }; const onMenuKeydown = (event) => { if (event.key !== 'Escape' || !menuPortal) return; closeHeadMenu(); @@ -312,6 +319,7 @@ export function render({ navigate, chrome }) { const onMenuViewportChange = () => positionHeadMenu(); document.addEventListener('click', onOutsideClick); document.addEventListener('keydown', onMenuKeydown); + document.addEventListener('shine:dropdown-open', onPeerDropdownOpen); window.addEventListener('resize', onMenuViewportChange, { passive: true }); window.addEventListener('scroll', onMenuViewportChange, { passive: true, capture: true }); @@ -487,6 +495,7 @@ function renderRow(item) { chatFilterMenu.destroy(); document.removeEventListener('click', onOutsideClick); document.removeEventListener('keydown', onMenuKeydown); + document.removeEventListener('shine:dropdown-open', onPeerDropdownOpen); window.removeEventListener('resize', onMenuViewportChange); window.removeEventListener('scroll', onMenuViewportChange, true); }; diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index c276cc55..3be47e4d 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -10040,10 +10040,11 @@ body.chat-topbar-overlay .composer-slot { } .profile-head-menu .shared-dropdown-menu__item > img { - width: 23px; - height: 23px; - min-width: 23px; - flex-basis: 23px; + width: 27px; + height: 27px; + min-width: 27px; + flex-basis: 27px; + filter: drop-shadow(0 0 6px rgba(101, 183, 255, 0.36)); } .dm-head-filter-title, @@ -10235,20 +10236,20 @@ body.chat-topbar-overlay .composer-slot { .channels-screen--list .channel-row { position: relative; - width: calc(100% - 8px); - grid-template-columns: 58px minmax(0, 1fr); - gap: 10px; - min-height: 64px; - margin: 2px 4px; - padding: 5px 8px 5px 5px; + width: calc(100% - 2px); + grid-template-columns: 46px minmax(0, 1fr); + gap: 8px; + min-height: 44px; + margin: 1px 1px; + padding: 3px 6px 3px 3px; border-radius: 14px; } .channels-screen--list .channel-row .avatar { - width: 52px; - height: 52px; - min-width: 52px; - min-height: 52px; + width: 42px; + height: 42px; + min-width: 42px; + min-height: 42px; } .channels-screen--list .channel-row-main { @@ -10314,3 +10315,173 @@ body.chat-topbar-overlay .composer-slot { linear-gradient(180deg, rgba(255,255,255,.075), transparent 30%), rgba(10, 24, 52, .68) !important; } + + +/* ===== UX refinements 2026-08-29 ===== */ +/* Start: larger breathing room between brand and actions. */ +.auth-screen--welcome .auth-brand { + margin-bottom: clamp(34px, 6vh, 58px) !important; +} +.auth-screen--welcome .shine-actions { + margin-top: 0; +} + +/* Language selector: actual two-position switch with explicit confirmation. */ +.language-segmented-control { + position: relative; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0; + padding: 4px; + min-height: 58px; + border: 1px solid rgba(190, 210, 238, .18); + border-radius: 18px; + background: rgba(12, 22, 40, .72); + overflow: hidden; +} +.language-segmented-thumb { + position: absolute; + z-index: 0; + top: 4px; + bottom: 4px; + left: 4px; + width: calc(50% - 4px); + border: 1px solid rgba(99, 190, 255, .52); + border-radius: 14px; + background: linear-gradient(180deg, rgba(76, 166, 255, .28), rgba(30, 105, 204, .20)); + box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 0 18px rgba(63,160,255,.14); + transition: transform 180ms ease; +} +.language-choice-card[data-language="en"] .language-segmented-thumb { transform: translateX(100%); } +.language-segment { + position: relative; + z-index: 1; + min-height: 50px; + padding: 0 12px; + border: 0 !important; + background: transparent !important; + color: rgba(231,239,255,.64); + font-size: 16px; + font-weight: 650; +} +.language-segment.is-selected { color: #eef8ff; } +.language-choice-actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-top: 8px; +} +.language-choice-actions .shine-btn { height: 50px; } + +/* Entry settings actions use the existing Start button visual language without huge width side effects. */ +.screen-content.preauth-flow .server-check-btn.shine-btn { + width: auto; + height: 44px; + min-height: 44px; + padding-inline: 18px; + border-radius: 14px; + font-size: 15px; +} +.screen-content.preauth-flow .auth-footer-actions .shine-btn { + width: 100%; +} + +/* Channels: much wider, substantially shorter rows. */ +.channels-screen--list .channels-list-content, +.channels-screen--list .channels-groups, +.channels-screen--list .stack:has(> .channel-row) { + width: calc(100% + 16px) !important; + margin-left: -8px !important; + margin-right: -8px !important; +} +.channels-screen--list .channel-row { + width: 100% !important; + min-height: 54px !important; + margin: 1px 0 !important; + padding: 5px 6px 5px 4px !important; + grid-template-columns: 50px minmax(0, 1fr) !important; + gap: 8px !important; + border-radius: 15px !important; +} +.channels-screen--list .channel-row .avatar { + width: 48px !important; + height: 48px !important; + min-width: 48px !important; + min-height: 48px !important; +} +.channels-screen--list .channel-row-main { + gap: 0 !important; + padding-right: 3px !important; + align-self: center; + overflow: hidden; +} +.channels-screen--list .channel-row-title, +.channels-screen--list .channel-row-technical, +.channels-screen--list .channel-row-description, +.channels-screen--list .channel-row-owner { + margin-block: 0 !important; + line-height: 1.12 !important; +} +.channels-screen--list .channel-row-preview-line { + display: flex !important; + align-items: baseline !important; + gap: 7px !important; + min-width: 0 !important; + width: 100% !important; + line-height: 1.15 !important; +} +.channels-screen--list .channel-row-preview-line .channel-row-message { + flex: 1 1 auto !important; + min-width: 0 !important; + max-width: 100% !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + line-height: 1.15 !important; +} +.channels-screen--list .channel-row-preview-line .channel-row-time { + flex: 0 0 auto !important; + position: static !important; + align-self: baseline !important; + margin: 0 !important; + padding: 0 !important; + line-height: 1.15 !important; + transform: none !important; + white-space: nowrap !important; +} +.channels-screen--list .channel-row-controls { top: 3px !important; right: 5px !important; } + +/* Toolbar: reserve enough vertical room for the Connections label and safe area. */ +:root { --toolbar-height: 88px; } +.toolbar-slot { + min-height: calc(88px + env(safe-area-inset-bottom)) !important; + overflow: visible !important; + padding-bottom: env(safe-area-inset-bottom) !important; +} +.toolbar { + min-height: 82px !important; + overflow: visible !important; + padding-bottom: 6px !important; + align-items: end; +} +.toolbar-btn-network { + min-height: 72px !important; + align-content: end !important; + overflow: visible !important; + padding-bottom: 2px !important; +} +.toolbar-btn-network .toolbar-icon-img { + transform: translateY(-4px) !important; +} +.toolbar-btn-network > span:last-child { + display: block !important; + transform: translateY(-2px) !important; + line-height: 1.05 !important; + padding-bottom: 1px !important; +} + +.channels-screen--list .channel-row-technical, +.channels-screen--list .channel-row-description, +.channels-screen--list .channel-row-owner { + display: none !important; +}