SHA256
Доработать аватары и меню UI
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
<link rel="apple-touch-icon" href="./img/logo.jpg" />
|
<link rel="apple-touch-icon" href="./img/logo.jpg" />
|
||||||
<title>СИЯНИЕ</title>
|
<title>СИЯНИЕ</title>
|
||||||
<script>
|
<script>
|
||||||
window.__SHINE_BUILD_HASH__ = '20260822122600';
|
window.__SHINE_BUILD_HASH__ = '20260822132200';
|
||||||
window.__SHINE_CLIENT_VERSION__ = '1.2.10';
|
window.__SHINE_CLIENT_VERSION__ = '1.2.10';
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -25,27 +25,28 @@ export function buildAvatarInitials({ login, firstName = '', lastName = '' } = {
|
|||||||
return (cleanLogin[0] || '?').toUpperCase();
|
return (cleanLogin[0] || '?').toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderUserAvatar({
|
export function renderAvatar({
|
||||||
login,
|
initials = '?',
|
||||||
firstName = '',
|
|
||||||
lastName = '',
|
|
||||||
avatar = null,
|
avatar = null,
|
||||||
size = 'large',
|
size = 'large',
|
||||||
className = '',
|
className = '',
|
||||||
title = '',
|
title = '',
|
||||||
|
alt = 'Аватар',
|
||||||
|
glow = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const wrap = document.createElement('div');
|
const wrap = document.createElement('div');
|
||||||
const classes = ['avatar', 'avatar-image'];
|
const classes = new Set(['avatar', 'avatar-image', 'avatar-framed']);
|
||||||
const sizeClass = pickSizeClass(size);
|
const sizeClass = pickSizeClass(size);
|
||||||
if (sizeClass) classes.push(sizeClass);
|
if (sizeClass) classes.add(sizeClass);
|
||||||
const extraClass = String(className || '').trim();
|
const extraClass = String(className || '').trim();
|
||||||
if (extraClass) classes.push(...extraClass.split(/\s+/g));
|
if (extraClass) extraClass.split(/\s+/g).filter(Boolean).forEach((value) => classes.add(value));
|
||||||
wrap.className = classes.join(' ');
|
if (glow) classes.add('avatar-glow');
|
||||||
|
wrap.className = Array.from(classes).join(' ');
|
||||||
if (title) wrap.title = String(title);
|
if (title) wrap.title = String(title);
|
||||||
|
|
||||||
const fallback = document.createElement('span');
|
const fallback = document.createElement('span');
|
||||||
fallback.className = 'avatar-fallback';
|
fallback.className = 'avatar-fallback';
|
||||||
fallback.textContent = buildAvatarInitials({ login, firstName, lastName });
|
fallback.textContent = String(initials || '?').trim().slice(0, 2).toUpperCase() || '?';
|
||||||
wrap.append(fallback);
|
wrap.append(fallback);
|
||||||
|
|
||||||
const txId = String(avatar?.ar || '').trim();
|
const txId = String(avatar?.ar || '').trim();
|
||||||
@@ -56,7 +57,8 @@ export function renderUserAvatar({
|
|||||||
const expectedSha256Hex = validateSha256Hex(sha256Hex) ? sha256Hex : '';
|
const expectedSha256Hex = validateSha256Hex(sha256Hex) ? sha256Hex : '';
|
||||||
|
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.alt = 'Аватар';
|
img.className = 'avatar-photo';
|
||||||
|
img.alt = String(alt || 'Аватар');
|
||||||
img.loading = 'lazy';
|
img.loading = 'lazy';
|
||||||
img.decoding = 'async';
|
img.decoding = 'async';
|
||||||
wrap.append(img);
|
wrap.append(img);
|
||||||
@@ -131,3 +133,24 @@ export function renderUserAvatar({
|
|||||||
|
|
||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function renderUserAvatar({
|
||||||
|
login,
|
||||||
|
firstName = '',
|
||||||
|
lastName = '',
|
||||||
|
avatar = null,
|
||||||
|
size = 'large',
|
||||||
|
className = '',
|
||||||
|
title = '',
|
||||||
|
glow = false,
|
||||||
|
} = {}) {
|
||||||
|
return renderAvatar({
|
||||||
|
initials: buildAvatarInitials({ login, firstName, lastName }),
|
||||||
|
avatar,
|
||||||
|
size,
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
alt: 'Аватар',
|
||||||
|
glow,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export function createOverflowDots({ className = '' } = {}) {
|
||||||
|
const dots = document.createElement('span');
|
||||||
|
const extra = String(className || '').trim();
|
||||||
|
dots.className = `app-overflow-dots${extra ? ` ${extra}` : ''}`;
|
||||||
|
dots.setAttribute('aria-hidden', 'true');
|
||||||
|
for (let i = 0; i < 3; i += 1) {
|
||||||
|
dots.append(document.createElement('i'));
|
||||||
|
}
|
||||||
|
return dots;
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
openArweaveAttachmentManager,
|
openArweaveAttachmentManager,
|
||||||
markArweaveAttachmentPlaced,
|
markArweaveAttachmentPlaced,
|
||||||
} from '../components/arweave-attachment-manager.js';
|
} from '../components/arweave-attachment-manager.js';
|
||||||
import { buildArweaveDataUrl } from '../services/arweave-file-service.js';
|
import { renderAvatar } from '../components/avatar-image.js';
|
||||||
import { makeShineChannelRoute } from '../services/shine-routes.js';
|
import { makeShineChannelRoute } from '../services/shine-routes.js';
|
||||||
|
|
||||||
export const pageMeta = { id: 'add-channel-view', title: 'Создание канала' };
|
export const pageMeta = { id: 'add-channel-view', title: 'Создание канала' };
|
||||||
@@ -40,17 +40,16 @@ function normalizeMetaText(value, max, label) {
|
|||||||
function renderAvatarPreview(slot, avatar, title) {
|
function renderAvatarPreview(slot, avatar, title) {
|
||||||
if (!slot) return;
|
if (!slot) return;
|
||||||
slot.innerHTML = '';
|
slot.innerHTML = '';
|
||||||
const wrap = document.createElement('div');
|
const label = String(title || 'К').trim() || 'К';
|
||||||
wrap.className = 'channel-profile-avatar';
|
const wrap = renderAvatar({
|
||||||
|
initials: label.slice(0, 1).toUpperCase() || 'К',
|
||||||
|
avatar,
|
||||||
|
size: 'small',
|
||||||
|
className: 'channel-profile-avatar',
|
||||||
|
title: label,
|
||||||
|
alt: 'Аватар канала',
|
||||||
|
});
|
||||||
wrap.style.setProperty('--channel-avatar-size', '104px');
|
wrap.style.setProperty('--channel-avatar-size', '104px');
|
||||||
if (avatar?.ar) {
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.alt = 'Аватар канала';
|
|
||||||
img.src = buildArweaveDataUrl({ gateway: state.entrySettings.arweaveServer, txId: avatar.ar });
|
|
||||||
wrap.append(img);
|
|
||||||
} else {
|
|
||||||
wrap.textContent = String(title || 'К').trim().slice(0, 1).toUpperCase() || 'К';
|
|
||||||
}
|
|
||||||
slot.append(wrap);
|
slot.append(wrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
readArweaveAttachmentHistory,
|
readArweaveAttachmentHistory,
|
||||||
} from '../components/arweave-attachment-manager.js';
|
} from '../components/arweave-attachment-manager.js';
|
||||||
import { formatBytes } from '../services/attachment-format.js';
|
import { formatBytes } from '../services/attachment-format.js';
|
||||||
|
import { createOverflowDots } from '../components/overflow-dots.js';
|
||||||
import { toUserMessage } from '../services/ui-error-texts.js';
|
import { toUserMessage } from '../services/ui-error-texts.js';
|
||||||
import { state } from '../state.js';
|
import { state } from '../state.js';
|
||||||
|
|
||||||
@@ -73,13 +74,14 @@ export function render({ navigate }) {
|
|||||||
<button class="secondary-btn arweave-uploads-back" type="button" data-action="back" aria-label="Назад">←</button>
|
<button class="secondary-btn arweave-uploads-back" type="button" data-action="back" aria-label="Назад">←</button>
|
||||||
<div class="arweave-uploads-title">Загрузка файлов в блокчейн</div>
|
<div class="arweave-uploads-title">Загрузка файлов в блокчейн</div>
|
||||||
<button class="primary-btn arweave-uploads-add" type="button" data-action="upload" aria-label="Добавить файл">+</button>
|
<button class="primary-btn arweave-uploads-add" type="button" data-action="upload" aria-label="Добавить файл">+</button>
|
||||||
<button class="secondary-btn arweave-uploads-menu-btn" type="button" data-action="menu" aria-label="Меню">⋮</button>
|
<button class="secondary-btn arweave-uploads-menu-btn" type="button" data-action="menu" aria-label="Меню"></button>
|
||||||
<div class="arweave-uploads-menu" data-menu="true" hidden>
|
<div class="arweave-uploads-menu" data-menu="true" hidden>
|
||||||
<button class="text-btn" type="button" data-action="upload-menu">Добавить файл</button>
|
<button class="text-btn" type="button" data-action="upload-menu">Добавить файл</button>
|
||||||
<button class="text-btn" type="button" data-action="clear">Очистить историю</button>
|
<button class="text-btn" type="button" data-action="clear">Очистить историю</button>
|
||||||
<button class="text-btn" type="button" data-action="help">Справка</button>
|
<button class="text-btn" type="button" data-action="help">Справка</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
controls.querySelector('[data-action="menu"]')?.append(createOverflowDots());
|
||||||
|
|
||||||
const statusLine = document.createElement('p');
|
const statusLine = document.createElement('p');
|
||||||
statusLine.className = 'meta-muted inline-error';
|
statusLine.className = 'meta-muted inline-error';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
showToast,
|
showToast,
|
||||||
softHaptic,
|
softHaptic,
|
||||||
} from '../services/channels-ux.js';
|
} from '../services/channels-ux.js';
|
||||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
import { renderAvatar, renderUserAvatar } from '../components/avatar-image.js';
|
||||||
import { markArweaveAttachmentPlaced, openArweaveAttachmentManager } from '../components/arweave-attachment-manager.js';
|
import { markArweaveAttachmentPlaced, openArweaveAttachmentManager } from '../components/arweave-attachment-manager.js';
|
||||||
import {
|
import {
|
||||||
composeMessageWithAttachments,
|
composeMessageWithAttachments,
|
||||||
@@ -31,7 +31,6 @@ import {
|
|||||||
makeProfileRoute,
|
makeProfileRoute,
|
||||||
makeShineMessageRoute,
|
makeShineMessageRoute,
|
||||||
} from '../services/shine-routes.js';
|
} from '../services/shine-routes.js';
|
||||||
import { buildArweaveDataUrl } from '../services/arweave-file-service.js';
|
|
||||||
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
|
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
|
||||||
|
|
||||||
export const pageMeta = { id: 'channel-view', title: 'Канал' };
|
export const pageMeta = { id: 'channel-view', title: 'Канал' };
|
||||||
@@ -387,18 +386,17 @@ function getStatusActionOptionsForTarget(targetMsgSubType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createChannelAvatarElement(channel, size = 72) {
|
function createChannelAvatarElement(channel, size = 72) {
|
||||||
const wrap = document.createElement('div');
|
|
||||||
wrap.className = 'channel-profile-avatar';
|
|
||||||
wrap.style.setProperty('--channel-avatar-size', `${size}px`);
|
|
||||||
const txId = String(channel?.avaAr || '').trim();
|
const txId = String(channel?.avaAr || '').trim();
|
||||||
if (txId) {
|
const title = String(channel?.displayTitle || channel?.name || 'К').trim() || 'К';
|
||||||
const img = document.createElement('img');
|
const wrap = renderAvatar({
|
||||||
img.alt = 'Аватар канала';
|
initials: title.slice(0, 1).toUpperCase() || 'К',
|
||||||
img.src = buildArweaveDataUrl({ gateway: state.entrySettings.arweaveServer, txId });
|
avatar: txId ? { ar: txId } : null,
|
||||||
wrap.append(img);
|
size: 'small',
|
||||||
} else {
|
className: 'channel-profile-avatar',
|
||||||
wrap.textContent = String(channel?.displayTitle || channel?.name || 'К').trim().slice(0, 1).toUpperCase() || 'К';
|
title,
|
||||||
}
|
alt: 'Аватар канала',
|
||||||
|
});
|
||||||
|
wrap.style.setProperty('--channel-avatar-size', `${size}px`);
|
||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import {
|
|||||||
writeChannelNotificationsState,
|
writeChannelNotificationsState,
|
||||||
} from '../services/channels-ux.js';
|
} from '../services/channels-ux.js';
|
||||||
import { makeShineChannelRoute } from '../services/shine-routes.js';
|
import { makeShineChannelRoute } from '../services/shine-routes.js';
|
||||||
import { navigateBack } from '../router.js';
|
import { renderAvatar } from '../components/avatar-image.js';
|
||||||
import { buildArweaveDataUrl } from '../services/arweave-file-service.js';
|
import { createOverflowDots } from '../components/overflow-dots.js';
|
||||||
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
|
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
|
||||||
|
|
||||||
export const pageMeta = { id: 'channels-list', title: 'Каналы' };
|
export const pageMeta = { id: 'channels-list', title: 'Каналы' };
|
||||||
@@ -90,6 +90,16 @@ function avatarLetterFromName(name = '') {
|
|||||||
return first.toUpperCase();
|
return first.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createChannelAvatar(channel = {}) {
|
||||||
|
return renderAvatar({
|
||||||
|
initials: channel.avatar || channel.initials || avatarLetterFromName(channel.displayTitle || channel.title || channel.name),
|
||||||
|
avatar: channel.avaAr ? { ar: String(channel.avaAr || '').trim() } : null,
|
||||||
|
size: 'small',
|
||||||
|
title: channel.displayTitle || channel.title || channel.name || 'Канал',
|
||||||
|
alt: 'Аватар канала',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function allFeedSummaries() {
|
function allFeedSummaries() {
|
||||||
const feed = state.channelsFeed || {};
|
const feed = state.channelsFeed || {};
|
||||||
return [
|
return [
|
||||||
@@ -885,7 +895,6 @@ function renderDemoFallback(container, navigate, error, onRetry, { localOnly = f
|
|||||||
const row = document.createElement('article');
|
const row = document.createElement('article');
|
||||||
row.className = 'channel-row';
|
row.className = 'channel-row';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<div class="avatar">${channel.avatar || channel.initials || '#'}</div>
|
|
||||||
<div class="channel-row-main">
|
<div class="channel-row-main">
|
||||||
<strong class="channel-row-title">${channel.title || channel.displayName || channel.name}</strong>
|
<strong class="channel-row-title">${channel.title || channel.displayName || channel.name}</strong>
|
||||||
<p class="channel-row-message">${channel.messagePreview || 'Ждем ваших начинаний'}</p>
|
<p class="channel-row-message">${channel.messagePreview || 'Ждем ваших начинаний'}</p>
|
||||||
@@ -894,6 +903,7 @@ function renderDemoFallback(container, navigate, error, onRetry, { localOnly = f
|
|||||||
<span class="channel-row-time">—</span>
|
<span class="channel-row-time">—</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
row.prepend(createChannelAvatar(channel));
|
||||||
row.addEventListener('click', () => {
|
row.addEventListener('click', () => {
|
||||||
const route = channel.route || makeShineChannelRoute({
|
const route = channel.route || makeShineChannelRoute({
|
||||||
ownerLogin: String(channel.ownerName || 'channel'),
|
ownerLogin: String(channel.ownerName || 'channel'),
|
||||||
@@ -1203,16 +1213,7 @@ function renderListContent({ screen, container, listState, navigate, refreshFeed
|
|||||||
const countersVisible = listState.revealedCounters.has(channel.id);
|
const countersVisible = listState.revealedCounters.has(channel.id);
|
||||||
row.classList.toggle('is-counters-visible', countersVisible);
|
row.classList.toggle('is-counters-visible', countersVisible);
|
||||||
|
|
||||||
const avatar = document.createElement('div');
|
const avatar = createChannelAvatar(channel);
|
||||||
avatar.className = 'avatar';
|
|
||||||
if (channel.avaAr) {
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.alt = '';
|
|
||||||
img.src = buildArweaveDataUrl({ gateway: state.entrySettings.arweaveServer, txId: channel.avaAr });
|
|
||||||
avatar.append(img);
|
|
||||||
} else {
|
|
||||||
avatar.textContent = channel.avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
const main = renderChannelMain(channel);
|
const main = renderChannelMain(channel);
|
||||||
|
|
||||||
@@ -1234,7 +1235,7 @@ function renderListContent({ screen, container, listState, navigate, refreshFeed
|
|||||||
const menuButton = document.createElement('button');
|
const menuButton = document.createElement('button');
|
||||||
menuButton.type = 'button';
|
menuButton.type = 'button';
|
||||||
menuButton.className = 'channel-menu-trigger';
|
menuButton.className = 'channel-menu-trigger';
|
||||||
menuButton.textContent = '…';
|
menuButton.append(createOverflowDots());
|
||||||
menuButton.addEventListener('click', (event) => {
|
menuButton.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(menuButton);
|
animatePress(menuButton);
|
||||||
@@ -1370,13 +1371,6 @@ export function render({ navigate, route, chrome }) {
|
|||||||
const topBarLeft = document.createElement('div');
|
const topBarLeft = document.createElement('div');
|
||||||
topBarLeft.className = 'channels-top-left';
|
topBarLeft.className = 'channels-top-left';
|
||||||
|
|
||||||
const backBtn = document.createElement('button');
|
|
||||||
backBtn.type = 'button';
|
|
||||||
backBtn.className = 'icon-btn header-back-btn channels-top-back-btn';
|
|
||||||
backBtn.textContent = '←';
|
|
||||||
backBtn.setAttribute('aria-label', 'Назад');
|
|
||||||
backBtn.addEventListener('click', () => navigateBack());
|
|
||||||
|
|
||||||
const topTitle = document.createElement('strong');
|
const topTitle = document.createElement('strong');
|
||||||
topTitle.className = 'channels-top-title';
|
topTitle.className = 'channels-top-title';
|
||||||
|
|
||||||
@@ -1388,7 +1382,7 @@ export function render({ navigate, route, chrome }) {
|
|||||||
topMenuBtn.className = 'icon-btn channels-top-more-btn';
|
topMenuBtn.className = 'icon-btn channels-top-more-btn';
|
||||||
topMenuBtn.setAttribute('aria-label', 'Ещё действия');
|
topMenuBtn.setAttribute('aria-label', 'Ещё действия');
|
||||||
topMenuBtn.title = 'Ещё действия';
|
topMenuBtn.title = 'Ещё действия';
|
||||||
topMenuBtn.textContent = '⋮';
|
topMenuBtn.append(createOverflowDots());
|
||||||
topMenuBtn.addEventListener('click', (event) => {
|
topMenuBtn.addEventListener('click', (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
animatePress(topMenuBtn);
|
animatePress(topMenuBtn);
|
||||||
@@ -1400,7 +1394,6 @@ export function render({ navigate, route, chrome }) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
topBarLeft.append(backBtn);
|
|
||||||
topBarRight.append(topMenuBtn);
|
topBarRight.append(topMenuBtn);
|
||||||
topBarEl.append(topBarLeft, topTitle, topBarRight);
|
topBarEl.append(topBarLeft, topTitle, topBarRight);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { renderHeader } from '../components/header.js';
|
import { renderHeader } from '../components/header.js';
|
||||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
import { renderUserAvatar } from '../components/avatar-image.js';
|
||||||
|
import { createOverflowDots } from '../components/overflow-dots.js';
|
||||||
import { directMessages } from '../mock-data.js';
|
import { directMessages } from '../mock-data.js';
|
||||||
import {
|
import {
|
||||||
addAppLogEntry,
|
addAppLogEntry,
|
||||||
@@ -978,7 +979,7 @@ export function render({ navigate, route, chrome }) {
|
|||||||
onClick: () => handleStartCall('audio'),
|
onClick: () => handleStartCall('audio'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '⋮',
|
iconNode: createOverflowDots(),
|
||||||
title: 'Действия чата',
|
title: 'Действия чата',
|
||||||
ariaLabel: 'Открыть меню действий чата',
|
ariaLabel: 'Открыть меню действий чата',
|
||||||
className: 'chat-header-icon-btn chat-header-menu-btn',
|
className: 'chat-header-icon-btn chat-header-menu-btn',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '../state.js';
|
} from '../state.js';
|
||||||
import { loadCurrentRelations } from '../services/user-connections.js';
|
import { loadCurrentRelations } from '../services/user-connections.js';
|
||||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
import { renderUserAvatar } from '../components/avatar-image.js';
|
||||||
|
import { createOverflowDots } from '../components/overflow-dots.js';
|
||||||
import { loadProfileSnapshot } from '../services/user-profile-params.js';
|
import { loadProfileSnapshot } from '../services/user-profile-params.js';
|
||||||
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
|
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
|
||||||
|
|
||||||
@@ -37,13 +38,14 @@ async function loadDmAvatarSnapshot(login) {
|
|||||||
return pending;
|
return pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDmAvatar(login) {
|
function createDmAvatar(login, { className = '' } = {}) {
|
||||||
const cleanLogin = String(login || '').trim();
|
const cleanLogin = String(login || '').trim();
|
||||||
const title = cleanLogin ? `Профиль ${cleanLogin}` : '';
|
const title = cleanLogin ? `Профиль ${cleanLogin}` : '';
|
||||||
const avatarEl = renderUserAvatar({
|
const avatarEl = renderUserAvatar({
|
||||||
login: cleanLogin || 'unknown',
|
login: cleanLogin || 'unknown',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
title,
|
title,
|
||||||
|
className,
|
||||||
});
|
});
|
||||||
if (!cleanLogin) return avatarEl;
|
if (!cleanLogin) return avatarEl;
|
||||||
void loadDmAvatarSnapshot(cleanLogin).then((snapshot) => {
|
void loadDmAvatarSnapshot(cleanLogin).then((snapshot) => {
|
||||||
@@ -58,6 +60,7 @@ function createDmAvatar(login) {
|
|||||||
: null,
|
: null,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
title,
|
title,
|
||||||
|
className,
|
||||||
});
|
});
|
||||||
upgraded.classList.add('avatar');
|
upgraded.classList.add('avatar');
|
||||||
avatarEl.replaceWith(upgraded);
|
avatarEl.replaceWith(upgraded);
|
||||||
@@ -91,8 +94,6 @@ function compareChatRows(a, b) {
|
|||||||
return nameA.localeCompare(nameB, 'ru');
|
return nameA.localeCompare(nameB, 'ru');
|
||||||
}
|
}
|
||||||
|
|
||||||
const SVG_CHEVRON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>';
|
|
||||||
|
|
||||||
export function render({ navigate, chrome }) {
|
export function render({ navigate, chrome }) {
|
||||||
const screen = document.createElement('section');
|
const screen = document.createElement('section');
|
||||||
screen.className = 'stack dm-screen dm-list-screen';
|
screen.className = 'stack dm-screen dm-list-screen';
|
||||||
@@ -102,15 +103,11 @@ export function render({ navigate, chrome }) {
|
|||||||
head.className = 'dm-head';
|
head.className = 'dm-head';
|
||||||
head.innerHTML = `
|
head.innerHTML = `
|
||||||
<div class="dm-head-brand">
|
<div class="dm-head-brand">
|
||||||
<div class="dm-head-hex">${(login[0] || 'A').toUpperCase()}</div>
|
<div class="dm-head-avatar-slot"></div>
|
||||||
<div class="dm-head-id">
|
|
||||||
<span class="dm-head-name"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<h1 class="dm-head-title">Контакты</h1>
|
<h1 class="dm-head-title">Контакты</h1>
|
||||||
<div class="dm-head-menu-wrap">
|
<div class="dm-head-menu-wrap">
|
||||||
<button type="button" class="dm-head-menu-btn" aria-label="Меню контактов" aria-haspopup="menu" aria-expanded="false">
|
<button type="button" class="dm-head-menu-btn" aria-label="Меню контактов" aria-haspopup="menu" aria-expanded="false">
|
||||||
<span class="dm-head-menu-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
|
||||||
</button>
|
</button>
|
||||||
<div class="dm-head-menu" role="menu" hidden>
|
<div class="dm-head-menu" role="menu" hidden>
|
||||||
<button type="button" class="dm-head-menu-item" role="menuitem" data-action="search-contacts">
|
<button type="button" class="dm-head-menu-item" role="menuitem" data-action="search-contacts">
|
||||||
@@ -123,11 +120,14 @@ export function render({ navigate, chrome }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
const headName = head.querySelector('.dm-head-name');
|
const headAvatarSlot = head.querySelector('.dm-head-avatar-slot');
|
||||||
if (headName) headName.textContent = login;
|
if (headAvatarSlot) {
|
||||||
|
headAvatarSlot.append(createDmAvatar(login, { className: 'dm-head-avatar' }));
|
||||||
|
}
|
||||||
|
const menuButton = head.querySelector('.dm-head-menu-btn');
|
||||||
|
menuButton?.append(createOverflowDots());
|
||||||
|
|
||||||
const menuWrap = head.querySelector('.dm-head-menu-wrap');
|
const menuWrap = head.querySelector('.dm-head-menu-wrap');
|
||||||
const menuButton = head.querySelector('.dm-head-menu-btn');
|
|
||||||
const menuTemplate = head.querySelector('.dm-head-menu');
|
const menuTemplate = head.querySelector('.dm-head-menu');
|
||||||
// The header itself is mounted into chrome/topbar, whose hit-test area ends at the
|
// The header itself is mounted into chrome/topbar, whose hit-test area ends at the
|
||||||
// header bounds. A dropdown overflowing below it can look visible but clicks may
|
// header bounds. A dropdown overflowing below it can look visible but clicks may
|
||||||
@@ -229,7 +229,6 @@ export function render({ navigate, chrome }) {
|
|||||||
${item.unread ? `<span class="dm-unread-badge">${item.unread > 99 ? '99+' : item.unread}</span>` : '<span class="dm-row-meta-spacer" aria-hidden="true"></span>'}
|
${item.unread ? `<span class="dm-unread-badge">${item.unread > 99 ? '99+' : item.unread}</span>` : '<span class="dm-row-meta-spacer" aria-hidden="true"></span>'}
|
||||||
<div class="dm-row-meta-line">
|
<div class="dm-row-meta-line">
|
||||||
${item.time ? '<span class="dm-row-time"></span>' : '<span class="dm-row-time dm-row-time--empty"></span>'}
|
${item.time ? '<span class="dm-row-time"></span>' : '<span class="dm-row-time dm-row-time--empty"></span>'}
|
||||||
<span class="dm-chevron">${SVG_CHEVRON}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -282,10 +282,7 @@ export function render({ navigate, route, chrome } = {}) {
|
|||||||
else window.history.replaceState({}, '', nextPath);
|
else window.history.replaceState({}, '', nextPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBackButtonState(backBtn) {
|
|
||||||
if (!(backBtn instanceof HTMLButtonElement)) return;
|
|
||||||
backBtn.disabled = centerHistory.length === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSearchModal() {
|
function openSearchModal() {
|
||||||
const root = document.getElementById('modal-root');
|
const root = document.getElementById('modal-root');
|
||||||
@@ -490,7 +487,6 @@ export function render({ navigate, route, chrome } = {}) {
|
|||||||
if (engine && activeFilter !== 'all') engine.setFilter(FILTERS[activeFilter].pred);
|
if (engine && activeFilter !== 'all') engine.setFilter(FILTERS[activeFilter].pred);
|
||||||
|
|
||||||
persistHistory();
|
persistHistory();
|
||||||
setBackButtonState(backBtnEl);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestId !== loadSeq) return;
|
if (requestId !== loadSeq) return;
|
||||||
window.alert(`Ошибка загрузки связей: ${error?.message || 'unknown'}`);
|
window.alert(`Ошибка загрузки связей: ${error?.message || 'unknown'}`);
|
||||||
@@ -499,24 +495,10 @@ export function render({ navigate, route, chrome } = {}) {
|
|||||||
|
|
||||||
const header = renderHeader({
|
const header = renderHeader({
|
||||||
title: 'Связи',
|
title: 'Связи',
|
||||||
leftAction: {
|
|
||||||
label: '←',
|
|
||||||
onClick: () => {
|
|
||||||
if (!centerHistory.length) return;
|
|
||||||
const prev = centerHistory.pop();
|
|
||||||
if (!prev) {
|
|
||||||
setBackButtonState(backBtnEl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
void load(prev, { pushHistory: false });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rightActions: [
|
rightActions: [
|
||||||
{ label: 'Найти', onClick: openSearchModal },
|
{ label: 'Найти', onClick: openSearchModal },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const backBtnEl = header.querySelector('.header-left .icon-btn');
|
|
||||||
setBackButtonState(backBtnEl);
|
|
||||||
|
|
||||||
// Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
|
// Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
|
||||||
screen.cleanup = () => {
|
screen.cleanup = () => {
|
||||||
@@ -542,7 +524,6 @@ export function render({ navigate, route, chrome } = {}) {
|
|||||||
window.setTimeout(() => openSearchModal(), 0);
|
window.setTimeout(() => openSearchModal(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setBackButtonState(backBtnEl);
|
|
||||||
|
|
||||||
// Панель фильтров слоёв (оверлей под шапкой)
|
// Панель фильтров слоёв (оверлей под шапкой)
|
||||||
const filterBar = document.createElement('div');
|
const filterBar = document.createElement('div');
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '../services/user-profile-params.js';
|
} from '../services/user-profile-params.js';
|
||||||
import { buildIdentityLines } from '../services/user-connections.js';
|
import { buildIdentityLines } from '../services/user-connections.js';
|
||||||
import { renderUserAvatar } from '../components/avatar-image.js';
|
import { renderUserAvatar } from '../components/avatar-image.js';
|
||||||
|
import { createOverflowDots } from '../components/overflow-dots.js';
|
||||||
|
|
||||||
export const pageMeta = { id: 'profile-view', title: 'Профиль' };
|
export const pageMeta = { id: 'profile-view', title: 'Профиль' };
|
||||||
|
|
||||||
@@ -105,13 +106,13 @@ export function render({ navigate, chrome }) {
|
|||||||
<div class="header-center"><h1 class="page-title">Профиль</h1></div>
|
<div class="header-center"><h1 class="page-title">Профиль</h1></div>
|
||||||
<div class="header-actions profile-head-menu-wrap dm-head-menu-wrap">
|
<div class="header-actions profile-head-menu-wrap dm-head-menu-wrap">
|
||||||
<button type="button" class="dm-head-menu-btn profile-head-menu-btn" aria-label="Меню профиля" aria-haspopup="menu" aria-expanded="false">
|
<button type="button" class="dm-head-menu-btn profile-head-menu-btn" aria-label="Меню профиля" aria-haspopup="menu" aria-expanded="false">
|
||||||
<span class="dm-head-menu-dots" aria-hidden="true"><i></i><i></i><i></i></span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const profileMenuWrap = topbar.querySelector('.profile-head-menu-wrap');
|
const profileMenuWrap = topbar.querySelector('.profile-head-menu-wrap');
|
||||||
const profileMenuButton = topbar.querySelector('.profile-head-menu-btn');
|
const profileMenuButton = topbar.querySelector('.profile-head-menu-btn');
|
||||||
|
profileMenuButton?.append(createOverflowDots());
|
||||||
let profileMenuPortal = null;
|
let profileMenuPortal = null;
|
||||||
|
|
||||||
const closeProfileMenu = () => {
|
const closeProfileMenu = () => {
|
||||||
|
|||||||
@@ -204,3 +204,20 @@
|
|||||||
drop-shadow(0 0 5px rgba(110, 205, 255, 0.82))
|
drop-shadow(0 0 5px rgba(110, 205, 255, 0.82))
|
||||||
drop-shadow(0 0 10px rgba(72, 145, 255, 0.42)) !important;
|
drop-shadow(0 0 10px rgba(72, 145, 255, 0.42)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Личный чат: нижние иконки используют ту же бело-голубую роль, что и верхний toolbar. */
|
||||||
|
:root .dm-chat-input button.dm-emoji-btn,
|
||||||
|
:root .dm-chat-input button.dm-send-btn,
|
||||||
|
:root .dm-chat-input button.dm-edit-banner__close,
|
||||||
|
:root .dm-chat-input button.dm-emoji-btn:hover,
|
||||||
|
:root .dm-chat-input button.dm-send-btn:hover,
|
||||||
|
:root .dm-chat-input button.dm-edit-banner__close:hover,
|
||||||
|
:root .dm-chat-input button.dm-emoji-btn:focus,
|
||||||
|
:root .dm-chat-input button.dm-send-btn:focus,
|
||||||
|
:root .dm-chat-input button.dm-edit-banner__close:focus {
|
||||||
|
color: #F7FBFF !important;
|
||||||
|
text-shadow:
|
||||||
|
0 0 5px rgba(92, 190, 255, 0.72),
|
||||||
|
0 0 12px rgba(72, 145, 255, 0.34) !important;
|
||||||
|
filter: drop-shadow(0 0 4px rgba(92, 190, 255, 0.46)) !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8728,3 +8728,407 @@ body.chat-topbar-overlay .app-shell.keyboard-open .scroll-to-bottom-btn {
|
|||||||
.notifications-screen .notification-feed-tabs .notification-tab-btn {
|
.notifications-screen .notification-feed-tabs .notification-tab-btn {
|
||||||
min-width: 92px;
|
min-width: 92px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Единый шаблон аватаров (2026-08-22) =====
|
||||||
|
* Базовая аватарка повторяет принцип орбов экрана «Связи»:
|
||||||
|
* - fallback: нейтральный серый круг + белые инициалы;
|
||||||
|
* - фото занимает тот же внутренний круг;
|
||||||
|
* - стеклянный внешний круг остаётся поверх всегда, независимо от наличия фото;
|
||||||
|
* - усиленное свечение включается модификатором .avatar-glow, не меняя геометрию аватара.
|
||||||
|
*/
|
||||||
|
.avatar.avatar-image.avatar-framed {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
isolation: isolate;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #ffffff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar.avatar-image.avatar-framed > .avatar-fallback,
|
||||||
|
.avatar.avatar-image.avatar-framed > .avatar-photo {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
place-self: center;
|
||||||
|
width: 92.5%;
|
||||||
|
height: 92.5%;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar.avatar-image.avatar-framed > .avatar-fallback {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #454b55;
|
||||||
|
color: #ffffff;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.42);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||||
|
inset 0 -8px 16px rgba(0, 0, 0, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar.avatar-image.avatar-framed > .avatar-photo {
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar.avatar-image.avatar-framed.has-image > .avatar-photo {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar.avatar-image.avatar-framed.has-image > .avatar-fallback {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Второй круг — тот же стеклянный overlay, который используется в «Связях». */
|
||||||
|
.avatar.avatar-image.avatar-framed::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 50% auto auto 50%;
|
||||||
|
width: 119%;
|
||||||
|
height: 119%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
background: url('../assets/glass_overlay_faithful.png') center / contain no-repeat;
|
||||||
|
box-shadow: none;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Опциональный усиленный ореол для состояний «сияющий» и будущих экранов. */
|
||||||
|
.avatar.avatar-image.avatar-framed.avatar-glow::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -16%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, rgba(139, 232, 255, 0.36) 0%, rgba(116, 217, 255, 0.14) 48%, rgba(116, 217, 255, 0) 74%);
|
||||||
|
filter: blur(3px);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== UI cleanup: main tabs, shared overflow menu and DM list (2026-08-22) ===== */
|
||||||
|
/* Единое вертикальное троеточие для всех overflow/menu-кнопок. */
|
||||||
|
.app-overflow-dots {
|
||||||
|
width: 8px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3.5px;
|
||||||
|
color: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-overflow-dots i {
|
||||||
|
display: block;
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
flex: 0 0 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
box-shadow:
|
||||||
|
0 0 5px rgba(92, 190, 255, 0.72),
|
||||||
|
0 0 10px rgba(72, 145, 255, 0.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-menu-trigger .app-overflow-dots,
|
||||||
|
.arweave-uploads-menu-btn .app-overflow-dots {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Пустые каналы: больше никакой золотой вставки/рамки. Сообщение оформлено
|
||||||
|
* как спокойный матовый chip из панели фильтров «Связей». */
|
||||||
|
.channels-screen--list .channels-empty-state.channels-empty-state--silent {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 12px 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
backdrop-filter: none;
|
||||||
|
-webkit-backdrop-filter: none;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channels-screen--list .channels-empty-state.channels-empty-state--silent .meta-muted {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: auto;
|
||||||
|
max-width: calc(100% - 24px);
|
||||||
|
min-height: 30px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 7px 14px;
|
||||||
|
border: 0.5px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.08);
|
||||||
|
color: #cfe0ff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* «Личные»: вместо жёлтого шестиугольника — текущий аватар пользователя. */
|
||||||
|
.dm-head-avatar-slot {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
min-width: 38px;
|
||||||
|
min-height: 38px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-head-avatar.avatar,
|
||||||
|
.dm-head-avatar.avatar-image {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Список личных чатов: без цветной границы карточки, компактная ава как в шапке
|
||||||
|
* самого чата (28px), а дата/время стоит на строке превью. */
|
||||||
|
.dm-list-screen .dm-dialog-card {
|
||||||
|
grid-template-columns: 38px minmax(0, 1fr) auto;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 58px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: none;
|
||||||
|
background: rgba(7, 10, 18, 0.42);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
-webkit-backdrop-filter: blur(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-card--family,
|
||||||
|
.dm-list-screen .dm-card--shining {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-av {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-av .avatar,
|
||||||
|
.dm-list-screen .list-item .avatar {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
min-width: 28px;
|
||||||
|
min-height: 28px;
|
||||||
|
font-size: 10px;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-main,
|
||||||
|
.dm-list-screen .dm-row-meta-col {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 22px 20px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-titlewrap {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 22px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-last-message {
|
||||||
|
align-self: center;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-meta-col {
|
||||||
|
align-self: center;
|
||||||
|
justify-items: end;
|
||||||
|
justify-content: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-unread-badge,
|
||||||
|
.dm-list-screen .dm-row-meta-spacer {
|
||||||
|
grid-row: 1;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-time {
|
||||||
|
grid-row: 2;
|
||||||
|
align-self: center;
|
||||||
|
line-height: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-meta-line {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-chevron {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
.dm-list-screen .dm-dialog-card {
|
||||||
|
grid-template-columns: 38px minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-list-screen .dm-row-meta-col {
|
||||||
|
grid-column: 3;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Личные сообщения: финальная чистка чата ===== */
|
||||||
|
/* В списке «Личные» слева в шапке остаётся только аватар текущего пользователя. */
|
||||||
|
.topbar-slot .dm-list-screen .dm-head-brand {
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Пузырям сообщений не нужна отдельная обводка: направление читается по фону и геометрии. */
|
||||||
|
.dm-chat-screen .bubble,
|
||||||
|
.dm-chat-screen .bubble.in,
|
||||||
|
.dm-chat-screen .bubble.out,
|
||||||
|
.dm-chat-screen .bubble.call-tech {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-screen .bubble-reply-preview {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Нижняя панель чата повторяет бело-голубую цветовую роль верхнего toolbar вместо золота. */
|
||||||
|
.dm-chat-input {
|
||||||
|
border-top: 1px solid rgba(92, 190, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .dm-input {
|
||||||
|
border-color: rgba(247, 251, 255, 0.12);
|
||||||
|
color: var(--app-topbar-gold);
|
||||||
|
text-shadow:
|
||||||
|
0 0 4px rgba(92, 190, 255, 0.34),
|
||||||
|
0 0 10px rgba(72, 145, 255, 0.16);
|
||||||
|
caret-color: var(--app-topbar-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .dm-input:focus {
|
||||||
|
border-color: rgba(92, 190, 255, 0.28);
|
||||||
|
box-shadow: 0 0 0 1px rgba(92, 190, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .dm-input::placeholder {
|
||||||
|
color: rgba(247, 251, 255, 0.46);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .dm-emoji-btn,
|
||||||
|
.dm-chat-input .dm-send-btn,
|
||||||
|
.dm-chat-input .dm-edit-banner__close {
|
||||||
|
color: var(--app-topbar-gold) !important;
|
||||||
|
text-shadow:
|
||||||
|
0 0 5px var(--app-topbar-blue-glow),
|
||||||
|
0 0 12px var(--app-topbar-blue-glow-soft) !important;
|
||||||
|
filter: drop-shadow(0 0 4px rgba(92, 190, 255, 0.42));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .dm-send-btn {
|
||||||
|
border-color: rgba(247, 251, 255, 0.12);
|
||||||
|
background: rgba(92, 190, 255, 0.08);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .dm-edit-banner {
|
||||||
|
border-color: rgba(92, 190, 255, 0.18);
|
||||||
|
background: rgba(92, 190, 255, 0.08);
|
||||||
|
color: var(--app-topbar-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-chat-input .emoji-picker-tab.is-active {
|
||||||
|
background: rgba(92, 190, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== DM + Channels polish (2026-08-22 13:22) ===== */
|
||||||
|
/* «Личные»: аватар основной вкладки стоит ровно на той же горизонтальной позиции,
|
||||||
|
* что и аватар собеседника в открытом чате: после 40px back-slot + 8px gap. */
|
||||||
|
.topbar-slot .dm-list-screen .dm-head-brand {
|
||||||
|
margin-left: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-head-avatar-slot,
|
||||||
|
.dm-head-avatar.avatar,
|
||||||
|
.dm-head-avatar.avatar-image {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
min-width: 28px;
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-head-avatar.avatar,
|
||||||
|
.dm-head-avatar.avatar-image {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Composer должен быть продолжением основного фона: без верхнего разделителя
|
||||||
|
* и без отдельной затемнённой/стеклянной подложки. */
|
||||||
|
.dm-chat-input {
|
||||||
|
border-top: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
backdrop-filter: none !important;
|
||||||
|
-webkit-backdrop-filter: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Каналы: счётчик непрочитанных — целиком голубой светящийся овал,
|
||||||
|
* цифра остаётся обычной белой, без собственного glow. */
|
||||||
|
.channel-row-count {
|
||||||
|
background: rgba(92, 190, 255, 0.16);
|
||||||
|
border: 1px solid rgba(92, 190, 255, 0.56);
|
||||||
|
color: #FFFFFF;
|
||||||
|
box-shadow:
|
||||||
|
0 0 7px rgba(92, 190, 255, 0.58),
|
||||||
|
0 0 15px rgba(72, 145, 255, 0.28),
|
||||||
|
inset 0 0 8px rgba(92, 190, 255, 0.10);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Каналы: логин/техническая подпись и overflow-троеточие используют
|
||||||
|
* тот же основной белый + голубое свечение, что верхний toolbar. */
|
||||||
|
.channel-row-technical {
|
||||||
|
font-family: var(--font-main);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--app-topbar-gold);
|
||||||
|
text-shadow:
|
||||||
|
0 0 5px var(--app-topbar-blue-glow),
|
||||||
|
0 0 12px var(--app-topbar-blue-glow-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-menu-trigger {
|
||||||
|
color: var(--app-topbar-gold);
|
||||||
|
text-shadow:
|
||||||
|
0 0 5px var(--app-topbar-blue-glow),
|
||||||
|
0 0 12px var(--app-topbar-blue-glow-soft);
|
||||||
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@
|
|||||||
}
|
}
|
||||||
.fg-orb-host .fg-pngorb-init {
|
.fg-orb-host .fg-pngorb-init {
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
background: #26344a; color: #cfe0ff; font-weight: 600; font-size: 20px;
|
background: #454b55; color: #ffffff; font-weight: 600; font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fg-node.is-family .node-dot {
|
.fg-node.is-family .node-dot {
|
||||||
|
|||||||
Reference in New Issue
Block a user