diff --git a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java index 42feb1af..95105a46 100644 --- a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java +++ b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/UserProfileStateDAO.java @@ -119,14 +119,14 @@ public final class UserProfileStateDAO { sql=""" SELECT cn.owner_login, cn.slug, cn.display_name, cn.ava_ar, cn.owner_bch_name, cn.channel_root_block_number, encode(cn.channel_root_block_hash,'hex') root_hash FROM channel_names_state cn WHERE LOWER(cn.owner_login)=LOWER(?) AND cn.channel_type_code=1 - ORDER BY LOWER(cn.display_name), cn.slug LIMIT ? OFFSET ? + ORDER BY cn.display_name, cn.slug LIMIT ? OFFSET ? """; } else if ("following".equals(mode)) { sql=""" SELECT DISTINCT cn.owner_login, cn.slug, cn.display_name, cn.ava_ar, cn.owner_bch_name, cn.channel_root_block_number, encode(cn.channel_root_block_hash,'hex') root_hash FROM connections_state cs JOIN channel_names_state cn ON cn.owner_bch_name=cs.to_bch_name AND cn.channel_root_block_number=cs.to_block_number AND cn.channel_root_block_hash=cs.to_block_hash WHERE LOWER(cs.login)=LOWER(?) AND cs.rel_type=30 AND cn.channel_type_code=1 - ORDER BY LOWER(cn.display_name), cn.slug LIMIT ? OFFSET ? + ORDER BY cn.display_name, cn.slug LIMIT ? OFFSET ? """; } else throw new IllegalArgumentException("Unsupported channel mode: "+mode); List out=new ArrayList<>(); diff --git a/shine-UI/js/pages/user-profile-list-view.js b/shine-UI/js/pages/user-profile-list-view.js index 5c651e67..f2d416ec 100644 --- a/shine-UI/js/pages/user-profile-list-view.js +++ b/shine-UI/js/pages/user-profile-list-view.js @@ -13,8 +13,8 @@ function parseAvatar(raw) { } const TITLES = { - friends: 'Друзья', close_friends: 'Близкие друзья', primary_received: 'Подтвердили основной аккаунт', - primary_given: 'Подтверждённые аккаунты', shine_received: 'Считают сияющим', shine_given: 'Подтверждённые сияющие', + friends: 'Друзья', close_friends: 'Близкие друзья', primary_received: 'Подтвердили аккаунт', + primary_given: 'Подтверждённые аккаунты', shine_received: 'Подтвердили сияние', shine_given: 'Подтверждённые сияющие', channels_owned: 'Каналы', channels_following: 'Подписки на каналы', }; diff --git a/shine-UI/js/pages/user-profile-view.js b/shine-UI/js/pages/user-profile-view.js index c9bc515a..c9369e5b 100644 --- a/shine-UI/js/pages/user-profile-view.js +++ b/shine-UI/js/pages/user-profile-view.js @@ -38,31 +38,12 @@ function metricHtml({ kind, label, value, glow = false, positionClass = '' }) { `; } -function openProfileSheet(title, html) { - const root = document.getElementById('modal-root'); - if (!root) return; - root.innerHTML = ` -
- -
`; - - const close = () => { - if (root.querySelector('#user-profile-sheet-backdrop')) root.innerHTML = ''; - }; - root.querySelector('#user-profile-sheet-backdrop')?.addEventListener('click', (event) => { - if (event.target?.id === 'user-profile-sheet-backdrop') close(); - }); +function spiritualPathDetailHtml(card) { + const value = String(card?.spiritualPath || '').trim(); + return `
${escapeHtml(value || 'Не заполнено')}
`; } -function spiritualPathSheetHtml(card) { - return `
${escapeHtml(card?.spiritualPath || 'Не заполнено')}
`; -} - -function contactsSheetHtml(card) { +function contactsDetailHtml(card) { const rows = [ ['Ссылки', card?.web], ['Телефон', card?.phone], @@ -70,7 +51,7 @@ function contactsSheetHtml(card) { ].filter(([, value]) => String(value || '').trim()); if (!rows.length) { - return '
Не заполнено
'; + return '
Не заполнено
'; } return rows.map(([label, value]) => `
@@ -232,10 +213,15 @@ export function render({ navigate, route, chrome }) {
` : ''} - `; + + `; const avatarSlot = body.querySelector('.user-profile-avatar-slot'); avatarSlot?.append(renderUserAvatar({ @@ -268,12 +254,27 @@ export function render({ navigate, route, chrome }) { } const detailButton = event.target.closest('[data-profile-detail]'); - if (detailButton?.dataset.profileDetail === 'spiritual-path') { - openProfileSheet('Духовный путь', spiritualPathSheetHtml(card)); - return; - } - if (detailButton?.dataset.profileDetail === 'contacts') { - openProfileSheet('Контакты', contactsSheetHtml(card)); + if (detailButton) { + const detailKind = detailButton.dataset.profileDetail; + const detailPanel = body.querySelector('#user-profile-detail-panel'); + if (!detailPanel) return; + + body.querySelectorAll('[data-profile-detail]').forEach((button) => { + const active = button === detailButton; + button.classList.toggle('is-active', active); + button.setAttribute('aria-pressed', active ? 'true' : 'false'); + }); + + if (detailKind === 'spiritual-path') { + detailPanel.innerHTML = spiritualPathDetailHtml(card); + } else if (detailKind === 'contacts') { + detailPanel.innerHTML = contactsDetailHtml(card); + } else { + return; + } + + detailPanel.hidden = false; + detailPanel.dataset.activeDetail = detailKind; return; } @@ -340,8 +341,6 @@ 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 = ''; }; return screen; diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index aaae4565..43fd6e78 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -9912,7 +9912,7 @@ body.chat-topbar-overlay .composer-slot > * { .user-profile-full-name { width: min(88%, 340px); - margin: 0 auto clamp(8px, 1.4vh, 12px); + margin: -5px auto clamp(13px, 2vh, 17px); color: rgba(239, 246, 255, 0.94); font-size: clamp(16px, 4.5vw, 19px); font-weight: 700; @@ -11340,8 +11340,8 @@ body.chat-topbar-overlay .composer-slot { .profile-stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px} .profile-stat-card{display:flex;flex-direction:column;align-items:flex-start;gap:3px;text-align:left;cursor:pointer} .profile-stat-card b{font-size:22px}.profile-stat-card span{font-size:12px;opacity:.78} -.profile-list-row{width:100%;align-items:center;gap:12px;text-align:left;cursor:pointer} -.profile-list-row-text{display:flex;flex-direction:column;gap:3px;min-width:0}.profile-list-row-text small{opacity:.68} +.profile-list-row{width:100%;align-items:center;justify-content:flex-start;gap:12px;text-align:left;cursor:pointer} +.profile-list-row-text{display:flex;flex:1 1 auto;flex-direction:column;align-items:flex-start;gap:3px;min-width:0;overflow:hidden}.profile-list-row-text b,.profile-list-row-text small{display:block;width:100%;text-align:left;overflow-wrap:anywhere}.profile-list-row-text small{opacity:.68} .profile-bottom-actions{justify-content:center;gap:12px;position:sticky;bottom:12px;z-index:3} .profile-about:empty{display:none} @@ -11527,3 +11527,152 @@ body.chat-topbar-overlay .page-header.app-topbar-shell .header-center { flex: 1 1 0 !important; min-width: 0 !important; } + +/* ===== Профиль другого пользователя: встроенные вкладки деталей (2026-09-02) ===== + * «Духовный путь» и «Контакты» переключают содержимое прямо в свободной области + * страницы. Активная кнопка отмечается отдельным сияющим эллипсом вокруг текста. */ +.user-profile-screen { + display: flex; + flex-direction: column; +} + +.user-profile-body { + flex: 1 0 auto; +} + +.user-profile-detail-links { + flex: 0 0 auto; +} + +.user-profile-detail-links button { + overflow: visible; +} + +.user-profile-detail-tab-label { + position: relative; + z-index: 0; + display: inline-flex; + min-height: 34px; + align-items: center; + justify-content: center; + color: rgba(229, 239, 253, 0.86); + transition: color 150ms ease, filter 150ms ease; +} + +.user-profile-detail-tab-label::before { + content: ''; + position: absolute; + z-index: -1; + left: 50%; + top: 50%; + width: calc(100% + 24px); + min-width: 112px; + height: 34px; + transform: translate(-50%, -50%); + border: 1px solid transparent; + border-radius: 50%; + opacity: 0; + pointer-events: none; + transition: opacity 150ms ease, border-color 150ms ease, box-shadow 150ms ease; +} + +.user-profile-detail-links button.is-active .user-profile-detail-tab-label { + color: #f4f9ff; + filter: brightness(1.08); +} + +.user-profile-detail-links button.is-active .user-profile-detail-tab-label::before { + opacity: 1; + border-color: rgba(144, 218, 255, 0.9); + box-shadow: + 0 0 5px rgba(139, 220, 255, 0.82), + 0 0 13px rgba(72, 145, 255, 0.58), + 0 0 26px rgba(72, 145, 255, 0.28), + inset 0 0 9px rgba(139, 220, 255, 0.16); +} + +.user-profile-detail-panel { + width: min(92%, 360px); + flex: 1 1 auto; + min-height: clamp(104px, 18vh, 170px); + margin: clamp(7px, 1.5vh, 12px) auto 0; + padding: clamp(10px, 2vh, 16px) 4px 4px; + color: rgba(225, 234, 248, 0.88); + overflow-wrap: anywhere; +} + +.user-profile-detail-panel[hidden] { + display: none !important; +} + +.user-profile-detail-copy { + white-space: pre-wrap; + overflow-wrap: anywhere; + color: rgba(225, 234, 248, 0.88); + font-size: 14px; + line-height: 1.58; +} + +.user-profile-detail-copy.is-muted { + color: rgba(194, 207, 229, 0.56); + text-align: center; +} + +.user-profile-detail-panel .user-profile-contact-row { + padding: 4px 0; +} + +@media (max-height: 700px) { + .user-profile-detail-panel { + min-height: 88px; + margin-top: 5px; + padding-top: 8px; + } +} + +/* ===== Профиль другого пользователя: сияние активной вкладки от букв (2026-09-02) ===== + * Без жёсткой рамки: синий свет рождается на глифах текста и мягко затухает + * в вытянутом эллиптическом ореоле вокруг подписи. */ +.user-profile-detail-tab-label::before { + width: calc(100% + 34px); + min-width: 118px; + height: 40px; + border: 0 !important; + background: radial-gradient( + ellipse at center, + rgba(105, 218, 255, 0.20) 0%, + rgba(72, 160, 255, 0.14) 34%, + rgba(54, 110, 255, 0.07) 56%, + transparent 76% + ); + box-shadow: none !important; + filter: blur(5px); + transform: translate(-50%, -50%) scale(0.88); + transition: opacity 160ms ease, transform 180ms ease, filter 180ms ease; +} + +.user-profile-detail-links button.is-active .user-profile-detail-tab-label { + color: #effbff; + filter: none; + text-shadow: + 0 0 1px rgba(245, 253, 255, 0.98), + 0 0 4px rgba(126, 222, 255, 0.98), + 0 0 8px rgba(78, 181, 255, 0.92), + 0 0 15px rgba(55, 126, 255, 0.72), + 0 0 25px rgba(55, 126, 255, 0.42); +} + +.user-profile-detail-links button.is-active .user-profile-detail-tab-label::before { + opacity: 1; + border: 0 !important; + background: radial-gradient( + ellipse at center, + rgba(112, 224, 255, 0.22) 0%, + rgba(74, 166, 255, 0.16) 34%, + rgba(51, 107, 255, 0.08) 57%, + transparent 78% + ); + box-shadow: none !important; + filter: blur(6px); + transform: translate(-50%, -50%) scale(1); +}