six-degrees 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +14 -14
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +20 -19
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +20 -19
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +16 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +24 -23
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +22 -21
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +22 -21
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +24 -23
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +24 -23
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +24 -23
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +24 -23
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +24 -23
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +24 -23
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +24 -23
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +24 -23
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +24 -23
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +18 -17
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0um1uey._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13dx4sd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__18bcnu4._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +2 -2
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +5 -5
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +9 -9
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0jdkqgi._.js → _1-658fq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +13 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +13 -0
- package/.next/standalone/.next/server/chunks/ssr/{_0gtv9jb._.js → app_components_CompanyScores_1g0mytz.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +14 -12
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -6
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +4 -4
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{lib_0i8v-s3._.js → lib_1c4ieuw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +22 -0
- package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
- package/.next/{static/chunks/2wgz9ietkqqe6.js → standalone/.next/static/chunks/0i02yxppzds1o.js} +1 -1
- package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
- package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +1 -0
- package/.next/standalone/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
- package/.next/{static/chunks/3sowbvbaj1s6n.js → standalone/.next/static/chunks/0ws7zy0w57ufi.js} +1 -1
- package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
- package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +34 -0
- package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
- package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
- package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
- package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +45 -0
- package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +1 -0
- package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +11 -0
- package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
- package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +1 -0
- package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
- package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +1 -0
- package/.next/standalone/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
- package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +11 -0
- package/.next/standalone/CHANGELOG.md +61 -0
- package/.next/standalone/README.md +22 -8
- package/.next/standalone/app/api/scraper/route.js +40 -3
- package/.next/standalone/app/api/update/route.js +12 -6
- package/.next/standalone/app/components/AppHeader.js +257 -0
- package/.next/standalone/app/components/Avatar.js +2 -1
- package/.next/standalone/app/components/ChainView.js +12 -12
- package/.next/standalone/app/components/FilterPanel.js +20 -12
- package/.next/standalone/app/components/ForceGraph.js +163 -25
- package/.next/standalone/app/components/GalaxyLab.js +43 -12
- package/.next/standalone/app/components/GridView.js +4 -4
- package/.next/standalone/app/components/LinkedInLimits.js +2 -1
- package/.next/standalone/app/components/ListView.js +3 -3
- package/.next/standalone/app/components/NoteDetail.js +2 -1
- package/.next/standalone/app/components/OnboardingGate.js +2 -2
- package/.next/standalone/app/components/OutlinkQuest.js +2 -1
- package/.next/standalone/app/components/PathsAnalyzer.js +3 -2
- package/.next/standalone/app/components/PeopleMap.js +2 -1
- package/.next/standalone/app/components/RingsView.js +3 -3
- package/.next/standalone/app/components/ScanTrail.js +85 -0
- package/.next/standalone/app/components/SeparationView.js +100 -77
- package/.next/standalone/app/components/Sidebar.js +4 -4
- package/.next/standalone/app/components/TheirCircle.js +2 -1
- package/.next/standalone/app/components/ThemeLoader.js +9 -0
- package/.next/standalone/app/components/UpdatePanel.js +20 -2
- package/.next/standalone/app/components/settings/AppearanceSection.js +226 -0
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +2 -1
- package/.next/standalone/app/components/ui.js +2 -1
- package/.next/standalone/app/globals.css +70 -2
- package/.next/standalone/app/import/page.js +3 -3
- package/.next/standalone/app/layout.js +11 -2
- package/.next/standalone/app/page.js +69 -215
- package/.next/standalone/app/paths/page.js +8 -21
- package/.next/standalone/app/profile/page.js +11 -10
- package/.next/standalone/app/queue/page.js +8 -21
- package/.next/standalone/app/scores/page.js +1 -1
- package/.next/standalone/app/settings/page.js +7 -3
- package/.next/standalone/app/setup/page.js +83 -88
- package/.next/standalone/app/social/Crm.js +2 -1
- package/.next/standalone/app/social/SocialHub.js +2 -2
- package/.next/standalone/app/social/page.js +1 -1
- package/.next/standalone/lib/data-import.js +1 -0
- package/.next/standalone/lib/galaxy-export.js +2 -1
- package/.next/standalone/lib/galaxy-lab.js +24 -8
- package/.next/standalone/lib/level.js +51 -0
- package/.next/standalone/lib/release.js +13 -7
- package/.next/standalone/lib/scanner-python.js +38 -8
- package/.next/standalone/lib/scraper-client.js +2 -1
- package/.next/standalone/lib/separation.js +4 -2
- package/.next/standalone/lib/theme-store.js +61 -0
- package/.next/standalone/lib/themes.js +186 -0
- package/.next/standalone/lib/tiers.js +2 -8
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +4 -4
- package/.next/standalone/scripts/build-desktop.mjs +325 -0
- package/.next/standalone/scripts/build-site.mjs +85 -15
- package/.next/standalone/scripts/gen-test-network.mjs +93 -0
- package/.next/standalone/scripts/next.mjs +20 -0
- package/.next/standalone/scripts/readme_buttons.py +3 -0
- package/.next/standalone/scripts/requirements.txt +18 -1
- package/.next/standalone/scripts/site-news.mjs +54 -9
- package/.next/standalone/scripts/smoke-desktop.mjs +177 -0
- package/.next/standalone/scripts/windows/six-degrees.iss +100 -0
- package/.next/static/chunks/02pq76u7y4v9-.js +22 -0
- package/.next/static/chunks/0cmq0exd4d0h9.js +1 -0
- package/.next/{standalone/.next/static/chunks/2wgz9ietkqqe6.js → static/chunks/0i02yxppzds1o.js} +1 -1
- package/.next/static/chunks/0m_p4k-q2qh--.js +1 -0
- package/.next/static/chunks/0oyslj62jkubd.js +1 -0
- package/.next/static/chunks/{1x_ysly42-teq.css → 0vo-l8hxneba6.css} +1 -1
- package/.next/{standalone/.next/static/chunks/3sowbvbaj1s6n.js → static/chunks/0ws7zy0w57ufi.js} +1 -1
- package/.next/static/chunks/0yh8z2tbrc5i_.js +1 -0
- package/.next/static/chunks/172e10mi-tpo_.js +34 -0
- package/.next/static/chunks/1ei5vbe2uw6fl.js +1 -0
- package/.next/static/chunks/1f7_pl_wq7lh3.js +11 -0
- package/.next/static/chunks/1jzbe7zdx9vbj.js +2 -0
- package/.next/static/chunks/1m3017gp790pw.js +45 -0
- package/.next/static/chunks/1t20e8u0st7ep.js +1 -0
- package/.next/static/chunks/2-ch9rm27eril.js +11 -0
- package/.next/static/chunks/2ldv-v3s9yaif.js +1 -0
- package/.next/static/chunks/3bp284gjf7dyq.js +1 -0
- package/.next/static/chunks/3k1f6clbn3gj3.js +7 -0
- package/.next/static/chunks/3l4t8je6iguzc.js +1 -0
- package/.next/static/chunks/{0muwt9bykzr-1.js → 3tplzuc9jk-tl.js} +1 -1
- package/.next/static/chunks/42m0bqr8pbovz.js +11 -0
- package/CHANGELOG.md +61 -0
- package/README.md +22 -8
- package/lib/data-import.js +1 -0
- package/lib/galaxy-export.js +2 -1
- package/lib/galaxy-lab.js +24 -8
- package/lib/level.js +51 -0
- package/lib/release.js +13 -7
- package/lib/scanner-python.js +38 -8
- package/lib/scraper-client.js +2 -1
- package/lib/separation.js +4 -2
- package/lib/theme-store.js +61 -0
- package/lib/themes.js +186 -0
- package/lib/tiers.js +2 -8
- package/package.json +4 -4
- package/scripts/requirements.txt +18 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljcioj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16hs8kw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +0 -9
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +0 -9
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +0 -3
- package/.next/standalone/.next/static/chunks/03bziijw5srol.js +0 -1
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +0 -34
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +0 -2
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +0 -1
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +0 -1
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +0 -43
- package/.next/standalone/.next/static/chunks/2dwtoszeq91us.js +0 -1
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +0 -1
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +0 -1
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +0 -1
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +0 -1
- package/.next/static/chunks/03bziijw5srol.js +0 -1
- package/.next/static/chunks/0bgq8wmopl_ig.js +0 -7
- package/.next/static/chunks/0ezus6p2eb-vg.js +0 -1
- package/.next/static/chunks/0g_fy2034bp0x.js +0 -34
- package/.next/static/chunks/0q5dru4q6gxl5.js +0 -22
- package/.next/static/chunks/0w-byiwb2zax8.js +0 -2
- package/.next/static/chunks/1g7jcovdejc9-.js +0 -1
- package/.next/static/chunks/1i51yylxdzht1.js +0 -1
- package/.next/static/chunks/1zf8swaasjf1e.js +0 -43
- package/.next/static/chunks/2dwtoszeq91us.js +0 -1
- package/.next/static/chunks/2rqtpccruavf2.js +0 -1
- package/.next/static/chunks/30x3oq_st7a5r.js +0 -1
- package/.next/static/chunks/355ryio1b7sm1.js +0 -1
- package/.next/static/chunks/3ap5hgma7n0o7.js +0 -1
- package/.next/static/chunks/3fci6qi39-1n5.js +0 -1
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_buildManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{I-cj79yE2IZ8taX2dZGp3 → dQUvvVSXqbkEFC3-CNcwT}/_ssgManifest.js +0 -0
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { initialsFor } from '../../lib/tiers';
|
|
4
4
|
import { localPhoto } from '../../lib/photos';
|
|
5
|
+
import { TIER_COLORS as THEME_TIERS } from '../../lib/themes';
|
|
5
6
|
|
|
6
|
-
const FALLBACK =
|
|
7
|
+
const FALLBACK = THEME_TIERS; // the theme's dot colours (lib/themes.js)
|
|
7
8
|
|
|
8
9
|
// A round face with a tier-coloured ring.
|
|
9
10
|
//
|
|
@@ -42,8 +42,8 @@ import { keyFor, score } from '../../lib/separation';
|
|
|
42
42
|
import { hasRequest } from '../../lib/requests-client';
|
|
43
43
|
import { watchScanner, scannerNow, isCircleScan } from '../../lib/scraper-client';
|
|
44
44
|
import useRequests from './useRequests';
|
|
45
|
+
import { TIER_COLORS } from '../../lib/themes';
|
|
45
46
|
|
|
46
|
-
const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
|
|
47
47
|
const DEGREE_COLORS = { 1: '#FFD700', 2: '#FF6B35', 3: '#3498DB', 4: '#9B59B6', 5: '#00ff88', 6: '#ff5050' };
|
|
48
48
|
const TIER_RANK = { S: 0, A: 1, B: 2, C: 3, D: 4 };
|
|
49
49
|
const GREEN = '#00ff88';
|
|
@@ -309,7 +309,7 @@ export default function ChainView({ connections, degree2 = [], onSelect, userNam
|
|
|
309
309
|
// === ONE CIRCLE: a bridge's, or anyone's in it, opened in place ===
|
|
310
310
|
if (focused) {
|
|
311
311
|
return (
|
|
312
|
-
<div ref={containerRef} style={{ flex: 1, background: '
|
|
312
|
+
<div ref={containerRef} style={{ flex: 1, background: 'var(--sd-page)', position: 'relative', overflow: 'hidden' }}>
|
|
313
313
|
<CircleFocus
|
|
314
314
|
key={trail.map((r) => r.id).join('>')}
|
|
315
315
|
trail={trail} index={index} reach={reach} dims={dims} requests={requests}
|
|
@@ -329,7 +329,7 @@ export default function ChainView({ connections, degree2 = [], onSelect, userNam
|
|
|
329
329
|
const hovBridge = hovered ? bridgePos.find(b => b.id === hovered) : null;
|
|
330
330
|
|
|
331
331
|
return (
|
|
332
|
-
<div ref={containerRef} style={{ flex: 1, background: '
|
|
332
|
+
<div ref={containerRef} style={{ flex: 1, background: 'var(--sd-page)', position: 'relative', overflow: 'hidden' }}>
|
|
333
333
|
<svg width={dims.w} height={dims.h}>
|
|
334
334
|
<g transform={`translate(${cx * (1 - zoom)}, ${cy * (1 - zoom)}) scale(${zoom})`}>
|
|
335
335
|
{/* A guide line for every ring of bridges */}
|
|
@@ -390,7 +390,7 @@ export default function ChainView({ connections, degree2 = [], onSelect, userNam
|
|
|
390
390
|
{/* How many in their circle are waiting for a scan: a small outlined number, top right */}
|
|
391
391
|
{ready > 0 && (
|
|
392
392
|
<g pointerEvents="none">
|
|
393
|
-
<circle cx={b.x + 14} cy={b.y - 14} r={6} fill="
|
|
393
|
+
<circle cx={b.x + 14} cy={b.y - 14} r={6} fill="var(--sd-bg)" stroke={GREEN} strokeWidth={1.2} />
|
|
394
394
|
<text x={b.x + 14} y={b.y - 11.4} textAnchor="middle" fill={GREEN} fontSize={ready > 9 ? 6 : 7} fontWeight={800}>{ready > 99 ? '99+' : ready}</text>
|
|
395
395
|
</g>
|
|
396
396
|
)}
|
|
@@ -408,7 +408,7 @@ export default function ChainView({ connections, degree2 = [], onSelect, userNam
|
|
|
408
408
|
})}
|
|
409
409
|
|
|
410
410
|
{/* Center: YOU */}
|
|
411
|
-
<circle cx={cx} cy={cy} r={22} fill="
|
|
411
|
+
<circle cx={cx} cy={cy} r={22} fill="var(--sd-bg)" stroke="#FFD700" strokeWidth={3} />
|
|
412
412
|
<text x={cx} y={cy + 4} textAnchor="middle" fill="#FFD700" fontSize={11} fontWeight={800}>
|
|
413
413
|
{userName?.split(' ')[0] || 'YOU'}
|
|
414
414
|
</text>
|
|
@@ -693,7 +693,7 @@ function CircleFocus({ trail, index, reach, dims, requests, scanningId, still, c
|
|
|
693
693
|
{(f.reached || (roomy && row.tier === 'S')) && (
|
|
694
694
|
<text x={p.x} y={above ? p.y - r - 4 / k : p.y + r + 10 / k} textAnchor="middle" fontSize={8.5 / k} fontWeight={f.reached ? 700 : 400}
|
|
695
695
|
fill={f.reached === 'hidden' ? '#999' : f.reached ? GREEN : '#aaa'} pointerEvents="none"
|
|
696
|
-
stroke="
|
|
696
|
+
stroke="var(--sd-bg)" strokeWidth={2.4 / k} paintOrder="stroke">
|
|
697
697
|
{firstName(row)}
|
|
698
698
|
</text>
|
|
699
699
|
)}
|
|
@@ -810,7 +810,7 @@ function CircleFocus({ trail, index, reach, dims, requests, scanningId, still, c
|
|
|
810
810
|
return (
|
|
811
811
|
<g key={'lk-' + row.id} transform={`translate(${p.x} ${p.y})`} pointerEvents="none"
|
|
812
812
|
opacity={litLinks && !lit ? 0.45 : 1}>
|
|
813
|
-
<circle r={c.radius} fill="
|
|
813
|
+
<circle r={c.radius} fill="var(--sd-bg)" fillOpacity={0.9} stroke={color} strokeOpacity={lit ? 0.7 : 0.25} strokeWidth={1 / k} />
|
|
814
814
|
{/* Their people, each joined to the middle of the cluster */}
|
|
815
815
|
{clusterPaths(c).map(([tier, { spokes }]) => (
|
|
816
816
|
<path key={'s' + tier} d={spokes} fill="none" stroke={TIER_COLORS[tier] || '#555'}
|
|
@@ -819,14 +819,14 @@ function CircleFocus({ trail, index, reach, dims, requests, scanningId, still, c
|
|
|
819
819
|
{clusterPaths(c).map(([tier, { dots: d }]) => (
|
|
820
820
|
<path key={tier} d={d} fill={TIER_COLORS[tier] || '#555'} fillOpacity={lit ? 1 : 0.9} />
|
|
821
821
|
))}
|
|
822
|
-
<circle r={LINK_HUB} fill={color} stroke={hovLink === j ? '#fff' : '
|
|
822
|
+
<circle r={LINK_HUB} fill={color} stroke={hovLink === j ? '#fff' : 'var(--sd-bg)'} strokeWidth={1.5} />
|
|
823
823
|
{row.id === scanningId && (
|
|
824
824
|
<circle r={c.radius + 4} fill="none" stroke={DEGREE_COLORS[3]} strokeWidth={1 / k} strokeDasharray={`${3 / k} ${2 / k}`} />
|
|
825
825
|
)}
|
|
826
826
|
<text y={c.radius + 12 / k} textAnchor="middle" fill="#fff" fontSize={10 / k} fontWeight={700}
|
|
827
|
-
stroke="
|
|
827
|
+
stroke="var(--sd-bg)" strokeWidth={2.4 / k} paintOrder="stroke">{firstName(row)}’s circle</text>
|
|
828
828
|
<text y={c.radius + 23 / k} textAnchor="middle" fill={color} fontSize={8 / k}
|
|
829
|
-
stroke="
|
|
829
|
+
stroke="var(--sd-bg)" strokeWidth={2.4 / k} paintOrder="stroke">
|
|
830
830
|
{c.people.length.toLocaleString('en-US')} · {ordinal(depth + link.step + 1)} degree
|
|
831
831
|
</text>
|
|
832
832
|
</g>
|
|
@@ -871,7 +871,7 @@ function CircleFocus({ trail, index, reach, dims, requests, scanningId, still, c
|
|
|
871
871
|
)}
|
|
872
872
|
{/* The person in the middle, with the halo if their own circle is ready to scan */}
|
|
873
873
|
{reachState(person, reach) === 'ready' && <Halo x={0} y={0} r={40} still={still} />}
|
|
874
|
-
<circle r={28} fill="
|
|
874
|
+
<circle r={28} fill="var(--sd-bg)" stroke={TIER_COLORS[person.tier] || '#888'} strokeWidth={hover === 'center' ? 4 : 3} filter="url(#focusGlow)" />
|
|
875
875
|
{localPhoto(person.profile_image_url) ? (
|
|
876
876
|
<>
|
|
877
877
|
<clipPath id="focus-clip"><circle r={24} /></clipPath>
|
|
@@ -1077,7 +1077,7 @@ function EmptyCircle({ person, depth, reach, requests, scanning, canScan, onSele
|
|
|
1077
1077
|
return (
|
|
1078
1078
|
<div role="status" style={{
|
|
1079
1079
|
position: 'absolute', left: '50%', top, transform: 'translateX(-50%)', width: 'min(360px, calc(100% - 32px))',
|
|
1080
|
-
background: '
|
|
1080
|
+
background: 'color-mix(in srgb, var(--sd-bg) 94%, transparent)', border: `1px solid ${state === 'hidden' ? 'rgba(255,255,255,0.12)' : 'rgba(0,255,136,0.25)'}`,
|
|
1081
1081
|
borderRadius: 12, padding: '14px 16px', textAlign: 'center',
|
|
1082
1082
|
}}>
|
|
1083
1083
|
<div style={{ fontSize: 13, fontWeight: 800, color: state === 'hidden' ? '#aaa' : '#fff', marginBottom: 6 }}>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import GalaxyLab, { NamesSwitch } from './GalaxyLab';
|
|
5
5
|
import { TIERS, GRID_DEGREES, showing, showingIn, toggleCell, toggleTier, toggleDegree, showAllTiers } from '../../lib/tier-grid';
|
|
6
|
+
import { TIER_COLORS } from '../../lib/themes';
|
|
6
7
|
|
|
7
8
|
function useIsMobile() {
|
|
8
9
|
const [m, setM] = useState(false);
|
|
@@ -10,7 +11,6 @@ function useIsMobile() {
|
|
|
10
11
|
return m;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const TIER_COLORS = { S: '#FFD700', A: '#9B59B6', B: '#3498DB', C: '#95A5A6', D: '#BDC3C7' };
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
const DEGREE_NAMES = ['', '1st', '2nd', '3rd', '4th', '5th', '6th'];
|
|
@@ -18,7 +18,8 @@ const DEGREE_NAMES = ['', '1st', '2nd', '3rd', '4th', '5th', '6th'];
|
|
|
18
18
|
const DEGREE_IS = {
|
|
19
19
|
network: { 1: 'your connections', 2: 'people in a scanned circle', 3: 'people found by a company scan' },
|
|
20
20
|
degrees: { 1: 'your bridges', 2: 'people in their circles' },
|
|
21
|
-
|
|
21
|
+
separation: { 2: 'the people Separation ranks' },
|
|
22
|
+
paths:{ 1: 'your connections', 2: 'people in a scanned circle', 3: 'people found by a company scan' },
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -31,6 +32,11 @@ export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
|
|
|
31
32
|
const dot = isMobile ? 21 : 23;
|
|
32
33
|
const anyAt = (d) => TIERS.some((t) => (counts[t]?.[d] || 0) > 0);
|
|
33
34
|
const what = (d) => DEGREE_IS[mode]?.[d];
|
|
35
|
+
// Separation: your connections are the ways in, and all of them stay, whatever their tier.
|
|
36
|
+
const waysIn = mode === 'separation';
|
|
37
|
+
const emptyTitle = (d, t) => (waysIn && d === 1
|
|
38
|
+
? 'Every connection stays a way in here, whatever their tier: the tiers choose who is ranked'
|
|
39
|
+
: `No ${t ? `${t}-Tier ` : 'one '}at ${DEGREE_NAMES[d]} degree yet`);
|
|
34
40
|
return (
|
|
35
41
|
<div style={{ marginBottom: 18 }}>
|
|
36
42
|
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 6 }}>
|
|
@@ -44,7 +50,7 @@ export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
|
|
|
44
50
|
const on = any && TIERS.every((t) => grid.hidden.includes(t) || !(counts[t]?.[d] > 0) || grid.degrees[t].includes(d));
|
|
45
51
|
return (
|
|
46
52
|
<button key={d} type="button" disabled={!any} aria-pressed={on}
|
|
47
|
-
title={any ? `${DEGREE_NAMES[d]} degree${what(d) ? `: ${what(d)}` : ''}. Tap to ${on ? 'hide' : 'show'} it for every tier` :
|
|
53
|
+
title={any ? `${DEGREE_NAMES[d]} degree${what(d) ? `: ${what(d)}` : ''}. Tap to ${on ? 'hide' : 'show'} it for every tier` : emptyTitle(d)}
|
|
48
54
|
onClick={() => onChange(toggleDegree(grid, d, counts))}
|
|
49
55
|
style={{
|
|
50
56
|
width: dot, height: 18, padding: 0, border: 'none', background: 'none', cursor: any ? 'pointer' : 'default',
|
|
@@ -87,14 +93,14 @@ export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
|
|
|
87
93
|
aria-label={`${t}-Tier at ${DEGREE_NAMES[d]} degree`}
|
|
88
94
|
title={people
|
|
89
95
|
? `${people.toLocaleString('en-US')} ${t}-Tier at ${DEGREE_NAMES[d]} degree. Tap to ${on ? 'hide' : 'show'} them`
|
|
90
|
-
:
|
|
96
|
+
: emptyTitle(d, t)}
|
|
91
97
|
onClick={() => onChange(toggleCell(grid, t, d, counts))}
|
|
92
98
|
style={{
|
|
93
99
|
width: dot, height: dot, padding: 0, borderRadius: '50%', boxSizing: 'border-box',
|
|
94
100
|
cursor: people ? 'pointer' : 'default', fontSize: 10, fontWeight: 800, lineHeight: 1,
|
|
95
|
-
border: !people ? '1px dashed rgba(255,255,255,0.1)' : on ? `1.5px solid ${color}` : `1.5px solid ${color}70`,
|
|
96
|
-
background: on ? color : 'transparent',
|
|
97
|
-
color: !people ? 'rgba(255,255,255,0.12)' : on ? '#0a0a1a' : `${color}b0`,
|
|
101
|
+
border: waysIn && d === 1 ? `1px solid ${color}40` : !people ? '1px dashed rgba(255,255,255,0.1)' : on ? `1.5px solid ${color}` : `1.5px solid ${color}70`,
|
|
102
|
+
background: on ? color : waysIn && d === 1 ? `${color}1f` : 'transparent',
|
|
103
|
+
color: waysIn && d === 1 ? `${color}90` : !people ? 'rgba(255,255,255,0.12)' : on ? '#0a0a1a' : `${color}b0`,
|
|
98
104
|
opacity: off && people ? 0.45 : 1,
|
|
99
105
|
}}>
|
|
100
106
|
{d}
|
|
@@ -112,7 +118,9 @@ export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
|
|
|
112
118
|
|
|
113
119
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 8 }}>
|
|
114
120
|
<div style={{ fontSize: 10, color: '#667', lineHeight: 1.4 }}>
|
|
115
|
-
|
|
121
|
+
{waysIn
|
|
122
|
+
? 'Tap a tier to leave it out of the ranking. Every connection stays a way in.'
|
|
123
|
+
: 'Tap a tier to hide it, or a dot to show that degree.'}
|
|
116
124
|
</div>
|
|
117
125
|
<div style={{ fontSize: 10.5, color: '#889', whiteSpace: 'nowrap', marginLeft: 8 }}>
|
|
118
126
|
{total.toLocaleString('en-US')} showing
|
|
@@ -130,7 +138,7 @@ export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
|
|
|
130
138
|
|
|
131
139
|
export default function FilterPanel({ collapsed, onToggle, mode, visualMode, grid, gridCounts = {}, onGridChange }) {
|
|
132
140
|
const isMobile = useIsMobile();
|
|
133
|
-
const isDegreesMode = mode === 'degrees';
|
|
141
|
+
const isDegreesMode = mode === 'degrees' || mode === 'separation';
|
|
134
142
|
|
|
135
143
|
if (collapsed) {
|
|
136
144
|
return (
|
|
@@ -168,8 +176,8 @@ export default function FilterPanel({ collapsed, onToggle, mode, visualMode, gri
|
|
|
168
176
|
borderRight: '1px solid rgba(52,152,219,0.15)',
|
|
169
177
|
padding: isMobile ? '12px 10px' : '16px 14px',
|
|
170
178
|
overflowY: 'auto', overflowX: 'hidden',
|
|
171
|
-
background: isMobile ? 'rgba(10,15,30,0.98)' : '
|
|
172
|
-
backdropFilter: 'blur
|
|
179
|
+
background: isMobile ? 'rgba(10,15,30,0.98)' : 'var(--sd-panel)', fontSize: 13,
|
|
180
|
+
backdropFilter: 'var(--sd-panel-blur)', WebkitBackdropFilter: 'var(--sd-panel-blur)',
|
|
173
181
|
boxShadow: 'inset 0 0 60px rgba(52,152,219,0.04), 4px 0 24px rgba(0,0,0,0.3)',
|
|
174
182
|
}}>
|
|
175
183
|
{/* Close button */}
|
|
@@ -186,7 +194,7 @@ export default function FilterPanel({ collapsed, onToggle, mode, visualMode, gri
|
|
|
186
194
|
</button>
|
|
187
195
|
|
|
188
196
|
{/* Which tiers, at which degrees: the same grid in Network Circle and in Degrees */}
|
|
189
|
-
<TierGrid grid={grid} counts={gridCounts} onChange={onGridChange} mode={
|
|
197
|
+
<TierGrid grid={grid} counts={gridCounts} onChange={onGridChange} mode={mode} isMobile={isMobile} />
|
|
190
198
|
|
|
191
199
|
{/* Network Circle's Galaxy: the physics that lays it out is what this panel is for. */}
|
|
192
200
|
{!isDegreesMode && visualMode === 'galaxy' && (isMobile ? <NamesSwitch /> : <GalaxyLab />)}
|
|
@@ -6,8 +6,10 @@ import { localPhoto } from '../../lib/photos';
|
|
|
6
6
|
import { recentre } from '../../lib/galaxy';
|
|
7
7
|
import { reachIndex, readyByCircle, scanBars } from '../../lib/reach';
|
|
8
8
|
import { ringSegments, RING } from '../../lib/dot-rings';
|
|
9
|
-
import { LAB_DEFAULTS, labNow, watchLab, effectiveLab, clockNow, watchClock, setClock, stopReplay, bornTimes, reachCounts, colourScheme, findMatches, loadSocial, chapterAt, heatColour } from '../../lib/galaxy-lab';
|
|
9
|
+
import { LAB_DEFAULTS, FORCE_KEYS, labNow, watchLab, effectiveLab, clockNow, watchClock, setClock, stopReplay, bornTimes, reachCounts, colourScheme, findMatches, loadSocial, chapterAt, heatColour } from '../../lib/galaxy-lab';
|
|
10
10
|
import { registerGalaxy } from '../../lib/galaxy-export';
|
|
11
|
+
import { MAP_LOOK } from '../../lib/themes';
|
|
12
|
+
import { keyFor, routeIndex } from '../../lib/separation';
|
|
11
13
|
|
|
12
14
|
// Connection fields are attacker-reachable: /api/ingest and /api/update-images
|
|
13
15
|
// accept writes, and a page on any other site can POST to this app on localhost.
|
|
@@ -63,6 +65,8 @@ const RING_CSS = `
|
|
|
63
65
|
.lab-focus .dot-rings, .lab-focus .catalyst-ring { opacity: 0.15; }
|
|
64
66
|
.lab-find .gn:not(.found), .lab-find .gl:not(.found) { opacity: 0.07; }
|
|
65
67
|
.lab-find .gn.found { stroke: #fff; stroke-width: 2px; }
|
|
68
|
+
.far text:not(.hub) { opacity: 0; }
|
|
69
|
+
@media (prefers-reduced-motion: no-preference) { .far text, g:not(.far) > text { transition: opacity 0.25s; } }
|
|
66
70
|
@keyframes lab-pop { from { opacity: 0; } }
|
|
67
71
|
@media (prefers-reduced-motion: no-preference) { .lab-pop .gn, .lab-pop .gl { animation: lab-pop 0.5s ease-out; } }
|
|
68
72
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -84,6 +88,10 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
84
88
|
}
|
|
85
89
|
return out;
|
|
86
90
|
}, [connections, fullDegree1, fullDegree2, scanNotes]);
|
|
91
|
+
// Every circle each person two steps away is in, from every circle scanned:
|
|
92
|
+
// the Galaxy links them to each of those connections, not only the one their
|
|
93
|
+
// row came through, so a shared person sits between the hubs they join.
|
|
94
|
+
const routes = useMemo(() => routeIndex(fullDegree2 || []), [fullDegree2]);
|
|
87
95
|
const svgRef = useRef(null);
|
|
88
96
|
const ringRef = useRef(null);
|
|
89
97
|
const sceneRef = useRef(null); // The scene on screen: { resize, select }
|
|
@@ -108,6 +116,7 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
108
116
|
const c = clockNow();
|
|
109
117
|
sceneRef.current?.setTime(c.at);
|
|
110
118
|
sceneRef.current?.setFind(c.find, c.fly);
|
|
119
|
+
sceneRef.current?.setFit(c.fit);
|
|
111
120
|
}), []);
|
|
112
121
|
useEffect(() => () => stopReplay(), []);
|
|
113
122
|
|
|
@@ -179,7 +188,7 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
179
188
|
const saved = transform ? recentre(transform, setFor, size) : null;
|
|
180
189
|
|
|
181
190
|
const select = (d) => onSelectRef.current?.(d);
|
|
182
|
-
const scene = renderNetworkMode(svg, ringRef.current, size, connections, select, tierColors, focusNodeRef, saved, viewRef, userName, marks, effectiveLab(labRef.current), stampRef.current, schemeRef.current);
|
|
191
|
+
const scene = renderNetworkMode(svg, ringRef.current, size, connections, select, tierColors, focusNodeRef, saved, viewRef, userName, marks, effectiveLab(labRef.current), stampRef.current, schemeRef.current, routes);
|
|
183
192
|
sceneRef.current = scene;
|
|
184
193
|
scene.select(selectedIdRef.current);
|
|
185
194
|
setClock({ min: scene.range.min, max: scene.range.max, of: scene.range.of });
|
|
@@ -200,6 +209,7 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
200
209
|
// the same nodes, so they fought each other and the graph shook.
|
|
201
210
|
// Refreshing made it worse because nothing ever stopped the old ones.
|
|
202
211
|
scene.simulation.stop();
|
|
212
|
+
scene.dispose();
|
|
203
213
|
unregister();
|
|
204
214
|
// And any glide or fly-to still under way, which would otherwise go on
|
|
205
215
|
// moving the next scene's view. viewRef already holds where it was going.
|
|
@@ -211,12 +221,12 @@ export default function ForceGraph({ connections, onSelect, tierColors, focusNod
|
|
|
211
221
|
// why onSelect is not a dependency. The size is read through sizeRef: a new
|
|
212
222
|
// size re-fits the view rather than rebuilding.
|
|
213
223
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
214
|
-
}, [connections, tierColors, measured, marks]);
|
|
224
|
+
}, [connections, tierColors, measured, marks, routes]);
|
|
215
225
|
|
|
216
226
|
return (
|
|
217
227
|
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
|
218
228
|
<style>{RING_CSS}</style>
|
|
219
|
-
<svg ref={svgRef} width={dimensions?.width ?? 800} height={dimensions?.height ?? 600} style={{ background: '
|
|
229
|
+
<svg ref={svgRef} width={dimensions?.width ?? 800} height={dimensions?.height ?? 600} style={{ background: 'transparent' }} />
|
|
220
230
|
{/* The selection ring. The scene places it over the selected dot. */}
|
|
221
231
|
<div ref={ringRef} className="galaxy-ring" style={{
|
|
222
232
|
position: 'absolute', left: 0, top: 0, display: 'none',
|
|
@@ -272,7 +282,7 @@ function easeRadius(el, r) {
|
|
|
272
282
|
// Graph coordinates put you at 0,0, whatever the size of the box; the zoom
|
|
273
283
|
// transform places that in the box. So a new size only moves the view, and a
|
|
274
284
|
// rebuild can start from the view as it was.
|
|
275
|
-
function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, focusNodeRef, savedTransform, viewRef, userName, marks = new Map(), lab = LAB_DEFAULTS, stamp = null, scheme = null) {
|
|
285
|
+
function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, focusNodeRef, savedTransform, viewRef, userName, marks = new Map(), lab = LAB_DEFAULTS, stamp = null, scheme = null, routes = new Map()) {
|
|
276
286
|
let colourOf = scheme?.of ?? ((d) => tierColors[d.tier] || '#666');
|
|
277
287
|
const centerNode = {
|
|
278
288
|
id: CENTER_ID, name: userName || 'You', tier: 'center', degree: 0,
|
|
@@ -323,6 +333,30 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
323
333
|
const via = parentOf.get(c.id) ?? CENTER_ID;
|
|
324
334
|
return { source: via, target: c.id, near: via !== CENTER_ID };
|
|
325
335
|
});
|
|
336
|
+
// Every other way in, for someone two steps away who is in more than one
|
|
337
|
+
// circle on screen: a line to each of those connections too, so they sit
|
|
338
|
+
// between the hubs they join and tie them together, as a note linked from
|
|
339
|
+
// two others does in Obsidian. After the first lines, so links[i] stays
|
|
340
|
+
// connections[i] for the replay.
|
|
341
|
+
const alsoVia = new Map(); // id → the other connections they're reached through
|
|
342
|
+
for (const n of nodes) {
|
|
343
|
+
if (n.degree !== 2 || !parentOf.has(n.id)) continue;
|
|
344
|
+
const ways = routes.get(keyFor(n));
|
|
345
|
+
if (!ways || ways.size < 2) continue;
|
|
346
|
+
const others = [...ways].filter((id) => id != null && id !== parentOf.get(n.id) && drawnD1.has(id));
|
|
347
|
+
if (others.length) alsoVia.set(n.id, others);
|
|
348
|
+
}
|
|
349
|
+
for (const [id, others] of alsoVia) for (const via of others) links.push({ source: via, target: id, near: true, also: true });
|
|
350
|
+
// How many lines meet at each dot (Size by → Links, as Obsidian sizes a note),
|
|
351
|
+
// and who has a circle on screen (a hub).
|
|
352
|
+
const linkCount = new Map();
|
|
353
|
+
for (const l of links) {
|
|
354
|
+
linkCount.set(l.source, (linkCount.get(l.source) || 0) + 1);
|
|
355
|
+
linkCount.set(l.target, (linkCount.get(l.target) || 0) + 1);
|
|
356
|
+
}
|
|
357
|
+
const hubs = new Set(parentOf.values());
|
|
358
|
+
const circleOf = new Map(); // hub → how many hang off them
|
|
359
|
+
for (const p of parentOf.values()) circleOf.set(p, (circleOf.get(p) || 0) + 1);
|
|
326
360
|
|
|
327
361
|
const tierRadius = (tier) => {
|
|
328
362
|
switch (tier) { case 'S': return 150; case 'A': return 250; case 'B': return 350; case 'C': return 450; default: return 520; }
|
|
@@ -352,7 +386,11 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
352
386
|
const reach = reachCounts(nodes, parentOf);
|
|
353
387
|
|
|
354
388
|
const nodeRadius = (d) => {
|
|
355
|
-
if (d.id === CENTER_ID) return 18;
|
|
389
|
+
if (d.id === CENTER_ID) return L.sizeBy === 'links' ? 24 : 18;
|
|
390
|
+
// Sized by links, a dot grows with the lines that meet at it: a connection
|
|
391
|
+
// with a scanned circle is a big hub, someone in three circles a little
|
|
392
|
+
// bigger than someone in one.
|
|
393
|
+
if (L.sizeBy === 'links') return Math.min(44, 3.5 + 2.1 * Math.sqrt(linkCount.get(d.id) || 1)) * L.dotSize;
|
|
356
394
|
// Sized by reach, a dot grows with everyone who hangs off it: a 2nd-degree
|
|
357
395
|
// person with a full circle behind them next to one with nobody.
|
|
358
396
|
if (L.sizeBy === 'reach') return Math.min(28, 2.5 + 1.6 * Math.sqrt(reach.get(d.id) || 0)) * L.dotSize;
|
|
@@ -397,9 +435,13 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
397
435
|
// through re-fits the destination, and a rebuild starts from it, rather than
|
|
398
436
|
// from a point along the way.
|
|
399
437
|
let heading = null;
|
|
400
|
-
|
|
438
|
+
// Names fade out zoomed out, as Obsidian's text does, all but the hubs'.
|
|
439
|
+
let labelsG = null;
|
|
440
|
+
const FAR = 0.85;
|
|
441
|
+
const zoomBehavior = d3.zoom().scaleExtent([0.1, 4]).on('zoom', (e) => {
|
|
401
442
|
g.attr('transform', e.transform);
|
|
402
443
|
viewRef.current = { transform: heading ?? e.transform, size: box };
|
|
444
|
+
labelsG?.classed('far', e.transform.k < FAR);
|
|
403
445
|
placeRing();
|
|
404
446
|
});
|
|
405
447
|
svg.call(zoomBehavior);
|
|
@@ -490,14 +532,28 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
490
532
|
// Each force is today's value times its slider in the lab (1 when it's off).
|
|
491
533
|
const linkBase = isMobileGraph ? 0 : layered ? 0.06 : 0.1;
|
|
492
534
|
const radialBase = layered ? 0.45 : 0.3;
|
|
493
|
-
const linkDistance =
|
|
535
|
+
const linkDistance = (d) => {
|
|
536
|
+
const base = (d.near ? Math.max(40, radiusOf(d.target) - radiusOf(d.source)) : radiusOf(d.target)) * L.distance;
|
|
537
|
+
if (L.sizeBy !== 'links') return base;
|
|
538
|
+
// Sized by links a hub is big, so its circle starts at its edge rather than
|
|
539
|
+
// under it; and a hub sits as far from you as its circle is wide, so the
|
|
540
|
+
// circles go round you like petals instead of piling up in the middle.
|
|
541
|
+
const petal = d.source.id === CENTER_ID ? 7 * Math.sqrt(circleOf.get(d.target.id) || 0) : 0;
|
|
542
|
+
return base + nodeRadius(d.source) + nodeRadius(d.target) + petal;
|
|
543
|
+
};
|
|
494
544
|
const charge = d => (d.id === CENTER_ID ? -20 : -1) * L.push;
|
|
545
|
+
// The center force pulls you, your connections and every hub; someone in a
|
|
546
|
+
// circle mostly follows their hub, so each circle holds together round it
|
|
547
|
+
// rather than being drawn into one even disc with all the others.
|
|
548
|
+
const gravity = d => 0.15 * L.gravity * (hubs.has(d.id) ? 0.5 : d.degree >= 2 ? 0.2 : 1);
|
|
549
|
+
// Thicker lines show more of themselves too, so a cluster's links read at a distance.
|
|
550
|
+
const lineOpacity = () => Math.min(0.45, 0.1 + 0.05 * L.lines);
|
|
495
551
|
const simulation = d3.forceSimulation(nodes)
|
|
496
552
|
.force('link', d3.forceLink(links).id(d => d.id).distance(linkDistance).strength(Math.min(1, linkBase * L.pull)))
|
|
497
553
|
.force('charge', d3.forceManyBody().strength(isMobileGraph ? 0 : charge))
|
|
498
554
|
.force('center', isMobileGraph ? null : d3.forceCenter(0, 0))
|
|
499
|
-
.force('gravityX', isMobileGraph ? null : d3.forceX(0).strength(
|
|
500
|
-
.force('gravityY', isMobileGraph ? null : d3.forceY(0).strength(
|
|
555
|
+
.force('gravityX', isMobileGraph ? null : d3.forceX(0).strength(gravity))
|
|
556
|
+
.force('gravityY', isMobileGraph ? null : d3.forceY(0).strength(gravity))
|
|
501
557
|
.force('collision', isMobileGraph ? null : d3.forceCollide().radius(d => nodeRadius(d) + 2))
|
|
502
558
|
.force('radial', isMobileGraph ? null : d3.forceRadial(d => d.id === CENTER_ID ? 0 : radiusOf(d), 0, 0).strength(radialBase * L.rings));
|
|
503
559
|
guidesG?.attr('opacity', Math.min(1, L.rings));
|
|
@@ -538,10 +594,12 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
538
594
|
// forceLink has already swapped each link's ids for the nodes themselves, so
|
|
539
595
|
// the colour is one step away. It used to search every node for every link:
|
|
540
596
|
// about 1.8 s at 30,000 people.
|
|
597
|
+
// The theme says whether a line takes the colour of the person it leads to, or one colour for all (lib/themes.js).
|
|
598
|
+
const lineColour = (d) => (MAP_LOOK.lines === 'one' ? MAP_LOOK.line : colourOf(d.target) || '#333');
|
|
541
599
|
const link = g.append('g').selectAll('line').data(links).join('line')
|
|
542
600
|
.attr('class', 'gl')
|
|
543
|
-
.attr('stroke',
|
|
544
|
-
.attr('stroke-opacity',
|
|
601
|
+
.attr('stroke', lineColour)
|
|
602
|
+
.attr('stroke-opacity', lineOpacity()).attr('stroke-width', 0.5 * L.lines);
|
|
545
603
|
|
|
546
604
|
// Catalyst outer glow rings (rendered behind the nodes)
|
|
547
605
|
// A catalyst is the green outline on their dot (design C, with the scan
|
|
@@ -593,7 +651,7 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
593
651
|
const node = g.append('g').selectAll('circle').data(nodes).join('circle')
|
|
594
652
|
.attr('class', 'gn')
|
|
595
653
|
.attr('r', nodeRadius)
|
|
596
|
-
.attr('fill', d => d.id === CENTER_ID ?
|
|
654
|
+
.attr('fill', d => d.id === CENTER_ID ? MAP_LOOK.you : colourOf(d))
|
|
597
655
|
.attr('fill-opacity', d => (d.degree >= 3 ? 0.55 : d.degree === 2 ? 0.75 : 1))
|
|
598
656
|
.attr('stroke', d => {
|
|
599
657
|
if (d.id === CENTER_ID) return '#FFD700';
|
|
@@ -688,15 +746,17 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
688
746
|
const labelled = (n) => {
|
|
689
747
|
if (findHit && findHit.size <= 40 && findHit.has(n.id)) return true;
|
|
690
748
|
if (!L.labels) return false;
|
|
691
|
-
return n.id === CENTER_ID || (n.degree === 1 && (L.names === 'all' || n.tier === 'S' || n.is_catalyst));
|
|
749
|
+
return n.id === CENTER_ID || (n.degree === 1 && (L.names === 'all' || n.tier === 'S' || n.is_catalyst || hubs.has(n.id)));
|
|
692
750
|
};
|
|
693
|
-
|
|
751
|
+
labelsG = g.append('g').classed('far', d3.zoomTransform(svg.node()).k < FAR);
|
|
694
752
|
let labels = labelsG.selectAll('text');
|
|
695
753
|
const drawLabels = () => {
|
|
696
754
|
labels = labelsG.selectAll('text').data(nodes.filter(labelled), d => d.id).join('text')
|
|
697
755
|
.text(d => d.is_catalyst && d.tier !== 'S' ? d.name + ' ⚡' : d.name)
|
|
698
|
-
.attr('
|
|
699
|
-
|
|
756
|
+
.attr('class', d => (d.id === CENTER_ID || hubs.has(d.id) ? 'hub' : null))
|
|
757
|
+
// A hub's name grows with its dot, so it still reads with everyone on screen.
|
|
758
|
+
.attr('font-size', d => d.id === CENTER_ID ? 14 : hubs.has(d.id) ? Math.max(12, Math.min(22, nodeRadius(d) * 0.5)) : 10)
|
|
759
|
+
.attr('font-weight', d => d.id === CENTER_ID || hubs.has(d.id) ? 700 : 500)
|
|
700
760
|
.attr('fill', d => {
|
|
701
761
|
if (d.id === CENTER_ID) return '#fff';
|
|
702
762
|
if (d.is_catalyst) return '#00ff88';
|
|
@@ -710,7 +770,7 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
710
770
|
|
|
711
771
|
const catalystRings = g.selectAll('.catalyst-ring');
|
|
712
772
|
|
|
713
|
-
|
|
773
|
+
const paint = () => {
|
|
714
774
|
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y).attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
715
775
|
node.attr('cx', d => d.x).attr('cy', d => d.y);
|
|
716
776
|
catalystRings.attr('cx', d => d.x).attr('cy', d => d.y);
|
|
@@ -718,8 +778,77 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
718
778
|
labels.attr('x', d => d.x).attr('y', d => d.y);
|
|
719
779
|
if (heatFn) heatDots.attr('cx', d => d.x).attr('cy', d => d.y);
|
|
720
780
|
placeRing();
|
|
781
|
+
};
|
|
782
|
+
let tickedAt = 0;
|
|
783
|
+
simulation.on('tick', () => {
|
|
784
|
+
tickedAt = performance.now();
|
|
785
|
+
paint();
|
|
786
|
+
if (fitArmed && simulation.alpha() < SETTLED) { fitArmed = false; fitView(); }
|
|
721
787
|
});
|
|
722
788
|
|
|
789
|
+
// Fit: everyone on screen, once the layout has settled (a layout picked in
|
|
790
|
+
// the lab, or its Fit button). Asked for through the clock's `fit` count.
|
|
791
|
+
const SETTLED = 0.06;
|
|
792
|
+
let fitArmed = false;
|
|
793
|
+
let lastFit = clockNow().fit;
|
|
794
|
+
const fitView = () => {
|
|
795
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
796
|
+
nodes.forEach((n, i) => {
|
|
797
|
+
if (!shown[i]) return;
|
|
798
|
+
const r = nodeRadius(n);
|
|
799
|
+
x0 = Math.min(x0, n.x - r); y0 = Math.min(y0, n.y - r); x1 = Math.max(x1, n.x + r); y1 = Math.max(y1, n.y + r);
|
|
800
|
+
});
|
|
801
|
+
if (!Number.isFinite(x0) || !box.width || !box.height) return;
|
|
802
|
+
const k = Math.max(0.1, Math.min(2, Math.min(box.width / (x1 - x0 + 80), box.height / (y1 - y0 + 80))));
|
|
803
|
+
moveView(d3.zoomIdentity.translate(box.width / 2 - ((x0 + x1) / 2) * k, box.height / 2 - ((y0 + y1) / 2) * k).scale(k), 750);
|
|
804
|
+
};
|
|
805
|
+
let fitCheck = null;
|
|
806
|
+
const setFit = (fit) => {
|
|
807
|
+
if (fit === lastFit) return;
|
|
808
|
+
lastFit = fit;
|
|
809
|
+
fitArmed = true;
|
|
810
|
+
// A layout just picked restarts the forces a moment later; a settled one fits now.
|
|
811
|
+
clearTimeout(fitCheck);
|
|
812
|
+
fitCheck = setTimeout(() => {
|
|
813
|
+
if (fitArmed && simulation.alpha() < SETTLED && performance.now() - tickedAt > 100) { fitArmed = false; fitView(); }
|
|
814
|
+
}, 150);
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// Orbit (the lab's slider): the whole map turns round you, everyone at once,
|
|
818
|
+
// so nothing moves against anything else and the layout keeps its shape
|
|
819
|
+
// (each circle spinning on its own hub was tried: while the forces were still
|
|
820
|
+
// settling it dragged every circle off its hub). Moved in place every frame
|
|
821
|
+
// rather than by the forces, so it costs a repaint; a big network repaints
|
|
822
|
+
// every second or third frame, at the same speed. Never with Reduce Motion,
|
|
823
|
+
// nor on a phone (whose layout has no physics).
|
|
824
|
+
const every = nodes.length > 12000 ? 3 : nodes.length > 3000 ? 2 : 1;
|
|
825
|
+
let orbitTimer = null;
|
|
826
|
+
let lastT = 0;
|
|
827
|
+
let frame = 0;
|
|
828
|
+
const turn = (elapsed) => {
|
|
829
|
+
if (++frame % every) return;
|
|
830
|
+
const dt = Math.min(0.1, (elapsed - lastT) / 1000);
|
|
831
|
+
lastT = elapsed;
|
|
832
|
+
if (dt <= 0) return;
|
|
833
|
+
const a = 0.1 * L.orbit * dt; // radians this frame
|
|
834
|
+
const c = Math.cos(a), sn = Math.sin(a);
|
|
835
|
+
for (const n of nodes) {
|
|
836
|
+
if (n.fx != null) continue; // you, and anyone being dragged
|
|
837
|
+
const { x, y } = n;
|
|
838
|
+
n.x = x * c - y * sn;
|
|
839
|
+
n.y = x * sn + y * c;
|
|
840
|
+
// Its momentum turns with it, or while the forces settle it drifts the old way.
|
|
841
|
+
if (n.vx) { const vx = n.vx; n.vx = vx * c - n.vy * sn; n.vy = vx * sn + n.vy * c; }
|
|
842
|
+
}
|
|
843
|
+
// While the forces run, their tick paints.
|
|
844
|
+
if (performance.now() - tickedAt > 50) paint();
|
|
845
|
+
};
|
|
846
|
+
const setOrbit = () => {
|
|
847
|
+
const on = !isMobileGraph && L.orbit > 0 && !reducedMotion();
|
|
848
|
+
if (on && !orbitTimer) { lastT = 0; frame = 0; orbitTimer = d3.timer(turn); }
|
|
849
|
+
if (!on && orbitTimer) { orbitTimer.stop(); orbitTimer = null; }
|
|
850
|
+
};
|
|
851
|
+
|
|
723
852
|
// A slider moved: change the running layout in place and let it settle again.
|
|
724
853
|
const setLab = (next) => {
|
|
725
854
|
if (isMobileGraph) {
|
|
@@ -736,16 +865,17 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
736
865
|
simulation.force('collision').radius(d => nodeRadius(d) + 2);
|
|
737
866
|
if (ringNode) select(ringNode.id);
|
|
738
867
|
}
|
|
739
|
-
if (prev.lines !== L.lines) link.attr('stroke-width', 0.5 * L.lines);
|
|
868
|
+
if (prev.lines !== L.lines) link.attr('stroke-width', 0.5 * L.lines).attr('stroke-opacity', lineOpacity());
|
|
740
869
|
if (sized || prev.names !== L.names || prev.labels !== L.labels) { drawLabels(); showBorn(); }
|
|
741
870
|
if (!L.on || !L.branch) g.classed('lab-focus', false);
|
|
742
871
|
g.classed('lab-pop', L.on && nodes.length < 5000);
|
|
743
|
-
|
|
872
|
+
if (prev.orbit !== L.orbit) setOrbit();
|
|
873
|
+
const forces = FORCE_KEYS.some(k => prev[k] !== L[k]);
|
|
744
874
|
if (!forces && !sized) return;
|
|
745
875
|
simulation.force('link').distance(linkDistance).strength(Math.min(1, linkBase * L.pull));
|
|
746
876
|
simulation.force('charge').strength(charge);
|
|
747
|
-
simulation.force('gravityX').strength(
|
|
748
|
-
simulation.force('gravityY').strength(
|
|
877
|
+
simulation.force('gravityX').strength(gravity);
|
|
878
|
+
simulation.force('gravityY').strength(gravity);
|
|
749
879
|
simulation.force('radial').strength(radialBase * L.rings);
|
|
750
880
|
guidesG?.attr('opacity', Math.min(1, L.rings));
|
|
751
881
|
simulation.alpha(Math.max(simulation.alpha(), 0.5)).restart();
|
|
@@ -762,6 +892,7 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
762
892
|
dotRings.each(function (d) { extraEls.set(d.id, [this]); });
|
|
763
893
|
catalystRings.each(function (d) { extraEls.set(d.id, [...(extraEls.get(d.id) || []), this]); });
|
|
764
894
|
const shown = new Uint8Array(nodes.length).fill(1);
|
|
895
|
+
const idxOf = new Map(nodes.map((n, i) => [n.id, i]));
|
|
765
896
|
const d1 = nodes.filter(n => n.degree === 1);
|
|
766
897
|
const dated = d1.map(n => bornById.get(n.id)).filter(Number.isFinite);
|
|
767
898
|
const range = { min: dated.length ? dated.reduce((m, t) => Math.min(m, t)) : null, max: dated.length ? dated.reduce((m, t) => Math.max(m, t)) : null, of: d1.length };
|
|
@@ -786,6 +917,11 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
786
917
|
if (linkEls[i - 1]) linkEls[i - 1].style.display = display;
|
|
787
918
|
for (const el of extraEls.get(nodes[i].id) || []) el.style.display = display;
|
|
788
919
|
}
|
|
920
|
+
// A line to another way in shows while both its ends do.
|
|
921
|
+
for (let j = connections.length; j < links.length; j++) {
|
|
922
|
+
const vis = shown[idxOf.get(links[j].source.id)] && shown[idxOf.get(links[j].target.id)];
|
|
923
|
+
linkEls[j].style.display = vis ? '' : 'none';
|
|
924
|
+
}
|
|
789
925
|
showBorn();
|
|
790
926
|
if (!stamp) return;
|
|
791
927
|
stamp.style.display = at == null ? 'none' : 'block';
|
|
@@ -801,8 +937,8 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
801
937
|
colourOf = next.of;
|
|
802
938
|
heatFn = next.heat || null;
|
|
803
939
|
drawHeat();
|
|
804
|
-
node.attr('fill', d => d.id === CENTER_ID ?
|
|
805
|
-
link.attr('stroke',
|
|
940
|
+
node.attr('fill', d => d.id === CENTER_ID ? MAP_LOOK.you : colourOf(d));
|
|
941
|
+
link.attr('stroke', lineColour);
|
|
806
942
|
drawLabels();
|
|
807
943
|
showBorn();
|
|
808
944
|
if (ringNode) select(ringNode.id);
|
|
@@ -836,7 +972,9 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
836
972
|
}
|
|
837
973
|
};
|
|
838
974
|
|
|
839
|
-
|
|
975
|
+
setOrbit();
|
|
976
|
+
const dispose = () => { orbitTimer?.stop(); orbitTimer = null; clearTimeout(fitCheck); };
|
|
977
|
+
return { simulation, resize, select, setLab, setTime, setColours, setFind, setFit, dispose, range };
|
|
840
978
|
}
|
|
841
979
|
|
|
842
980
|
function setupDrag(node, simulation, fixedId) {
|