Связи: финальный вид сияющих связей

Перенести финальный плазменный рендер связей из коммита Pixel 2559f1e6 в main.
This commit is contained in:
AidarKC
2026-06-10 19:00:22 +04:00
parent 9ca469a075
commit e3061b46f9
5 changed files with 211 additions and 37 deletions
+26 -22
View File
@@ -71,8 +71,11 @@ function ensureShineFilter() {
svg.setAttribute('width', '0');
svg.setAttribute('height', '0');
svg.style.position = 'absolute';
svg.innerHTML = '<defs><filter id="fg-shine-glow" x="-120%" y="-120%" width="340%" height="340%" '
+ 'color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="3.4"/></filter></defs>';
svg.innerHTML = '<defs>'
+ '<filter id="fg-shine-glow" x="-120%" y="-120%" width="340%" height="340%" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="3.4"/></filter>'
+ '<filter id="fg-plasma-blur6" x="-100%" y="-200%" width="300%" height="500%" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="6"/></filter>'
+ '<filter id="fg-plasma-blur2" x="-60%" y="-120%" width="220%" height="340%" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="2"/></filter>'
+ '</defs>';
document.body.appendChild(svg);
}
@@ -388,9 +391,8 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
const cpx = 2 * desX - mx; // CP так, чтобы середина Q-кривой попала в desired
const cpy = 2 * desY - my;
// Связь рисуем по статусу узла:
// • обычная — одна тонкая (1.0–1.2px) матовая дуга, градиент с ГЛУБОКИМ уходом в прозрачность;
// • СИЯЮЩАЯ — двухслойный неоновый «световод»: широкий размытый glow (под) + тонкий чёткий
// core 1.5px (над) → изящно, но с объёмным OLED-свечением (см. .fg-edge-glow / .fg-edge-core).
// • обычная — мягкое цветное свечение по типу связи;
// • сияющая — плазма из трёх слоёв на одном S-пути (flare/tube/core).
const shine = Boolean(n.shining) && !n.hidden;
const d = `M${x1.toFixed(1)} ${y1.toFixed(1)} Q${cpx.toFixed(1)} ${cpy.toFixed(1)} ${x2.toFixed(1)} ${y2.toFixed(1)}`;
// ПРОРАСТАНИЕ из центра: dasharray = длина пути, dashoffset от длины к 0 по мере разлёта узла
@@ -404,24 +406,26 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
dashAttr = ` stroke-dasharray="${L.toFixed(1)}" stroke-dashoffset="${(L * (1 - growP)).toFixed(1)}"`;
}
if (shine) {
// glow (размытый, приглушённый) + core (тонкий, чёткий) — растут синхронно (общий dashAttr)
const gOp = (0.4 * nodeOpacity).toFixed(2);
const cOpAttr = nodeOpacity < 0.995 ? ` opacity="${nodeOpacity.toFixed(2)}"` : '';
parts.push(`<path class="fg-edge-glow" d="${d}"${dashAttr} opacity="${gOp}" />`);
parts.push(`<path class="fg-edge-core" d="${d}"${dashAttr}${cOpAttr} />`);
const pnx = -uy;
const pny = ux;
const amp = Math.min(13, 5 + segLen0 * 0.05);
const bowX = desX - mx;
const bowY = desY - my;
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)}`;
parts.push(`<path class="fg-plasma-flare" d="${dS}"${dashAttr} opacity="${(0.42 * nodeOpacity).toFixed(3)}" />`);
parts.push(`<path class="fg-plasma-tube" d="${dS}"${dashAttr} opacity="${(0.85 * nodeOpacity).toFixed(3)}" />`);
parts.push(`<path class="fg-plasma-core" d="${dS}"${dashAttr} opacity="${nodeOpacity.toFixed(3)}" />`);
} else {
// обычная: тонкая дуга, градиент 0.42 (центр) → 0.07 (аватарка) — глубокий уход в прозрачность,
// чтобы матовые связи не спорили с сияющими.
const gid = `fg-grad-${gi}`;
gi += 1;
defs.push(
`<linearGradient id="${gid}" gradientUnits="userSpaceOnUse" x1="${x1.toFixed(1)}" y1="${y1.toFixed(1)}" x2="${x2.toFixed(1)}" y2="${y2.toFixed(1)}">`
+ `<stop offset="0" stop-color="${FOCUS_NEON}" stop-opacity="0.42"/>`
+ `<stop offset="1" stop-color="${relationColor(n.relationType)}" stop-opacity="0.07"/></linearGradient>`
);
const sw = (1.0 + n.strength * 0.2).toFixed(2); // 1.01.2px
const op = nodeOpacity < 0.995 ? ` opacity="${nodeOpacity.toFixed(2)}"` : '';
parts.push(`<path d="${d}" fill="none" stroke="url(#${gid})" stroke-width="${sw}" stroke-linecap="round"${op}${dashAttr} />`);
const col = relationColor(n.relationType);
const haloOp = (0.22 * nodeOpacity).toFixed(2);
const coreOp = (0.7 * nodeOpacity).toFixed(2);
const sw = (2.6 + n.strength * 1.4).toFixed(2);
parts.push(`<path d="${d}" fill="none" stroke="${col}" stroke-width="${sw}" stroke-linecap="round" opacity="${haloOp}"${dashAttr} />`);
parts.push(`<path d="${d}" fill="none" stroke="${col}" stroke-width="1.2" stroke-linecap="round" opacity="${coreOp}"${dashAttr} />`);
}
}
edgesSvg.innerHTML = `<defs>${defs.join('')}</defs>${parts.join('')}`;