diff --git a/shine-UI/js/mock-data.js b/shine-UI/js/mock-data.js index b44879b..5199e4b 100644 --- a/shine-UI/js/mock-data.js +++ b/shine-UI/js/mock-data.js @@ -39,39 +39,31 @@ export const deviceSessions = [ }, ]; +// Экран «Личные сообщения» — списочная форма «Связей». Храним СЕМАНТИКУ, не цвет. +// Цвет/режим вычисляет js/pages/messages/dm-visual-resolver.js (resolveDmVisualState): +// relationType: 'contact' | 'friend' | 'family' (family → золотой обод) +// relationRole: 'parent'|'child'|'sibling'|'spouse'|null +// isShining: true → небесный (celestial) обод/свечение (важнее relationType) +// isConfirmed: true → статус доверия «Подтверждён» (золотой shield) — НЕ красит обод +// hasActiveLink: true → статус «Связь» (изумруд) — приоритетнее «Подтверждён» +// unreadCount: number; preview: string +// toneOverride: 'default'|'family'|'shining' — ТОЛЬКО для тестового мока, в проде не использовать +// (на проде поля придут из relationFlagsForTarget/shineConfirmed/shine — пока мок для оффлайн-демо) +// ЛС-демо (мок). Поля СЕМАНТИЧЕСКИЕ (без хранения цвета) — визуал решает dm-visual-resolver.js. +// Набор покрывает ровно матрицу состояний M01–M06 из спеки (по одному кейсу на строку). export const directMessages = [ - { - id: 'u1', - name: 'Марина К.', - initials: 'МК', - lastMessage: 'Вечером скину обновления по макетам.', - time: '15:08', - unread: 2, - }, - { - id: 'u2', - name: 'Илья П.', - initials: 'ИП', - lastMessage: 'Спасибо, уже проверяю!', - time: '14:31', - unread: 0, - }, - { - id: 'u3', - name: 'Елена Д.', - initials: 'ЕД', - lastMessage: 'Тестовый стенд снова доступен.', - time: '13:02', - unread: 5, - }, - { - id: 'u4', - name: 'Никита О.', - initials: 'НО', - lastMessage: 'Отлично, давай так и сделаем.', - time: 'вчера', - unread: 0, - }, + // M01 — обычный контакт, подтверждён: violet-обод, справа золотой shield «Подтверждён», без unread. + { id: 'u1', name: 'Марина К.', initials: 'МК', preview: 'Вечером скину обновления по макетам.', lastMessage: 'Вечером скину обновления по макетам.', time: '15:08', relationType: 'contact', relationRole: null, isShining: false, isConfirmed: true, hasActiveLink: false, unreadCount: 0, photo: '/assets/demo-avatars/u1.jpg' }, + // M02 — обычная активная связь: violet-обод, изумрудная капсула «Связь», отдельная violet-сфера «2». + { id: 'u2', login: 'ilya', name: 'Илья П.', initials: 'ИП', preview: 'Спасибо, уже проверяю!', lastMessage: 'Спасибо, уже проверяю!', time: '14:31', relationType: 'contact', relationRole: null, isShining: false, isConfirmed: false, hasActiveLink: true, unreadCount: 2, photo: '/assets/demo-avatars/u2.jpg', connectedVia: [{ login: 'pavel', name: 'Павел С.', photo: '/assets/demo-avatars/u6.jpg' }] }, + // M03 — сияющий с активной связью: мини-сфера языка «Связи», изумруд «Связь», violet-сфера «5». + { id: 'u3', login: 'elena', name: 'Елена Д.', initials: 'ЕД', preview: 'Тестовый стенд снова доступен.', lastMessage: 'Тестовый стенд снова доступен.', time: '13:02', relationType: 'contact', relationRole: null, isShining: true, isConfirmed: false, hasActiveLink: true, unreadCount: 5, photo: '/assets/demo-avatars/u3.jpg', connectedVia: [{ login: 'pavel', name: 'Павел С.', photo: '/assets/demo-avatars/u6.jpg' }, { login: 'marina', name: 'Марина К.', photo: '/assets/demo-avatars/u1.jpg' }] }, + // M04 — обычный контакт без статуса: только violet-обод и спокойный chevron, без unread. + { id: 'u4', name: 'Никита О.', initials: 'НО', preview: 'Отлично, давай так и сделаем.', lastMessage: 'Отлично, давай так и сделаем.', time: 'вчера', relationType: 'contact', relationRole: null, isShining: false, isConfirmed: false, hasActiveLink: false, unreadCount: 0, photo: '/assets/demo-avatars/u4.jpg' }, + // M05 — семья с подтверждением: золотой обод + тёплая аура, справа золотой shield «Подтверждён». + { id: 'u6', name: 'Павел С.', initials: 'ПС', preview: 'Семейный архив обновил.', lastMessage: 'Семейный архив обновил.', time: 'вчера', relationType: 'family', relationRole: 'parent', isShining: false, isConfirmed: true, hasActiveLink: false, unreadCount: 0, photo: '/assets/demo-avatars/u6.jpg' }, + // M06 — семья с активной связью: золотой обод (family), справа «Связь» по приоритету link>confirmed, violet-сфера «1». + { id: 'u7', login: 'anya', name: 'Аня В.', initials: 'АВ', preview: 'Семейный чат: жду в 19:00.', lastMessage: 'Семейный чат: жду в 19:00.', time: 'пн', relationType: 'family', relationRole: 'sibling', isShining: false, isConfirmed: true, hasActiveLink: true, unreadCount: 1, photo: '/assets/demo-avatars/u7.jpg', connectedVia: [{ login: 'marina', name: 'Марина К.', photo: '/assets/demo-avatars/u1.jpg' }] }, ]; export const contactDirectory = [ diff --git a/shine-UI/js/pages/messages/dm-visual-resolver.js b/shine-UI/js/pages/messages/dm-visual-resolver.js new file mode 100644 index 0000000..d7467c8 --- /dev/null +++ b/shine-UI/js/pages/messages/dm-visual-resolver.js @@ -0,0 +1,34 @@ +// Экран «Личные сообщения» — единый слой «семантика отношения → визуальное состояние». +// messages-list.js только рендерит готовый результат; здесь вся логика выбора тона/статуса/бейджа. +// Источник полей — мок directMessages (оффлайн-демо). На проде сюда же подставятся реальные +// relationFlagsForTarget / shineConfirmed / shine — UI карточек переписывать не придётся. + +// Тон обода аватара. ВАЖНО: «Подтверждён» НЕ красит обод золотым (золото = семья/близкий круг). +// isShining → 'shining' (небесный) ; relationType==='family' → 'family' (золотой) ; иначе 'default' (violet). +// toneOverride — только для тестового мока (в проде не задавать). +export function resolveAvatarTone(msg) { + const o = String(msg?.toneOverride || '').trim(); + if (o === 'default' || o === 'family' || o === 'shining') return o; + if (msg?.isShining) return 'shining'; + if (msg?.relationType === 'family') return 'family'; + return 'default'; +} + +// Непрочитанные: показываем только при >0; 1–99, далее «99+». Отдельная violet-сфера (НЕ изумруд). +export function resolveUnreadStyle(msg) { + const n = Math.max(0, Math.trunc(Number(msg?.unreadCount ?? msg?.unread ?? 0)) || 0); + if (n <= 0) return null; + return { count: n, label: n > 99 ? '99+' : String(n) }; +} + +// Итоговое визуальное состояние карточки. +export function resolveDmVisualState(msg) { + const via = Array.isArray(msg?.connectedVia) && msg.connectedVia.length ? msg.connectedVia : null; + return { + tone: resolveAvatarTone(msg), // 'default' | 'family' | 'shining' + shining: Boolean(msg?.isShining), + confirmed: Boolean(msg?.isConfirmed), // галочка ✓ у имени (без слова «Подтверждён») + via, // путь «через кого»: [{name, photo}, …] | null + unread: resolveUnreadStyle(msg), // { count, label } | null + }; +} diff --git a/shine-UI/styles/network-graph.css b/shine-UI/styles/network-graph.css index 2c6715d..22adccc 100644 --- a/shine-UI/styles/network-graph.css +++ b/shine-UI/styles/network-graph.css @@ -4,6 +4,20 @@ Отдельный модуль, чтобы не раздувать components.css. ========================================================================== */ +/* Канонические токены ЯЗЫКА СВЯЗЕЙ (единый источник цвета отношений для всего продукта). + Экран «Личные сообщения» наследует их через --dm-* (не дублирует hex). + (force-graph пока использует свои JS-цвета RELATION_COLORS — миграция на токены = будущая задача.) */ +:root { + --rel-contact: #8C63FF; /* violet — обычная связь / контакт */ + --rel-family: #F0B82E; /* gold — семья / близкий круг / важность / подтверждение */ + --rel-shining: #68D8FF; /* celestial — сияющий / сильная активная связь */ + --rel-link: #19E58A; /* emerald — статус «Связь» (активный канал) */ + --rel-contact-glow: rgba(140, 99, 255, 0.24); + --rel-family-glow: rgba(240, 184, 46, 0.30); + --rel-shining-glow: rgba(104, 216, 255, 0.35); + --rel-link-glow: rgba(25, 229, 138, 0.24); +} + .fg-stage { touch-action: none; /* перехватываем жесты сами (pan), без скролла страницы */ user-select: none;