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 {