diff --git a/shine-UI/js/pages/user-profile-view.js b/shine-UI/js/pages/user-profile-view.js index 837a329a..c3bf2b2c 100644 --- a/shine-UI/js/pages/user-profile-view.js +++ b/shine-UI/js/pages/user-profile-view.js @@ -1,8 +1,8 @@ import { renderHeader } from '../components/header.js'; import { SHINE_CONNECTIONS_LOGO_SRC } from '../components/shine-logo.js'; import { renderUserAvatar } from '../components/avatar-image.js'; -import { state } from '../state.js'; -import { loadUserProfileCard } from '../services/user-connections.js'; +import { authService, state } from '../state.js'; +import { loadRelationsForPair, loadUserProfileCard } from '../services/user-connections.js'; import { makeProfileLinksRoute } from '../services/shine-routes.js'; import { navigateBack } from '../router.js'; @@ -17,6 +17,13 @@ function escapeHtml(text) { .replaceAll("'", '''); } +function effectiveSocial(flags = {}) { + if (flags.outCloseFriend) return 'close_friend'; + if (flags.outFriend) return 'friend'; + if (flags.outContact) return 'contact'; + return 'none'; +} + function metricHtml({ kind, label, value, glow = false, positionClass = '' }) { const numericValue = Number(value || 0); return ` @@ -51,8 +58,8 @@ function openProfileSheet(title, html) { }); } -function spiritualPathSheetHtml(card) { - return `
${escapeHtml(card?.spiritualPath || 'Не заполнено')}
`; +function aboutSheetHtml(card) { + return `
${escapeHtml(card?.about || 'Не заполнено')}
`; } function contactsSheetHtml(card) { @@ -79,6 +86,20 @@ function addIconHtml() { `; } +function relationMenuHtml(flags = {}) { + const current = effectiveSocial(flags); + const rows = [ + ['friend', 'Друг'], + ['close_friend', 'Близкий друг'], + ['contact', 'Контакт'], + ]; + return rows.map(([kind, label]) => ` + `).join(''); +} + export function render({ navigate, route, chrome }) { const requestedLogin = String(route?.params?.login || '').trim(); const selfLogin = String(state.session.login || '').trim(); @@ -101,6 +122,66 @@ export function render({ navigate, route, chrome }) { screen.append(status, body); let card = null; + let relationFlags = null; + let relationLoadPromise = null; + let addMenu = null; + let addActionButton = null; + + function updateRelationUi() { + if (!addMenu) return; + addMenu.innerHTML = relationMenuHtml(relationFlags || {}); + const social = effectiveSocial(relationFlags || {}); + addActionButton?.classList.toggle('is-active', social !== 'none'); + addActionButton?.setAttribute('aria-label', social === 'none' ? 'Добавить' : 'Изменить связь'); + } + + async function ensureRelationFlags() { + if (relationFlags) return relationFlags; + if (relationLoadPromise) return relationLoadPromise; + if (!selfLogin || !card?.login) return {}; + relationLoadPromise = loadRelationsForPair({ currentLogin: selfLogin, targetLogin: card.login }) + .then((flags) => { + relationFlags = flags || {}; + updateRelationUi(); + return relationFlags; + }) + .finally(() => { + relationLoadPromise = null; + }); + return relationLoadPromise; + } + + async function setRelationKind(kind, enabled) { + await authService.setUserRelation({ + login: selfLogin, + toLogin: card.login, + kind, + enabled, + storagePwd: state.session.storagePwdInMemory, + }); + } + + async function changeSocial(next) { + const flags = await ensureRelationFlags(); + const current = effectiveSocial(flags); + if (current === next) return; + + if (next === 'contact') { + if (flags.outCloseFriend) await setRelationKind('close_friend', false); + if (flags.outFriend) await setRelationKind('friend', false); + if (!flags.outContact) await setRelationKind('contact', true); + } + if (next === 'friend') { + if (flags.outCloseFriend) await setRelationKind('close_friend', false); + if (!flags.outFriend) await setRelationKind('friend', true); + } + if (next === 'close_friend' && !flags.outCloseFriend) { + await setRelationKind('close_friend', true); + } + + relationFlags = await loadRelationsForPair({ currentLogin: selfLogin, targetLogin: card.login }); + updateRelationUi(); + } function renderProfile() { if (!card) return; @@ -108,18 +189,11 @@ export function render({ navigate, route, chrome }) { const stats = card.stats || {}; const official = card.accountRole === 'primary'; const shining = card.shineStatus === 'shining'; - const fullName = [card.firstName, card.lastName] - .map((value) => String(value || '').trim()) - .filter(Boolean) - .join(' '); - const about = String(card.about || '').trim(); const title = header.querySelector('.page-title'); if (title) title.textContent = card.login; body.innerHTML = ` - ${fullName ? `
${escapeHtml(fullName)}
` : ''} -
${metricHtml({ kind: 'primary_received', label: 'Официальный', value: stats.primaryReceivedCount, glow: official, positionClass: 'is-top-left' })} ${metricHtml({ kind: 'shine_received', label: 'Сияющий', value: stats.shineReceivedCount, glow: shining, positionClass: 'is-top-right' })} @@ -128,17 +202,16 @@ export function render({ navigate, route, chrome }) { ${metricHtml({ kind: 'close_friends', label: 'Близкие друзья', value: stats.closeFriendsCount, positionClass: 'is-bottom-right' })}
- ${about ? `
${escapeHtml(about)}
` : ''} - -
+
${metricHtml({ kind: 'channels_following', label: 'Подписки', value: stats.followingChannelsCount, positionClass: 'is-channel-metric' })} ${metricHtml({ kind: 'channels_owned', label: 'Каналы', value: stats.ownedPublicChannelsCount, positionClass: 'is-channel-metric' })}
${!isSelf ? `
+ ` : ''} `; @@ -166,7 +239,14 @@ export function render({ navigate, route, chrome }) { glow: shining, })); + addMenu = body.querySelector('.user-profile-add-menu'); + addActionButton = body.querySelector('[data-profile-action="add"]'); + updateRelationUi(); status.textContent = ''; + + if (!isSelf && selfLogin) { + void ensureRelationFlags().catch(() => {}); + } } body.addEventListener('click', async (event) => { @@ -179,8 +259,8 @@ export function render({ navigate, route, chrome }) { } const detailButton = event.target.closest('[data-profile-detail]'); - if (detailButton?.dataset.profileDetail === 'spiritual-path') { - openProfileSheet('Духовный путь', spiritualPathSheetHtml(card)); + if (detailButton?.dataset.profileDetail === 'about') { + openProfileSheet('О себе', aboutSheetHtml(card)); return; } if (detailButton?.dataset.profileDetail === 'contacts') { @@ -188,15 +268,38 @@ export function render({ navigate, route, chrome }) { return; } - const actionButton = event.target.closest('[data-profile-action]'); - const action = actionButton?.dataset.profileAction; - if (action === 'add') { + const relationButton = event.target.closest('[data-relation-kind]'); + if (relationButton) { if (!selfLogin) { status.className = 'status-line user-profile-status is-unavailable'; status.textContent = 'Для добавления пользователя необходимо войти.'; return; } - navigate(`SHiNE/${encodeURIComponent(card.login)}/manage`); + const next = relationButton.dataset.relationKind; + try { + addMenu?.classList.add('is-busy'); + await changeSocial(next); + if (addMenu) addMenu.hidden = true; + addActionButton?.setAttribute('aria-expanded', 'false'); + status.className = 'status-line user-profile-status'; + status.textContent = ''; + } catch (error) { + status.className = 'status-line user-profile-status is-unavailable'; + status.textContent = `Ошибка: ${error?.message || 'Не удалось изменить связь'}`; + } finally { + addMenu?.classList.remove('is-busy'); + } + return; + } + + const actionButton = event.target.closest('[data-profile-action]'); + const action = actionButton?.dataset.profileAction; + if (action === 'add') { + if (!addMenu) return; + const willOpen = addMenu.hidden; + addMenu.hidden = !willOpen; + actionButton.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); + if (willOpen && selfLogin) void ensureRelationFlags().catch(() => {}); return; } if (action === 'links') { @@ -208,6 +311,17 @@ export function render({ navigate, route, chrome }) { } }); + const handleOutsidePointer = (event) => { + if (!addMenu || addMenu.hidden) return; + if (event.target instanceof Node && body.contains(event.target)) { + const insideActions = event.target.closest?.('.user-profile-actions-wrap'); + if (insideActions) return; + } + addMenu.hidden = true; + addActionButton?.setAttribute('aria-expanded', 'false'); + }; + document.addEventListener('pointerdown', handleOutsidePointer); + async function refresh() { card = await loadUserProfileCard(requestedLogin); renderProfile(); @@ -219,6 +333,7 @@ export function render({ navigate, route, chrome }) { }); screen.cleanup = () => { + document.removeEventListener('pointerdown', handleOutsidePointer); const root = document.getElementById('modal-root'); if (root?.querySelector('#user-profile-sheet-backdrop')) root.innerHTML = ''; }; diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index cc5fcbce..eacccf5b 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -9865,6 +9865,696 @@ body.chat-topbar-overlay .composer-slot > * { pointer-events: auto; } +/* ===== Профиль другого пользователя: плавающая композиция без рамок (2026-09-01) ===== */ +.user-profile-screen { + --user-profile-avatar-size: min(33.6vw, 134px); + --user-profile-metric-size: clamp(42px, 12vw, 48px); + position: relative; + min-height: 100%; + gap: 0; + isolation: isolate; + overflow-x: hidden; + margin-top: -8px; +} + +.user-profile-screen::before { + content: ''; + position: absolute; + z-index: -1; + inset: 4% 0 14%; + pointer-events: none; + background: + radial-gradient(circle at 50% 27%, rgba(73, 113, 189, 0.16), transparent 29%), + radial-gradient(circle at 25% 58%, rgba(49, 198, 255, 0.08), transparent 24%), + radial-gradient(circle at 76% 56%, rgba(224, 190, 86, 0.07), transparent 25%); + filter: blur(18px); +} + + +.user-profile-status { + min-height: 0; + margin: 0; + text-align: center; + font-size: 12px; +} + +.user-profile-status:empty { + display: none; +} + +.user-profile-body { + display: flex; + flex-direction: column; + align-items: stretch; + min-height: 0; + padding: 0 0 10px; +} + +.user-profile-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) var(--user-profile-avatar-size) minmax(0, 1fr); + grid-template-rows: 1fr 1fr; + align-items: stretch; + width: 100%; + height: var(--user-profile-avatar-size); + margin-top: 0; +} + +.user-profile-avatar-slot { + grid-column: 2; + grid-row: 1 / 3; + width: var(--user-profile-avatar-size); + height: var(--user-profile-avatar-size); + display: grid; + place-items: center; + align-self: center; + justify-self: center; + z-index: 2; +} + +.user-profile-screen .user-profile-hero-avatar.avatar, +.user-profile-screen .user-profile-hero-avatar.avatar-image { + width: 100%; + height: 100%; + min-width: 100%; + min-height: 100%; + overflow: visible; + border: 0 !important; + background: transparent; + box-shadow: none; +} + +/* На профиле используем ту же стеклянную обводку аватара, что и в остальных местах приложения. */ +.user-profile-screen .user-profile-hero-avatar.avatar-framed::after { + display: block; +} + +.user-profile-screen .user-profile-hero-avatar.avatar-framed > .avatar-fallback, +.user-profile-screen .user-profile-hero-avatar.avatar-framed > .avatar-photo { + width: 92.5%; + height: 92.5%; +} + +.user-profile-screen .user-profile-hero-avatar.avatar-glow::before { + inset: -12%; + opacity: 0.5; +} + +.user-profile-screen .user-profile-hero-avatar .avatar-fallback { + font-size: clamp(34px, 9vw, 58px); + font-weight: 800; +} + +.user-profile-metric { + width: min(100%, 92px); + min-width: 0; + padding: 0; + border: 0; + outline: 0; + background: transparent; + box-shadow: none; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + color: #edf5ff; + cursor: pointer; +} + +.user-profile-metric:focus-visible .user-profile-metric-circle, +.user-profile-metric:active .user-profile-metric-circle { + transform: scale(0.94); +} + +.user-profile-metric-circle { + width: var(--user-profile-metric-size); + height: var(--user-profile-metric-size); + min-width: var(--user-profile-metric-size); + min-height: var(--user-profile-metric-size); + border-radius: 50%; + display: grid; + place-items: center; + border: 0; + background: rgba(14, 25, 43, 0.34); + box-shadow: none; + color: rgba(238, 246, 255, 0.94); + font-size: clamp(15px, 4.2vw, 19px); + font-weight: 800; + line-height: 1; + transition: transform 120ms ease, filter 160ms ease, box-shadow 160ms ease; +} + +.user-profile-metric.is-glowing .user-profile-metric-circle { + color: var(--app-topbar-gold); + background: rgba(14, 25, 43, 0.42); + box-shadow: + 0 0 8px var(--app-topbar-blue-glow), + 0 0 22px var(--app-topbar-blue-glow-soft), + 0 0 38px rgba(72, 145, 255, 0.18); + text-shadow: + 0 0 5px var(--app-topbar-blue-glow), + 0 0 14px var(--app-topbar-blue-glow-soft); +} + +.user-profile-metric-label { + width: 100%; + min-height: 24px; + color: rgba(205, 220, 243, 0.78); + font-size: clamp(9px, 2.8vw, 11px); + font-weight: 600; + line-height: 1.08; + text-align: center; +} + +.user-profile-metric.is-top-left, +.user-profile-metric.is-bottom-left { + grid-column: 1; + justify-self: end; + padding-right: clamp(4px, 2vw, 10px); +} + +.user-profile-metric.is-top-right, +.user-profile-metric.is-bottom-right { + grid-column: 3; + justify-self: start; + padding-left: clamp(4px, 2vw, 10px); +} + +/* Парные подписи читаются от центра: у левых к аватару прижат конец текста, у правых — начало. */ +.user-profile-hero .user-profile-metric.is-top-left .user-profile-metric-label, +.user-profile-hero .user-profile-metric.is-bottom-left .user-profile-metric-label { + text-align: right; + align-self: flex-end; +} + +.user-profile-hero .user-profile-metric.is-top-right .user-profile-metric-label, +.user-profile-hero .user-profile-metric.is-bottom-right .user-profile-metric-label { + text-align: left; + align-self: flex-start; +} + +.user-profile-metric.is-top-left, +.user-profile-metric.is-top-right { + grid-row: 1; + align-self: start; +} + +.user-profile-metric.is-bottom-left, +.user-profile-metric.is-bottom-right { + grid-row: 2; + align-self: end; + flex-direction: column-reverse; +} + +.user-profile-channel-metrics { + width: 66.666%; + margin: clamp(22px, 4vh, 30px) auto 0; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: start; + justify-items: center; +} + +.user-profile-channel-metrics .user-profile-metric { + width: 100%; +} + +.user-profile-channel-metrics .user-profile-metric-label { + min-height: 16px; + font-size: 11px; +} + +.user-profile-actions-wrap { + position: relative; + width: min(86%, 310px); + margin: clamp(18px, 3.4vh, 26px) auto 0; +} + +.user-profile-actions { + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: center; + justify-items: center; +} + +.user-profile-action-btn { + width: 54px; + height: 54px; + min-width: 54px; + min-height: 54px; + padding: 7px; + display: grid; + place-items: center; + border: 0 !important; + border-radius: 50%; + outline: 0; + background: transparent !important; + box-shadow: none !important; + color: rgba(226, 238, 255, 0.86); + cursor: pointer; + opacity: 0.9; + transition: transform 120ms ease, filter 160ms ease, opacity 160ms ease; +} + +.user-profile-action-btn:hover, +.user-profile-action-btn:focus-visible { + opacity: 1; + filter: drop-shadow(0 0 10px rgba(95, 196, 255, 0.42)); +} + +.user-profile-action-btn:active { + transform: scale(0.9); +} + +.user-profile-action-btn.is-active { + color: #d9f8ff; + filter: drop-shadow(0 0 9px rgba(83, 211, 255, 0.52)); +} + +.user-profile-action-btn img { + display: block; + width: 32px; + height: 32px; + object-fit: contain; +} + +.user-profile-action-btn.is-links img { + width: 39px; + height: 39px; + filter: brightness(1.08); +} + +.user-profile-action-svg { + width: 38px; + height: 38px; + overflow: visible; + fill: none; + stroke: currentColor; + stroke-width: 2.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.user-profile-add-menu { + position: absolute; + z-index: 12; + left: 0; + bottom: calc(100% + 8px); + width: min(190px, 68vw); + padding: 6px 4px; + border: 0; + border-radius: 18px; + background: linear-gradient(180deg, rgba(13, 20, 35, 0.93), rgba(5, 10, 20, 0.88)); + box-shadow: 0 18px 42px rgba(0, 0, 0, 0.38); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + transform-origin: 28px 100%; + animation: user-profile-menu-in 130ms ease-out both; +} + +.user-profile-add-menu[hidden] { + display: none !important; +} + +.user-profile-add-menu.is-busy { + pointer-events: none; + opacity: 0.58; +} + +.user-profile-add-option { + width: 100%; + min-height: 42px; + padding: 8px 11px; + border: 0; + border-radius: 12px; + background: transparent; + box-shadow: none; + display: grid; + grid-template-columns: 1fr 24px; + align-items: center; + gap: 8px; + text-align: left; + color: rgba(231, 239, 255, 0.88); + font-size: 13px; + cursor: pointer; +} + +.user-profile-add-option:hover, +.user-profile-add-option:focus-visible, +.user-profile-add-option.is-current { + background: radial-gradient(circle at 18% 50%, rgba(70, 171, 224, 0.14), transparent 68%); + color: #ffffff; +} + +.user-profile-add-option-check { + color: #8deeff; + font-weight: 800; + text-align: center; + text-shadow: 0 0 9px rgba(89, 224, 255, 0.7); +} + +.user-profile-detail-links { + width: min(86%, 330px); + margin: clamp(16px, 3vh, 24px) auto 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 30px; +} + +.user-profile-detail-links button { + position: relative; + isolation: isolate; + min-height: 38px; + padding: 5px 8px; + border: 0 !important; + background: transparent !important; + box-shadow: none !important; + color: rgba(229, 239, 253, 0.86); + font-size: 15px; + font-weight: 650; + cursor: pointer; + text-shadow: + 0 0 4px rgba(92, 190, 255, 0.46), + 0 0 11px rgba(72, 145, 255, 0.2); +} + +.user-profile-detail-links button::before { + content: ''; + position: absolute; + z-index: -1; + left: 50%; + top: 50%; + width: 112px; + height: 34px; + transform: translate(-50%, -50%); + border-radius: 50%; + pointer-events: none; + background: radial-gradient(ellipse at center, rgba(92, 190, 255, 0.14) 0%, rgba(72, 145, 255, 0.07) 46%, transparent 74%); + filter: blur(4px); +} + +.user-profile-detail-links button:hover, +.user-profile-detail-links button:focus-visible { + color: #f2f7ff; + text-shadow: 0 0 12px rgba(111, 189, 255, 0.34); +} + +.user-profile-sheet-backdrop { + position: fixed; + z-index: 1400; + inset: 0; + display: flex; + align-items: flex-end; + justify-content: center; + padding: 0 max(10px, env(safe-area-inset-right)) max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left)); + background: rgba(0, 0, 0, 0.34); + backdrop-filter: blur(3px); + -webkit-backdrop-filter: blur(3px); +} + +.user-profile-sheet { + width: min(100%, 410px); + max-height: min(58vh, 480px); + overflow: auto; + padding: 10px 20px calc(22px + env(safe-area-inset-bottom)); + border: 0; + border-radius: 28px 28px 18px 18px; + background: + radial-gradient(circle at 50% -12%, rgba(73, 145, 211, 0.16), transparent 42%), + linear-gradient(180deg, rgba(13, 20, 34, 0.96), rgba(5, 9, 17, 0.97)); + box-shadow: 0 -20px 55px rgba(0, 0, 0, 0.46); + animation: user-profile-sheet-in 180ms ease-out both; +} + +.user-profile-sheet-handle { + width: 42px; + height: 4px; + margin: 1px auto 14px; + border-radius: 999px; + background: rgba(211, 226, 246, 0.2); +} + +.user-profile-sheet-title { + margin-bottom: 16px; + color: #f2f7ff; + font-size: 19px; + font-weight: 800; + text-align: center; +} + +.user-profile-sheet-content { + display: grid; + gap: 13px; +} + +.user-profile-sheet-copy { + white-space: pre-wrap; + overflow-wrap: anywhere; + color: rgba(225, 234, 248, 0.88); + font-size: 14px; + line-height: 1.58; +} + +.user-profile-sheet-copy.is-muted { + color: rgba(194, 207, 229, 0.56); + text-align: center; +} + +.user-profile-contact-row { + display: grid; + grid-template-columns: minmax(70px, auto) minmax(0, 1fr); + gap: 14px; + align-items: baseline; +} + +.user-profile-contact-row span { + color: rgba(181, 198, 225, 0.56); + font-size: 12px; +} + +.user-profile-contact-row b { + overflow-wrap: anywhere; + color: rgba(233, 240, 252, 0.9); + font-size: 14px; + font-weight: 600; + text-align: right; +} + +@keyframes user-profile-menu-in { + from { opacity: 0; transform: translateY(8px) scale(0.96); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +@keyframes user-profile-sheet-in { + from { opacity: 0; transform: translateY(24px); } + to { opacity: 1; transform: translateY(0); } +} + +@media (max-height: 700px) { + .user-profile-channel-metrics { + margin-top: 18px; + } + .user-profile-actions-wrap { + margin-top: 16px; + } + .user-profile-detail-links { + margin-top: 14px; + } +} + +@media (max-width: 350px) { + .user-profile-screen { + --user-profile-metric-size: 40px; + } + .user-profile-metric-label { + font-size: 9px; + } + .user-profile-actions-wrap { + width: 92%; + } +} + +/* Боковые метрики: круг строго остаётся внутри высоты аватара, подпись может быть ниже. */ +.user-profile-hero > .user-profile-metric { + position: relative; + height: var(--user-profile-metric-size); + gap: 0; +} + +.user-profile-hero > .user-profile-metric .user-profile-metric-label { + position: absolute; + top: calc(100% + 5px); + left: 0; +} + +.user-profile-metric.is-bottom-left, +.user-profile-metric.is-bottom-right { + flex-direction: column; +} + +/* ===== Профиль другого пользователя: геометрия v4 (2026-09-01) ===== + * - однородный фон без локальных пятен; + * - аватар +10% относительно v3; + * - все малые круги +10% и строго симметричны по четвертям высоты аватара; + * - неактивные круги полностью прозрачные; + * - активное состояние даёт только внешний ореол невидимой окружности (эффект затмения); + * - подписи якорятся на собственных кругах и остаются в боковых колонках. + */ +.user-profile-screen { + --user-profile-avatar-size: min(36.96vw, 147px); + --user-profile-metric-size: clamp(46px, 13.2vw, 53px); + margin-top: 0; + background: transparent; +} + +.user-profile-screen::before { + display: none; + content: none; +} + +.user-profile-body { + padding-top: clamp(10px, 1.8vh, 16px); +} + +.user-profile-hero { + grid-template-columns: minmax(0, 1fr) var(--user-profile-avatar-size) minmax(0, 1fr); + grid-template-rows: repeat(2, minmax(0, 1fr)); + height: var(--user-profile-avatar-size); + margin-top: 0; + overflow: visible; +} + +/* Боковые метрики имеют ровно размер своего невидимого круга. + Это исключает разные смещения слева/справа из-за ширины текста. */ +.user-profile-hero > .user-profile-metric { + position: relative; + width: var(--user-profile-metric-size); + min-width: var(--user-profile-metric-size); + height: var(--user-profile-metric-size); + padding: 0; + gap: 0; + justify-self: center; + align-self: center; + overflow: visible; +} + +.user-profile-metric.is-top-left, +.user-profile-metric.is-bottom-left { + grid-column: 1; + padding-right: 0; +} + +.user-profile-metric.is-top-right, +.user-profile-metric.is-bottom-right { + grid-column: 3; + padding-left: 0; +} + +.user-profile-metric.is-top-left, +.user-profile-metric.is-top-right { + grid-row: 1; + align-self: center; +} + +.user-profile-metric.is-bottom-left, +.user-profile-metric.is-bottom-right { + grid-row: 2; + align-self: center; + flex-direction: column; +} + +.user-profile-metric-circle { + position: relative; + z-index: 1; + width: var(--user-profile-metric-size); + height: var(--user-profile-metric-size); + min-width: var(--user-profile-metric-size); + min-height: var(--user-profile-metric-size); + border: 0 !important; + background: transparent !important; + box-shadow: none; + color: rgba(238, 246, 255, 0.94); + font-size: clamp(16px, 4.5vw, 20px); +} + +/* Свет идёт от геометрии невидимой окружности, а не от заливки/рамки. */ +.user-profile-metric.is-glowing .user-profile-metric-circle { + border: 0 !important; + background: transparent !important; + color: var(--app-topbar-gold); + box-shadow: + 0 0 7px 1px var(--app-topbar-blue-glow), + 0 0 20px 4px var(--app-topbar-blue-glow-soft), + 0 0 34px 8px rgba(72, 145, 255, 0.12); + text-shadow: none; +} + +/* Подпись всегда привязана к центру собственного круга и не влияет на его позицию. */ +.user-profile-hero > .user-profile-metric .user-profile-metric-label { + position: absolute; + top: calc(100% + 5px); + left: 50%; + width: clamp(76px, 22vw, 104px); + min-height: 0; + transform: translateX(-50%); + padding: 0; + text-align: center !important; + align-self: auto !important; + line-height: 1.05; + pointer-events: none; +} + +/* Чуть сильнее уводим подписи от аватара к внешним краям, + оставаясь связанными с центром соответствующего круга. */ +.user-profile-hero > .user-profile-metric.is-top-left .user-profile-metric-label, +.user-profile-hero > .user-profile-metric.is-bottom-left .user-profile-metric-label { + left: calc(50% - clamp(3px, 1.1vw, 5px)); +} + +.user-profile-hero > .user-profile-metric.is-top-right .user-profile-metric-label, +.user-profile-hero > .user-profile-metric.is-bottom-right .user-profile-metric-label { + left: calc(50% + clamp(3px, 1.1vw, 5px)); +} + +/* Нижние счётчики используют ту же прозрачную окружность и размер. */ +.user-profile-channel-metrics { + margin-top: clamp(28px, 4.6vh, 36px); +} + +.user-profile-channel-metrics .user-profile-metric { + gap: 5px; +} + +.user-profile-channel-metrics .user-profile-metric-label { + position: static; + width: 100%; + transform: none; + text-align: center; +} + +@media (max-height: 700px) { + .user-profile-body { + padding-top: 8px; + } + + .user-profile-channel-metrics { + margin-top: 25px; + } +} + +@media (max-width: 350px) { + .user-profile-screen { + --user-profile-avatar-size: min(36.96vw, 147px); + --user-profile-metric-size: 46px; + } + + .user-profile-hero > .user-profile-metric .user-profile-metric-label { + width: 72px; + font-size: 9px; + } +} + .chat-header-login-btn { padding: 4px 7px; min-height: 34px;