diff --git a/VERSION.properties b/VERSION.properties
index ce9e532..f9049c3 100644
--- a/VERSION.properties
+++ b/VERSION.properties
@@ -1,2 +1,2 @@
-client.version=1.2.162
+client.version=1.2.163
server.version=1.2.144
diff --git a/shine-UI/assets/glass_overlay_faithful.png b/shine-UI/assets/glass_overlay_faithful.png
new file mode 100644
index 0000000..3111655
Binary files /dev/null and b/shine-UI/assets/glass_overlay_faithful.png differ
diff --git a/shine-UI/js/pages/network/force-graph.js b/shine-UI/js/pages/network/force-graph.js
index e9a995e..7f752e4 100644
--- a/shine-UI/js/pages/network/force-graph.js
+++ b/shine-UI/js/pages/network/force-graph.js
@@ -424,45 +424,37 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
return wrap;
}
- // SVG-«стеклянный орб» для аватара (фото в стеклянной сфере ≈90% + блик + rim + свечение).
- // Уникальные id на экземпляр (иначе defs конфликтуют). Тело стекла тёмно-синее/прозрачное (без серости),
- // вторичный блик убран (был «звёздочкой»). Фолбэк: если фото не загрузилось — остаются инициалы.
- let orbSeq = 0;
- function buildGlassOrb(src, opts) {
+ // Векторный SVG-орб (buildGlassOrb) ретайрнут 13.06 — все орбы рисует buildPngOrb (PNG-оверлей).
+
+ // A/B-вариант (ветка glass-png-overlay): орб = фото + запечённый стеклянный PNG поверх.
+ // Слой 1 — фото круглой маской ~78% от бокса оверлея (сидит внутри кромки); слой 2 — glass_overlay.png
+ // на весь бокс (альфа уже в PNG). Кодовый glow не рисуем — у картинки своё свечение запечено (нет двойного).
+ const GLASS_OVERLAY_SRC = '/assets/glass_overlay_faithful.png';
+ function buildPngOrb(src, opts) {
const o = opts || {};
- const u = 'o' + (orbSeq += 1);
- const glowOp = o.isFocus ? 0.34 : 0.28;
- const glowSpread = o.isFocus ? 7 : 4.5; // центр — шире/мягче ореол (рычаг 1); спутники без изменений
- const imgFilter = o.isFocus ? 'grayscale(0.9) contrast(1.04)' : 'saturate(0.85) brightness(0.97)';
- const init = String(o.initials || '').slice(0, 2);
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.setAttribute('viewBox', '0 0 100 100');
- svg.setAttribute('class', 'fg-orb-svg');
- svg.setAttribute('aria-hidden', 'true');
- svg.innerHTML = ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + ''
- + (init ? '' + init + '' : '')
- + (src ? '' : '')
- + ''
- + ''
- + ''
- + ''
- + ''
- + '';
- const im = svg.querySelector('image');
- if (im) im.addEventListener('error', () => { try { im.remove(); } catch (e) { /* останутся инициалы */ } });
- return svg;
+ const wrap = document.createElement('div');
+ wrap.className = 'fg-pngorb';
+ function makeInit() {
+ const d = document.createElement('div');
+ d.className = 'fg-pngorb-photo fg-pngorb-init';
+ d.textContent = String(o.initials || '').slice(0, 2);
+ return d;
+ }
+ let photo;
+ if (src) {
+ photo = document.createElement('img');
+ photo.className = 'fg-pngorb-photo';
+ photo.src = src; photo.alt = '';
+ photo.addEventListener('error', () => { try { photo.replaceWith(makeInit()); } catch (e) { /* fallback */ } });
+ } else {
+ photo = makeInit();
+ }
+ const glass = document.createElement('img');
+ glass.className = 'fg-pngorb-glass';
+ glass.src = GLASS_OVERLAY_SRC; glass.alt = '';
+ glass.setAttribute('aria-hidden', 'true');
+ wrap.append(photo, glass);
+ return wrap;
}
function buildNodeElement(src, isFocus, tier, dotOnly = false) {
@@ -499,7 +491,8 @@ export function createForceGraph({ stage, model, onCenterTap, onNodeTap, onNodeL
const initials = buildAvatarInitials({ login: src.login || src.name || String(src.id), firstName: src.name || '' });
const dot = document.createElement('div');
dot.className = 'avatar node-dot fg-orb-host';
- dot.appendChild(buildGlassOrb(photoSrc, { isFocus, initials }));
+ // Единый PNG-оверлей на ВСЕХ полных орбах (фокус + спутники). tier-3 точки (dotOnly) сюда не идут.
+ dot.appendChild(buildPngOrb(photoSrc, { isFocus, initials }));
el.append(dot);
// Бейдж-счётчик числа связей (заполняется в updateBadges по degreeById). Скрыт, пока 0.
diff --git a/shine-UI/styles/network-graph.css b/shine-UI/styles/network-graph.css
index 14fb356..b62c40d 100644
--- a/shine-UI/styles/network-graph.css
+++ b/shine-UI/styles/network-graph.css
@@ -137,15 +137,45 @@
overflow: visible; /* не срезать внешнее свечение орба */
box-shadow: none;
}
-.fg-orb-svg {
+/* A/B PNG-оверлей орба (ветка glass-png-overlay): фото снизу + запечённый стеклянный PNG сверху.
+ Бокс = 119% от .node-dot (как .fg-orb-svg → сфера ≈ кромке node-dot, контакт линий от ORB_R сохраняется). */
+/* Специфичность `.fg-orb-host …` бьёт глобальное `.node-dot img` (иначе оно гасит opacity→0
+ и форсит размер 100%). Поэтому opacity/размеры/радиус задаём здесь явно. */
+/* Кромку даёт сам glass_overlay.png → убираем остаточный border .node-dot (синее кольцо
+ старого векторного орба). Только у PNG-хоста; вектор и свечение/box-shadow не трогаем. */
+.fg-orb-host:has(.fg-pngorb) {
+ border: none;
+}
+.fg-orb-host .fg-pngorb {
position: absolute;
- width: 119%;
- height: 119%;
- left: 50%;
- top: 50%;
+ left: 50%; top: 50%;
+ width: 119%; height: 119%;
transform: translate(-50%, -50%);
+}
+.fg-orb-host .fg-pngorb-glass {
+ position: absolute;
+ left: 50%; top: 50%;
+ width: 100%; height: 100%;
+ transform: translate(-50%, -50%);
+ opacity: 1;
+ border-radius: 0;
+ object-fit: contain;
display: block;
- overflow: visible;
+ pointer-events: none;
+}
+.fg-orb-host .fg-pngorb-photo {
+ position: absolute;
+ left: 50%; top: 50%;
+ width: 78%; height: 78%; /* ~78% от бокса оверлея — сидит внутри стеклянной кромки */
+ transform: translate(-50%, -50%);
+ opacity: 1;
+ border-radius: 50%;
+ object-fit: cover;
+ display: block;
+}
+.fg-orb-host .fg-pngorb-init {
+ display: flex; align-items: center; justify-content: center;
+ background: #26344a; color: #cfe0ff; font-weight: 600; font-size: 20px;
}
.fg-node.is-family .node-dot {