From 2559f1e66b8367552499289ced17a87589befcce386752db2dd2c9d9f5751692 Mon Sep 17 00:00:00 2001 From: Pixel Date: Wed, 10 Jun 2026 16:09:33 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B2=D1=8F=D0=B7=D0=B8:=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B2=D0=B8=D0=B4?= =?UTF-8?q?=20=D1=81=D0=B8=D1=8F=D1=8E=D1=89=D0=B8=D1=85=20=D1=81=D0=B2?= =?UTF-8?q?=D1=8F=D0=B7=D0=B5=D0=B9=20=E2=80=94=20=D0=BF=D0=BB=D0=B0=D0=B7?= =?UTF-8?q?=D0=BC=D0=B0=20(=D0=BF=D0=BE=D0=BB=D0=B5+=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=B1=D0=BA=D0=B0+=D1=8F=D0=B4=D1=80=D0=BE,=20screen-blend)=20?= =?UTF-8?q?+=20=D1=86=D0=B2=D0=B5=D1=82=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8?= =?UTF-8?q?=20=D1=83=20=D0=BE=D0=B1=D1=8B=D1=87=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Промежуточные итерации линий схлопнуты в один коммит. Убран мёртвый фильтр fg-plasma-turb. Лаборатория (/network-view/lab) и автотесты сохранены. Версия 1.2.158. Co-Authored-By: Claude Opus 4.8 (1M context) --- VERSION.properties | 4 +- .../features/interactive-network-graph.md | 16 ++++++ shine-UI/js/pages/network/force-graph.js | 53 +++++++++++-------- shine-UI/js/pages/network/selftest.js | 11 ++++ shine-UI/styles/network-graph.css | 49 +++++++++-------- 5 files changed, 88 insertions(+), 45 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index 30ea2ee..985cb9b 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.150 -server.version=1.2.134 +client.version=1.2.158 +server.version=1.2.142 diff --git a/shine-UI/Dev_Docs/features/interactive-network-graph.md b/shine-UI/Dev_Docs/features/interactive-network-graph.md index 2a239ce..41d2717 100644 --- a/shine-UI/Dev_Docs/features/interactive-network-graph.md +++ b/shine-UI/Dev_Docs/features/interactive-network-graph.md @@ -145,6 +145,22 @@ - **Бейдж числа связей** — `.fg-node-badge` (число из `degreeById`, обновляется в `updateBadges`). - **Цветовые кластеры** — мягкая аура узла по типу связи (CSS `is-family/friend/business/contact`). +**Линии-«жгуты» (партия 4, по референсу — плазменный композитинг):** +- **Сияющие** — ОДИН центральный S-путь (cubic Bézier) + ТРИ наложенных слоя с ОДИНАКОВЫМ `d` (объём + из толщины+размытия, НЕ из геометрии — никаких расходящихся линий): + Настоящий НЕОН (видимый ореол вокруг яркого ядра; поле/трубка в `mix-blend-mode: screen` — свет + складывается аддитивно с тёмным фоном, а в пересечениях у центра ярче — энергохаб): + - `.fg-plasma-flare` — плазменное облако: 16px, `#00bfff`, opacity 0.42, **`feGaussianBlur` stdDev=6**, screen (+ «дыхание» 3.6с); + - `.fg-plasma-tube` — направляющий свет: 6px, `#00e5ff`, opacity 0.85, **`feGaussianBlur` stdDev=2**, screen; + - `.fg-plasma-core` — ядро: 2px, `#dffaff` (светло-голубо-белое), opacity 1, без размытия. + Толщина/насыщенность подогнаны под референс (толстая яркая голубая плазма, гладкие края). + S-волна спокойная/изящная (amp до 13px). Размытие — именно SVG-фильтры (`#fg-plasma-blur6/2`), т.к. + CSS-`filter` на `` в части мобильных WebView не применяется (отсюда был «плоский»/«канатный» вид). + ⚠️ Это НЕ Canvas-движок (не библиотека force-graph): связи — реальные SVG ``, фильтры применяются. + Прозрачность слоёв inline (× spotlight/глубину). Тяжёлый blur только у сияющих (их мало) — перф. +- **Не-сияущие** — мягкое свечение **в цвете связи** (семья/друзья/бизнес/контакт): широкая + полупрозрачная подложка + тонкое ядро, без SVG-blur (дёшево). «Похоже, но тише». + **Фишки (партия 3, лаборатория):** - **Общие связи** — среди друзей человека один помечен как «общий» (он и твой друг тоже): золотой ободок + ★ (CSS `is-common`; в лаб-генерации `addDeepLevels` подставляет узнаваемого друга Ивана). diff --git a/shine-UI/js/pages/network/force-graph.js b/shine-UI/js/pages/network/force-graph.js index 77d0192..0f4dada 100644 --- a/shine-UI/js/pages/network/force-graph.js +++ b/shine-UI/js/pages/network/force-graph.js @@ -109,8 +109,13 @@ function ensureShineFilter() { svg.setAttribute('width', '0'); svg.setAttribute('height', '0'); svg.style.position = 'absolute'; - svg.innerHTML = ''; + // + SVG-фильтры размытия для плазменных линий (feGaussianBlur надёжнее CSS-filter на в WebView). + // Широкий регион фильтра (userSpaceOnUse-подобный запас в %), чтобы размытие не срезалось по bbox пути. + svg.innerHTML = '' + + '' + + '' + + '' + + ''; document.body.appendChild(svg); } @@ -773,27 +778,33 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL // 2-й уровень: матовая паутинка, проявляется по мере раскрытия родителя (локальный bloom) if (pe > 0.02) parts.push(``); } else if (shine || n.track || onPath) { - // glow (размытый, приглушённый) + core (тонкий, чёткий). Используется для сияющих, «трека - // прохождения» (n.track) И нити-крошки пути погружения (onPath) — путь назад к Ивану горит ярко. - const cOpVal = nodeOpacity * sp; - const gOp = (0.4 * cOpVal).toFixed(2); - const cOpAttr = cOpVal < 0.995 ? ` opacity="${cOpVal.toFixed(2)}"` : ''; - parts.push(``); - parts.push(``); + // СИЯЮЩАЯ связь — плазменный композитинг: ОДИН центральный S-путь (cubic) + ТРИ наложенных слоя + // с ОДИНАКОВЫМ d (объём из толщины+blur, не из геометрии). Слои: широкое поле / трубка / ядро. + const pnx = -uy; + const pny = ux; // перпендикуляр к хорде + const amp = Math.min(13, 5 + segLen0 * 0.05); // амплитуда S — спокойная изящная волна (∝ длине) + const bowX = desX - mx; + const bowY = desY - my; // вектор изящной дуги + пан-стретч — сохраняем + // единственная S-кривая: контрольная точка на 1/3 смещена +amp, на 2/3 — −amp (плавная волна) + const c1x = x1 + (x2 - x1) / 3 + bowX + pnx * amp; const c1y = y1 + (y2 - y1) / 3 + bowY + pny * amp; + const c2x = x1 + 2 * (x2 - x1) / 3 + bowX - pnx * amp; const c2y = y1 + 2 * (y2 - y1) / 3 + bowY - pny * amp; + const dS = `M${x1.toFixed(1)} ${y1.toFixed(1)} C${c1x.toFixed(1)} ${c1y.toFixed(1)} ${c2x.toFixed(1)} ${c2y.toFixed(1)} ${x2.toFixed(1)} ${y2.toFixed(1)}`; + const base = nodeOpacity * sp; // затемнение от spotlight/глубины + // 3 слоя на ОДНОМ пути dS. Видимый НЕОН: яркое ядро + чёткий голубой ореол (поле+трубка в режиме + // screen — свет складывается аддитивно с фоном). Прозрачности подняты, чтобы не было «белого каната». + parts.push(``); + parts.push(``); + parts.push(``); } else { - // обычная: тонкая дуга, градиент 0.42 (центр) → 0.07 (аватарка) — глубокий уход в прозрачность, - // чтобы матовые связи не спорили с сияющими. - const gid = `fg-grad-${gi}`; - gi += 1; - defs.push( - `` - + `` - + `` - ); - const sw = (1.0 + n.strength * 0.2).toFixed(2); // 1.0–1.2px + // ОБЫЧНАЯ связь — мягкий светящийся жгут В ЦВЕТЕ СВЯЗИ (семья тёплый / друзья синий / + // бизнес фиолет): тоньше и тише сияющих. Без SVG-blur (дёшево): широкая подложка + тонкое ядро. + const col = relationColor(n.relationType); const opVal = nodeOpacity * sp; - const op = opVal < 0.995 ? ` opacity="${opVal.toFixed(2)}"` : ''; - parts.push(``); + const haloOp = (0.22 * opVal).toFixed(2); + const coreOp = (0.7 * opVal).toFixed(2); + const sw = (2.6 + n.strength * 1.4).toFixed(2); // мягкая подложка-«свечение» в цвете связи + parts.push(``); + parts.push(``); } } edgesSvg.innerHTML = `${defs.join('')}${parts.join('')}`; diff --git a/shine-UI/js/pages/network/selftest.js b/shine-UI/js/pages/network/selftest.js index 388eb49..4c0dd22 100644 --- a/shine-UI/js/pages/network/selftest.js +++ b/shine-UI/js/pages/network/selftest.js @@ -124,6 +124,17 @@ export async function runNetworkSelfTest(graph, deepChipEl) { check('E2 выход: зум вернулся (~1)', Math.abs(s.zoom - 1) < 0.05, `zoom=${s.zoom}`); check('E3 выход: погружение снято', s.diveTargetId === null, `dive=${s.diveTargetId}`); + // === Тест K: сияющие линии — плазма из 3 слоёв на ОДНОМ S-пути (одинаковый d) === + if (typeof document !== 'undefined') { + const flare = document.querySelectorAll('.fg-plasma-flare'); + const tube = document.querySelectorAll('.fg-plasma-tube'); + const core = document.querySelectorAll('.fg-plasma-core'); + const equalLayers = flare.length >= 1 && flare.length === tube.length && tube.length === core.length; + const sameD = flare[0] && flare[0].getAttribute('d') === tube[0].getAttribute('d') + && tube[0].getAttribute('d') === core[0].getAttribute('d'); + check('K1 плазма: 3 слоя на ОДНОМ S-пути', equalLayers && !!sameD, `поле:${flare.length} трубка:${tube.length} ядро:${core.length} sameD:${!!sameD}`); + } + return finish(results); } diff --git a/shine-UI/styles/network-graph.css b/shine-UI/styles/network-graph.css index 0912e2a..5350608 100644 --- a/shine-UI/styles/network-graph.css +++ b/shine-UI/styles/network-graph.css @@ -60,38 +60,43 @@ transition: opacity 420ms ease; /* плавное появление линий при перестройке */ } -/* Сияющая связь = двухслойный неоновый «световод» (Neon Layering): изящно, но объёмно (как OLED). - GLOW — широкий размытый ореол неонового оттенка под линией; CORE — тонкий чёткий светлый контур. */ -.fg-edge-glow { +/* Сияющая связь = плазменный композитинг (3 слоя на ОДНОМ S-пути, см. renderEdges). + Настоящий НЕОН: яркое светлое ядро + ВИДИМЫЙ голубой ореол вокруг. Слои поля/трубки идут в режиме + mix-blend-mode: screen → свет складывается аддитивно с тёмным фоном (как реальное свечение), а в точках + пересечения нитей у центра — ярче (энергетический хаб). Прозрачность слоёв — inline (×spotlight/глубину). */ +.fg-plasma-flare { /* нижний: широкое насыщенное голубое плазменное свечение (по референсу) */ fill: none; - stroke: rgba(110, 225, 255, 1); - stroke-width: 4; + stroke: #00bfff; /* глубокий голубой */ + stroke-width: 16; stroke-linecap: round; - filter: blur(2px); /* мягкое объёмное свечение вокруг нити */ - /* синхро-пульс: нить «дышит» толщиной/размытием в том же ритме (3.6с), что и ободок сияющего узла — - в покое SVG не перерисовывается, поэтому все нити стартуют синхронно и пульсируют вместе. */ - animation: fg-edge-pulse 3.6s ease-in-out infinite; + filter: url(#fg-plasma-blur6); /* мягкое объёмное свечение (гладкие края — как на референсе) */ + mix-blend-mode: screen; /* аддитивное свечение поверх тёмного фона */ + /* синхро-«дыхание» поля толщиной в такт ободку сияющего узла (3.6с); прозрачность не трогаем (inline) */ + animation: fg-plasma-breath 3.6s ease-in-out infinite; } -.fg-edge-core { +.fg-plasma-tube { /* средний: яркая толстая неоновая трубка */ fill: none; - stroke: #e0f7fc; /* ультра-светлый голубой — чёткий контур луча */ - stroke-width: 1.5; + stroke: #00e5ff; /* яркий циан */ + stroke-width: 6; + stroke-linecap: round; + filter: url(#fg-plasma-blur2); /* SVG feGaussianBlur stdDeviation=2 */ + mix-blend-mode: screen; /* аддитивное свечение */ +} +.fg-plasma-core { /* верхний: яркое чёткое ядро (светло-голубо-белое) */ + fill: none; + stroke: #dffaff; /* светло-голубо-белое — «жидкое» ядро */ + stroke-width: 2; stroke-linecap: round; - animation: fg-edge-core-pulse 3.6s ease-in-out infinite; } -/* пульс «световода» в такт дыханию сияющего ободка (та же длительность 3.6с) */ -@keyframes fg-edge-pulse { - 0%, 100% { stroke-width: 3.4; filter: blur(1.6px); } - 50% { stroke-width: 5.2; filter: blur(2.8px); } -} -@keyframes fg-edge-core-pulse { - 0%, 100% { stroke-width: 1.3; } - 50% { stroke-width: 1.9; } +/* мягкое «дыхание» плазменного облака толщиной, синхронно с пульсом сияющего ободка (3.6с) */ +@keyframes fg-plasma-breath { + 0%, 100% { stroke-width: 14; } + 50% { stroke-width: 19; } } @media (prefers-reduced-motion: reduce) { - .fg-edge-glow, .fg-edge-core { animation: none; } + .fg-plasma-flare { animation: none; } } .fg-node {