From f19f7b0ec4c6ff2766382f929f402993fd571703aa73f7d3eecb66cf934e0fd1 Mon Sep 17 00:00:00 2001 From: Pixel Date: Sat, 13 Jun 2026 18:31:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D0=B7=D0=B8=20(13.06):=20?= =?UTF-8?q?=D0=BE=D1=80=D0=B1=D1=8B=20=D0=BF=D0=BE=20=D1=80=D0=B5=D1=84?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BD=D1=81=D1=83,=20=D0=BB=D0=B8=D0=BD?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BF=D0=BE=20=D0=BA=D0=B0=D1=82=D0=B5=D0=B3?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=B8,=20=D0=BF=D0=BE=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=8F=D0=BD=D0=BD=D0=B0=D1=8F=20=D0=B2=D1=81=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=B0=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Орбы: - материал «хрусталь»: чистое лицо (виньетка 0.5→0.22), диффузный блик окна вместо «капли» (+мягкий blur sf), стекло прозрачнее (тело 0.38→0.3), полупрозрачная преломляющая кромка (blur + opacity 0.25→0.2). - размер +11.5% (node-dot 52→58px); единый ORB_R=29 как источник радиуса. - убран значок * у общих узлов (логика is-common цела). Линии: - цвет по категории на ВСЕХ рёбрах; плазма только сияющим. - общий узел наследует сияние исходного человека (не серый). - контакт линий ровно на кромке сферы орба (ORB_R), без зазора, все уровни. Навигация: - констелляция (паутина 2-3 уровней) — постоянный режим; кнопка «Вселенная» убрана; Семья/Друзья/Сияющие остаются фильтрами. Чистка осиротевшего CSS. Версия 1.2.161. Co-Authored-By: Claude Opus 4.8 (1M context) --- VERSION.properties | 2 +- shine-UI/js/pages/network/force-graph.js | 36 +++++++++++++++--------- shine-UI/js/pages/network/lab.js | 26 ++++++----------- shine-UI/styles/network-graph.css | 27 +++--------------- 4 files changed, 36 insertions(+), 55 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index 0954584..94825aa 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.160 +client.version=1.2.161 server.version=1.2.144 diff --git a/shine-UI/js/pages/network/force-graph.js b/shine-UI/js/pages/network/force-graph.js index ae876f8..8f328a8 100644 --- a/shine-UI/js/pages/network/force-graph.js +++ b/shine-UI/js/pages/network/force-graph.js @@ -90,6 +90,11 @@ const RELATION_COLORS = { // Неоновый цвет центра — из него «вытекает» градиент каждой связи к цвету периферийного узла. const FOCUS_NEON = 'rgba(140, 240, 255, 0.95)'; +// Радиус видимой сферы орба (world-единицы), синхронно с CSS `.fg-node .node-dot` = 58px → радиус 29 +// (сфера орба ≈ радиусу node-dot). ЕДИНЫЙ источник радиуса для контакта линий с кромкой и раскладки детей — +// меняешь размер орба → меняй здесь и в CSS вместе, линии останутся впритык. +const ORB_R = 29; + function easeOutCubic(t) { const x = 1 - t; @@ -437,23 +442,24 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL svg.innerHTML = '' + '' + '' - + '' - + '' - + '' + + '' + + '' + + '' + '' + + '' + '' + '' + '' - + '' + + '' + '' + (init ? '' + init + '' : '') + (src ? '' : '') + '' + '' + '' - + '' - + '' - + ''; + + '' + + '' + + ''; const im = svg.querySelector('image'); if (im) im.addEventListener('error', () => { try { im.remove(); } catch (e) { /* останутся инициалы */ } }); return svg; @@ -634,7 +640,7 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL // АДАПТИВНЫЙ радиус орбиты (фикс слипания): дети не лезут на (возможно увеличенный зумом) родитель // и не накладываются друг на друга. Клиренс = радиус родителя + базовый отступ; место = по числу детей. const baseR = tier === 2 ? DEEP_R2 : DEEP_R3; - const pr = 26 * (p.scale || 1) * (p.depthScale || 1); // визуальный радиус родителя (world-единицы) + const pr = ORB_R * (p.scale || 1) * (p.depthScale || 1); // визуальный радиус родителя (world-единицы) const cnt = childCountByParent.get(n.parentId) || 1; const ringR = Math.max(baseR + pr, cnt * 13); // расталкивание: дети без наложений (клиренс + место) const r = ringR * e; // при e=0 — в центре родителя, при 1 — на полной орбите @@ -744,7 +750,9 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL const parent = (n.parentId && nodeById.get(n.parentId)) || focus; const fx = centerX + camX + parent.x * Z; const fy = centerY + camY + parent.y * Z; - const fr = parent.dotRadius * parent.scale * (parent.depthScale ?? 1) * Z + 4; + // радиус контакта = реальный радиус сферы орба: полный орб = ORB_R (см. renderNodes pr=ORB_R*…), + // лёгкая точка (.fg-dot) = её dotRadius. Старое dotRadius у орбов (32/16) — легаси, давало разный зазор. + const fr = (parent.dotOnly ? parent.dotRadius : ORB_R) * parent.scale * (parent.depthScale ?? 1) * Z; const nx = tx(n); const ny = ty(n); if ((nx < -80 && fx < -80) || (nx > viewW + 80 && fx > viewW + 80)) continue; @@ -761,8 +769,8 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL const len = Math.hypot(dx, dy) || 1; const ux = dx / len; const uy = dy / len; - const nr = n.dotRadius * n.scale * (n.depthScale ?? 1) * Z + 4; - // концы линии — у краёв кружков + const nr = (n.dotOnly ? n.dotRadius : ORB_R) * n.scale * (n.depthScale ?? 1) * Z; + // концы линии — ровно на кромке сферы орба (радиус ORB_R для полных орбов), без зазора и без захода внутрь const x1 = fx + ux * fr; const y1 = fy + uy * fr; const x2 = ex - ux * nr; @@ -828,9 +836,9 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL parts.push(``); } } - } else if (shine || n.track || onPath) { - // СИЯЮЩАЯ связь — плазменный композитинг: ОДИН центральный S-путь (cubic) + ТРИ наложенных слоя - // с ОДИНАКОВЫМ d (объём из толщины+blur, не из геометрии). Слои: широкое поле / трубка / ядро. + } else if (shine) { + // СИЯЮЩАЯ связь → цвет сияющей линии (плазма). Только сияющим — несияющие (в т.ч. активный путь + // погружения track/onPath) идут ниже в ЦВЕТ КАТЕГОРИИ. Плазма: ОДИН S-путь + ТРИ слоя на одном d. const pnx = -uy; const pny = ux; // перпендикуляр к хорде const amp = Math.min(13, 5 + segLen0 * 0.05); // амплитуда S — спокойная изящная волна (∝ длине) diff --git a/shine-UI/js/pages/network/lab.js b/shine-UI/js/pages/network/lab.js index c7cc6d6..6b05168 100644 --- a/shine-UI/js/pages/network/lab.js +++ b/shine-UI/js/pages/network/lab.js @@ -46,7 +46,7 @@ function helpText() { '• Тап по центральному узлу — здесь открылся бы профиль.', '• Долгое нажатие — контекстное меню (в реальном пути «Связи»).', '• Чипы под шапкой — фильтры слоёв: Все / Семья / Друзья / Сияющие.', - '• Чип «Вселенная» — режим «Интерактивная паутина» (глубина 2-3 уровней, фейковые связи).', + '• Вселенная — постоянный режим «Интерактивная паутина» (глубина 2-3 уровней, фейковые связи).', ' Наведи мышь/палец на узел — его связи временно выплывают (превью). КЛИК по узлу — «умный', ' наезд»: камера летит и центрирует его, он вырастает, друзья раскрываются крупно вокруг,', ' путь назад к Ивану горит нитью, остальное затемняется. Клик по нему ещё раз — всплыть.', @@ -99,12 +99,13 @@ function addDeepLevels(model) { for (let i = 0; i < k2; i += 1) { const id2 = `${p.id}__d2_${i}`; const ang2 = (i / k2) * Math.PI * 2 + seed01(p.id) * 0.6; - // i===0 + есть общий → подставляем узнаваемого друга Ивана как ОБЩУЮ связь (золотой ободок ★) + // i===0 + есть общий → подставляем узнаваемого друга Ивана как ОБЩУЮ связь (золотой ободок ★). + // Сияние НАСЛЕДУЕМ от исходного человека: если он сияющий — связь к нему тоже плазма (не серая). if (i === 0 && common) { extra.push({ id: id2, login: id2, name: common.name || common.login, avatar: null, photo: common.photo || null, relationType: common.relationType || 'friend', - strength: 0.5, shining: false, tier: 2, parentId: String(p.id), deepAngle: ang2, common: true, + strength: 0.5, shining: Boolean(common.shining), tier: 2, parentId: String(p.id), deepAngle: ang2, common: true, }); continue; } @@ -181,7 +182,8 @@ export function renderNetworkLab({ navigate }) { header.classList.add('network-header-overlay'); let centerLogin = START_LOGIN; - let deepMode = false; + // Констелляция (паутина 2-3 уровней) — ПОСТОЯННЫЙ режим по умолчанию (чип «Вселенная» убран). + let deepMode = true; // Состояние активного слоя (как в network-view): фокус всегда виден. let activeFilter = 'all'; @@ -259,18 +261,8 @@ export function renderNetworkLab({ navigate }) { filterChips[key] = chip; filterBar.append(chip); }); - // Переключатель прототипа «Вселенная» (глубина 2-3 уровней) — отдельный чип. - const deepChip = document.createElement('button'); - deepChip.type = 'button'; - deepChip.className = 'fg-filter-chip fg-deep-chip'; - deepChip.textContent = '🌌 Вселенная'; - deepChip.addEventListener('click', () => { - deepMode = !deepMode; - deepChip.classList.toggle('is-active', deepMode); - graph.setModel(buildLabModel(centerLogin, deepMode)); - if (activeFilter !== 'all') graph.setFilter(FILTERS[activeFilter].pred); - }); - filterBar.append(deepChip); + // Чип «Вселенная» убран: констелляция — постоянный режим (deepMode=true по умолчанию). + // Семья/Друзья/Сияющие остаются фильтрами поверх постоянной вселенной (см. filterBar выше). stage.append(filterBar); // --- Поиск + телепорт камеры: ввёл имя → камера летит к узлу (dive в «Вселенной», иначе перецентр) --- @@ -329,7 +321,7 @@ export function renderNetworkLab({ navigate }) { // Автопроверки: ТОЛЬКО при ?fgtest — экспонируем граф и прогоняем self-test (результат в консоль). if (typeof window !== 'undefined' && String(location.search).includes('fgtest')) { window.__fg = graph; - import('./selftest.js').then((m) => m.runNetworkSelfTest(graph, deepChip)).catch((e) => console.error('[fg-selftest] не загрузился', e)); + import('./selftest.js').then((m) => m.runNetworkSelfTest(graph, null)).catch((e) => console.error('[fg-selftest] не загрузился', e)); } screen.cleanup = () => { diff --git a/shine-UI/styles/network-graph.css b/shine-UI/styles/network-graph.css index 1ed7133..14fb356 100644 --- a/shine-UI/styles/network-graph.css +++ b/shine-UI/styles/network-graph.css @@ -120,9 +120,10 @@ } /* круглая аватарка узла (переиспользуем существующий .node-dot из renderUserAvatar) */ +/* 58px → радиус 29 = ORB_R в force-graph.js (контакт линий берётся от этого радиуса). */ .fg-node .node-dot { - width: 52px; - height: 52px; + width: 58px; + height: 58px; margin: 0; font-size: 16px; transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease; @@ -353,14 +354,6 @@ .fg-dot.is-tier3 { animation: none; } } -/* Чип-переключатель «Вселенная» — активное состояние наследует стиль .fg-filter-chip.is-active */ -.fg-deep-chip.is-active { - background: rgba(150, 130, 255, 0.18); - border-color: rgba(190, 170, 255, 0.6); - box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.12), 0 0 14px rgba(150, 120, 255, 0.3); - color: #efeaff; -} - /* «Призрак» старой карты при Z-переходе (эффект погружения) */ .fg-ghost-layer { position: absolute; @@ -678,20 +671,8 @@ border: 0; } -/* «Общая связь» (этот человек — и твой друг тоже): золотой ободок + ★ под аватаркой */ +/* «Общая связь» (этот человек — и твой друг тоже): золотой ободок. Значок ★ убран по запросу. */ .fg-node.is-common .node-dot { border-color: rgba(255, 214, 120, 0.95); box-shadow: 0 0 14px rgba(255, 200, 90, 0.4); } -.fg-node.is-common .node-dot::after { - content: '★'; - position: absolute; - bottom: -5px; - left: 50%; - transform: translateX(-50%); - font-size: 10px; - line-height: 1; - color: #ffd678; - text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7); - pointer-events: none; -}