SHA256
Связи: двухслойные линии-световоды, живой фон и стеклянные фильтры
- Сияющие связи — двухслойный неоновый «световод»: размытый glow (4px, blur 2px, opacity 0.4) + тонкий чёткий core (1.5px, #e0f7fc). Объёмное OLED-свечение, линия остаётся изящной. Оба слоя растут синхронно (общий dashoffset). - Обычные линии — тоньше (1.0–1.2px) и глубокий уход в прозрачность (0.42 → 0.07), чтобы матовые связи не спорили с сияющими. - Живой фон-«небула»: глубокое размытое сине-голубое облако под центром, медленная пульсация радиуса/яркости + переливы индиго↔ультрамарин (hue-rotate, 7с). - Стеклянные чипы фильтров (frosted glass): rgba(255,255,255,0.03) + backdrop blur(12px) + граница 0.5px solid rgba(255,255,255,0.1); активный подсвечен сине-голубым. - Бамп client.version → 1.2.138; документация фичи обновлена. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3e4759a0c9
commit
41edd1423c
@@ -53,8 +53,6 @@ const RELATION_COLORS = {
|
||||
// Неоновый цвет центра — из него «вытекает» градиент каждой связи к цвету периферийного узла.
|
||||
const FOCUS_NEON = 'rgba(140, 240, 255, 0.95)';
|
||||
|
||||
// Яркий неон сияния — в него уходит градиент связи к «сияющему» узлу (совпадает со свечением аватарки).
|
||||
const SHINE_EDGE_NEON = 'rgba(150, 245, 255, 0.95)';
|
||||
|
||||
function easeOutCubic(t) {
|
||||
const x = 1 - t;
|
||||
@@ -416,26 +414,14 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
const desY = my + ux * baseBow + invY * lag;
|
||||
const cpx = 2 * desX - mx; // CP так, чтобы середина Q-кривой попала в desired
|
||||
const cpy = 2 * desY - my;
|
||||
// ТОНКАЯ изящная дуга: одинарная квадратичная кривая Безье, лёгкий градиентный штрих.
|
||||
// Обычная связь — неон у центра → цвет роли у узла. Связь к «СИЯЮЩЕМУ» — ярче, уходит в
|
||||
// неон сияния и МОНОЛИТНО светится (статичный drop-shadow через класс .fg-edge-shine).
|
||||
// Связь рисуем по статусу узла:
|
||||
// • обычная — одна тонкая (1.0–1.2px) матовая дуга, градиент с ГЛУБОКИМ уходом в прозрачность;
|
||||
// • СИЯЮЩАЯ — двухслойный неоновый «световод»: широкий размытый glow (под) + тонкий чёткий
|
||||
// core 1.5px (над) → изящно, но с объёмным OLED-свечением (см. .fg-edge-glow / .fg-edge-core).
|
||||
const shine = Boolean(n.shining) && !n.hidden;
|
||||
const gid = `fg-grad-${gi}`;
|
||||
gi += 1;
|
||||
const tipColor = shine ? SHINE_EDGE_NEON : relationColor(n.relationType);
|
||||
const baseStop = shine ? 0.85 : 0.5;
|
||||
const tipStop = shine ? 0.7 : 0.14;
|
||||
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="${baseStop}"/>`
|
||||
+ `<stop offset="1" stop-color="${tipColor}" stop-opacity="${tipStop}"/></linearGradient>`
|
||||
);
|
||||
const sw = (shine ? 1.7 + n.strength * 0.8 : 1.3 + n.strength * 0.9).toFixed(2); // тонко
|
||||
const d = `M${x1.toFixed(1)} ${y1.toFixed(1)} Q${cpx.toFixed(1)} ${cpy.toFixed(1)} ${x2.toFixed(1)} ${y2.toFixed(1)}`;
|
||||
// прозрачность луча = живая прозрачность узла (гаснет вместе с узлом при фильтре/уходе)
|
||||
const op = nodeOpacity < 0.995 ? ` opacity="${nodeOpacity.toFixed(2)}"` : '';
|
||||
// ПРОРАСТАНИЕ из центра: dasharray = длина пути, dashoffset уводим от длины к 0 по мере
|
||||
// разлёта (growP = доля пройденного узлом пути центр→орбита) → линия «вытягивается» из центра.
|
||||
// ПРОРАСТАНИЕ из центра: dasharray = длина пути, dashoffset от длины к 0 по мере разлёта узла
|
||||
// (growP = доля пройденного узлом пути центр→орбита) → линия «вытягивается» из центра.
|
||||
let dashAttr = '';
|
||||
if (growing) {
|
||||
const finalD = Math.hypot(n.bfx, n.bfy) || 1;
|
||||
@@ -444,8 +430,26 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
|
||||
const L = (Math.hypot(cpx - x1, cpy - y1) + Math.hypot(x2 - cpx, y2 - cpy) + Math.hypot(x2 - x1, y2 - y1)) / 2;
|
||||
dashAttr = ` stroke-dasharray="${L.toFixed(1)}" stroke-dashoffset="${(L * (1 - growP)).toFixed(1)}"`;
|
||||
}
|
||||
const cls = shine ? ' class="fg-edge-shine"' : ''; // монолитное неоновое свечение (drop-shadow)
|
||||
parts.push(`<path${cls} d="${d}" fill="none" stroke="url(#${gid})" stroke-width="${sw}" stroke-linecap="round"${op}${dashAttr} />`);
|
||||
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} />`);
|
||||
} 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.0–1.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} />`);
|
||||
}
|
||||
}
|
||||
edgesSvg.innerHTML = `<defs>${defs.join('')}</defs>${parts.join('')}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user