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;
-}