diff --git a/VERSION.properties b/VERSION.properties index 5f66ce5..f5d162c 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.135 +client.version=1.2.141 server.version=1.2.127 diff --git a/shine-UI/Dev_Docs/features/interactive-network-graph.md b/shine-UI/Dev_Docs/features/interactive-network-graph.md new file mode 100644 index 0000000..972a472 --- /dev/null +++ b/shine-UI/Dev_Docs/features/interactive-network-graph.md @@ -0,0 +1,95 @@ +# Интерактивная карта связей (force-directed graph) + +Экран **«Связи»** (`network-view`) — интерактивная нод-граф карта вместо статичного списка: +фокусный пользователь в центре, связи на орбите, навигация тапом/свайпом, премиальные +переходы в духе нативного iOS. + +## Где код +- `js/pages/network/force-graph.js` — **движок** (физика, рендер, жизненный цикл узлов, жесты). +- `js/pages/network/adapter.js` — реальные данные → нейтральная модель движка. +- `js/pages/network/node-menu.js` — общее контекстное меню узла. +- `js/pages/network/lab.js` — лаборатория (`network-view/lab`) на мок-данных, без бэкенда. +- `js/pages/network-view.js` — страница: шапка, поиск, фильтры, история, склейка с движком. +- `js/mock-data.js` — `networkGraphUsers` (связанный мульти-граф из 10 человек для лаборатории). +- `styles/network-graph.css` — все стили `.fg-*`. + +## Данные (read-only, сервер не трогаем) +Единый источник — `authService.getUserConnectionsGraph(login)` (один запрос: логин → прямые связи). +`network-view.js` → `buildGraphModel()` нормализует роли (parent/child/sibling/spouse/friend/contact), +направление и метки; `adapter.engineModelFromGraphModel()` превращает это в модель движка: +`{ focusId, nodes:[{ id, login, name, avatar, relationType, strength, shining, tier }] }`. + +## Модель движка и API +`createForceGraph({ stage, model, onNodeTap, onCenterTap, onNodeLongPress })` → +`{ setModel(model), setFilter(pred), recenter(id), getFocusNode(), destroy() }`. + +## Ключевые механики +- **Diffing-переходы (непрерывность состояний):** при смене фокуса общие узлы (тот же `id`) не + пересоздаются, а перелетают на новые места; новые «расцветают» (bloom) каскадом из центра; + исчезнувшие уходят в Ghost-слой. +- **CSS-bloom (разлёт без тряски):** разлёт/перелёт узлов делают нативные CSS-переходы на + `transform` (компоновщик, `cubic-bezier(0.16,1,0.3,1)`, `BLOOM_MS` со ступенчатой задержкой + `order × 40мс`), а НЕ JS-физика. Работает даже при троттлинге rAF; цикл лишь ведёт лучи за узлами + (`syncPositionsFromDOM`). Завершение — гарантированно по таймеру (`endCssBloom`). +- **Ghost-слой:** снимок только **аватарок** старого графа (без линий — иначе старые связи висят + «ошмётками»). Полноэкранный overlay, застывает на месте, `scale 1→0.7` + `opacity 0.5→0` за + **1000мс**, затем удаляется (мягкий породистый шлейф истории). +- **Прорастание линий (Edge Growth):** новая линия тянется к ФИНАЛЬНОЙ точке узла и раскрывается + `stroke-dasharray`(=длина пути) + `stroke-dashoffset`(длина→0), синхронно с разлётом узла + (`growP = текущая дистанция / финальная`) → кончик «вытягивается» из центра вслед за аватаркой. + Старые линии при этом исчезают мгновенно. Только для новых узлов; переезжающие — линия следует за ними. +- **Физика (только до-settle):** после CSS-разлёта — лёгкая радиальная пружина + отталкивание для + органичного покачивания; после фильтра физика НЕ включается (фиксация на равномерных углах). +- **Жёсткая заморозка (kill-switch):** когда сумма |vx|+|vy| < 0.03 — скорости обнуляются, координаты + округляются, `cancelAnimationFrame` (sleep). Нет «треска», батарея не страдает. +- **Обычные линии:** SVG ` Q` (квадратичные Безье) — тонкие матовые дуги (`stroke-width ~1.0–1.2`), + градиент с глубоким уходом в прозрачность: неон-центр `0.42` → цвет роли `0.07` у аватарки (растворяются + в фоне, не спорят с сияющими). Изгиб реагирует на скорость. +- **Сияющие связи (двухслойный «световод», Neon Layering):** два пути на одну связь — широкий размытый + GLOW (`stroke-width 4`, неон, `filter: blur(2px)`, `opacity 0.4`) + тонкий чёткий CORE (`1.5px`, + `#e0f7fc`). Линия остаётся изящной, но обретает объёмное OLED-свечение; растут оба синхронно (общий + dashoffset). Никаких бегущих импульсов. +- **Жесты:** свайп-pan с инерцией (новое касание прерывает); короткий тап — центрирование + нижний + сниппет; долгий тап — контекстное меню (в `#modal-root`, позиция по `getBoundingClientRect`); тап + по центру — профиль. Нажатия на чипы фильтров гасят `pointerdown` (stopPropagation), чтобы сцена не + перехватила указатель (`setPointerCapture`) и не «съела» click кнопки. +- **Фильтры слоёв (Все / Семья / Друзья / Сияющие):** CSS-переходы 300мс — несоответствующие узлы и их + линии гаснут НА МЕСТЕ (`opacity 0` + `scale 0.8`), оставшиеся плавно переплывают на равномерные углы, + затем жёсткая фиксация без физики (ноль тряски, мгновенный sleep). +- **Живой фон (Nebula):** под центром — глубокое размытое сине-голубое облако (`.fg-stage::before`, + `blur 80→96px`), бесконечная анимация 7с: «дышит» радиусом/яркостью и переливается индиго↔ультрамарин + (`hue-rotate`). На компоновщике (GPU), не будит rAF; подписи имён остаются контрастными. +- **Стеклянные чипы фильтров (frosted glass):** `background: rgba(255,255,255,0.03)`, + `backdrop-filter: blur(12px)`, граница `0.5px solid rgba(255,255,255,0.1)`; активный — подсвечен сине-голубым. +- **Поллиш:** «прицел» в центре (+пульс при захвате фокуса); «дыхание» фокуса (бесконечная CSS-анимация + размера, GPU, не будит rAF); свечение «сияющих» узлов — мягкая медленная пульсация (3.6с) многослойной + `box-shadow` + размытый ореол через SVG-фильтр `#fg-shine-glow`; тестовые фото-аватарки (`NETWORK_PHOTOS`); + хард-лимит ~90 DOM-аватарок (остальное — SVG-точки). + +## Параметры тюнинга (константы в начале `force-graph.js`) +| Константа | Значение | Назначение | +|---|---|---| +| `ORBIT_MIN / ORBIT_MAX` | 150 / 240 | радиус орбиты (защитный отступ от центра — подписи не наезжают) | +| `K_RADIAL` | 0.035 | жёсткость орбитальной пружины (мягко) | +| `K_FOCUS` | 0.12 | жёсткость пружины фокуса к центру | +| `CHARGE` / `CHARGE_START_FACTOR` | 1400 / 0.45 | отталкивание (на старте ослаблено) | +| `FRICTION` / `FRICTION_BOOST` / `BOOST_FRAMES` | 0.80 / 0.94 / 42 | базовое трение / стартовая вязкость / длительность (~700мс) | +| `BLOOM_MS` / `BLOOM_STAGGER` | 900 / 40 | длительность CSS-разлёта / задержка между узлами (каскад) | +| `SLEEP_V` | 0.03 | порог суммарной |v| для заморозки | +| `FOCUS_SCALE` | 1.5 | базовый масштаб фокуса | +| `MAX_FULL_NODES` | 90 | хард-лимит полных аватарок (далее — точки) | + +Прочее (вшито в код): Ghost-слой — 1000мс; CSS-переход фильтра — 300мс; пульсация сияния — 3.6с; +прорастание линий привязано к прогрессу разлёта узла (а не к отдельному таймеру). + +## Локальный запуск / проверка +- Dev-сервер: `.claude/shine-ui-dev-server.cjs` (Node, порт 7321, SPA-fallback + инжект ``). +- Лаборатория (без бэкенда): `http://localhost:7321/network-view/lab` — мок `networkGraphUsers`, + тап по узлам переключает сети. +- Реальный путь (`/network-view`) требует живого `wss://shineup.me/ws` (локально — `ws_open_error`, это норма). + +## Ограничения / на будущее +- Многоуровневая глубина (друзья друзей мельче, 3-й уровень — точки), кластеры, «общие связи» + упираются в API (отдаёт только прямые связи) — требуют доработки сервера. +- Превью в простое троттлит `requestAnimationFrame` (физика не идёт между вызовами) — для замеров + прокачивать кадры; в активном табе всё работает на 60 FPS. diff --git a/shine-UI/index.html b/shine-UI/index.html index 5d1a017..9c7f792 100644 --- a/shine-UI/index.html +++ b/shine-UI/index.html @@ -12,7 +12,7 @@