six-degrees 0.4.11 → 0.5.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 +9 -9
- 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.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 +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
- 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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +3 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +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 +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 +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 +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 +3 -3
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +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.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +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 +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.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 +14 -14
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +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 +14 -14
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
- 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.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 +14 -14
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
- 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 +14 -14
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
- 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 +14 -14
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
- 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 +14 -14
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
- 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 +14 -14
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
- 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 +14 -14
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
- 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 +14 -14
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__13dx4sd._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
- 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 -5
- 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 +19 -3
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
- 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/03bziijw5srol.js +1 -0
- package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/.next/standalone/CHANGELOG.md +187 -0
- package/.next/standalone/app/api/ingest/route.js +40 -1
- package/.next/standalone/app/api/scraper/route.js +7 -1
- package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
- package/.next/standalone/app/components/AppTabs.js +59 -0
- package/.next/standalone/app/components/ChainView.js +303 -89
- package/.next/standalone/app/components/CirclePanel.js +162 -0
- package/.next/standalone/app/components/CompanyScores.js +2 -2
- package/.next/standalone/app/components/FieldStep.js +4 -4
- package/.next/standalone/app/components/FilterPanel.js +121 -183
- package/.next/standalone/app/components/ForceGraph.js +41 -3
- package/.next/standalone/app/components/GalaxyLab.js +26 -31
- package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
- package/.next/standalone/app/components/MapControls.js +154 -0
- package/.next/standalone/app/components/NoteDetail.js +118 -0
- package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
- package/.next/standalone/app/components/PeopleMap.js +308 -0
- package/.next/standalone/app/components/ScanRadar.js +129 -0
- package/.next/standalone/app/components/ScanStatusBar.js +71 -27
- package/.next/standalone/app/components/SeparationView.js +626 -118
- package/.next/standalone/app/components/Sidebar.js +28 -2
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
- package/.next/standalone/app/components/settings/SectorSection.js +2 -2
- package/.next/standalone/app/components/settings/TierSection.js +1 -1
- package/.next/standalone/app/components/settings/TitleSection.js +149 -0
- package/.next/standalone/app/components/views.js +15 -14
- package/.next/standalone/app/page.js +89 -156
- package/.next/standalone/app/paths/page.js +39 -27
- package/.next/standalone/app/profile/page.js +33 -5
- package/.next/standalone/app/queue/page.js +56 -34
- package/.next/standalone/app/scores/page.js +16 -66
- package/.next/standalone/app/settings/page.js +27 -13
- package/.next/standalone/app/setup/page.js +298 -205
- package/.next/standalone/app/social/SocialHub.js +374 -0
- package/.next/standalone/app/social/page.js +10 -362
- package/.next/standalone/lib/chain-layout.js +105 -35
- package/.next/standalone/lib/degrees.js +0 -6
- package/.next/standalone/lib/dot-rings.js +25 -0
- package/.next/standalone/lib/galaxy-lab.js +65 -6
- package/.next/standalone/lib/ingest.js +2 -0
- package/.next/standalone/lib/island.js +40 -0
- package/.next/standalone/lib/linkedin-limits.js +6 -2
- package/.next/standalone/lib/map-heat.js +20 -0
- package/.next/standalone/lib/network-health.js +1 -1
- package/.next/standalone/lib/notifications.js +121 -0
- package/.next/standalone/lib/people-map.js +82 -0
- package/.next/standalone/lib/rarity.js +45 -3
- package/.next/standalone/lib/reach.js +7 -6
- package/.next/standalone/lib/rpc.js +15 -4
- package/.next/standalone/lib/scan-pace.js +46 -0
- package/.next/standalone/lib/scoring.js +39 -8
- package/.next/standalone/lib/scraper-client.js +19 -6
- package/.next/standalone/lib/sector-focus.js +4 -4
- package/.next/standalone/lib/separation.js +137 -10
- package/.next/standalone/lib/settings-effects.js +20 -11
- package/.next/standalone/lib/settings.js +3 -0
- package/.next/standalone/lib/tier-grid.js +99 -0
- package/.next/standalone/lib/title-ranking.js +83 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +51 -13
- package/.next/static/chunks/03bziijw5srol.js +1 -0
- package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
- package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
- package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
- package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
- package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
- package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
- package/.next/static/chunks/1i51yylxdzht1.js +1 -0
- package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
- package/.next/static/chunks/2rqtpccruavf2.js +1 -0
- package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
- package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
- package/.next/static/chunks/355ryio1b7sm1.js +1 -0
- package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
- package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
- package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
- package/CHANGELOG.md +187 -0
- package/lib/chain-layout.js +105 -35
- package/lib/degrees.js +0 -6
- package/lib/dot-rings.js +25 -0
- package/lib/galaxy-lab.js +65 -6
- package/lib/ingest.js +2 -0
- package/lib/island.js +40 -0
- package/lib/linkedin-limits.js +6 -2
- package/lib/map-heat.js +20 -0
- package/lib/network-health.js +1 -1
- package/lib/notifications.js +121 -0
- package/lib/people-map.js +82 -0
- package/lib/rarity.js +45 -3
- package/lib/reach.js +7 -6
- package/lib/rpc.js +15 -4
- package/lib/scan-pace.js +46 -0
- package/lib/scoring.js +39 -8
- package/lib/scraper-client.js +19 -6
- package/lib/sector-focus.js +4 -4
- package/lib/separation.js +137 -10
- package/lib/settings-effects.js +20 -11
- package/lib/settings.js +3 -0
- package/lib/tier-grid.js +99 -0
- package/lib/title-ranking.js +83 -0
- package/package.json +1 -1
- package/scripts/scrape.py +51 -13
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_05gt5ya.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0-85h6g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
- package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/standalone/.next/static/chunks/15sjx07xhpn6p.js +0 -1
- package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/standalone/.next/static/chunks/1a74dky22dxd6.js +0 -1
- package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/standalone/.next/static/chunks/297e1z34yi--z.js +0 -1
- package/.next/standalone/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
- package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/standalone/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
- package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- package/.next/standalone/app/components/OrbitGraph.js +0 -737
- package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
- package/.next/static/chunks/15sjx07xhpn6p.js +0 -1
- package/.next/static/chunks/160wjb73ddwqd.js +0 -6
- package/.next/static/chunks/1a74dky22dxd6.js +0 -1
- package/.next/static/chunks/1c_n0973t07l0.js +0 -2
- package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
- package/.next/static/chunks/236f0jtp68j_h.js +0 -1
- package/.next/static/chunks/297e1z34yi--z.js +0 -1
- package/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
- package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
- package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
- package/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
- package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
- /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
- /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
- /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// The right panel while a circle is open in Bridge Chains (Blake, 2026-10-02:
|
|
4
|
+
// "when im in a d2 cluster in the bridges and i open up the people panel it
|
|
5
|
+
// should show the cluster of whoever im in"). Who is in their circle, strongest
|
|
6
|
+
// first, with the people you added from it at the top: the ones whose own
|
|
7
|
+
// circle is the next link in the chain.
|
|
8
|
+
|
|
9
|
+
import { useMemo, useState } from 'react';
|
|
10
|
+
import Link from 'next/link';
|
|
11
|
+
import Avatar from './Avatar';
|
|
12
|
+
import { circleIndex } from '../../lib/circle';
|
|
13
|
+
import { reachIndex, reachState } from '../../lib/reach';
|
|
14
|
+
import { score1 } from '../../lib/separation';
|
|
15
|
+
import { TIER_ORDER } from '../../lib/tiers';
|
|
16
|
+
|
|
17
|
+
const PAGE = 60;
|
|
18
|
+
const rank = (a, b) => TIER_ORDER.indexOf(a.tier) - TIER_ORDER.indexOf(b.tier)
|
|
19
|
+
|| score1(b) - score1(a)
|
|
20
|
+
|| String(a.name || '').localeCompare(String(b.name || ''));
|
|
21
|
+
const first = (name) => String(name || '').trim().split(/\s+/)[0] || 'Their';
|
|
22
|
+
|
|
23
|
+
const rowStyle = {
|
|
24
|
+
display: 'flex', alignItems: 'center', gap: 9, width: '100%', padding: '7px 8px', borderRadius: 8, boxSizing: 'border-box',
|
|
25
|
+
background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.06)',
|
|
26
|
+
color: '#fff', cursor: 'pointer', textAlign: 'left', font: 'inherit', textDecoration: 'none',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** One person: opens their card, or (with `href`) goes there instead. */
|
|
30
|
+
function Row({ person, tierColors, note, noteColor, onSelect, href, label }) {
|
|
31
|
+
const body = (
|
|
32
|
+
<>
|
|
33
|
+
<Avatar person={person} size={26} tierColors={tierColors} />
|
|
34
|
+
<span style={{ flex: 1, minWidth: 0 }}>
|
|
35
|
+
<span style={{ display: 'block', fontSize: 12, fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
36
|
+
{person.name}
|
|
37
|
+
<span style={{ fontSize: 10, color: tierColors[person.tier] || '#888', fontWeight: 800, marginLeft: 6 }}>{person.tier}</span>
|
|
38
|
+
</span>
|
|
39
|
+
<span style={{ display: 'block', fontSize: 10.5, color: '#888', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
40
|
+
{[person.title, person.company].filter(Boolean).join(' · ') || ' '}
|
|
41
|
+
</span>
|
|
42
|
+
</span>
|
|
43
|
+
{note && <span style={{ fontSize: 10, fontWeight: 700, color: noteColor || '#888', whiteSpace: 'nowrap' }}>{note}</span>}
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
return href
|
|
47
|
+
? <Link href={href} aria-label={label} style={{ ...rowStyle, borderColor: 'rgba(0,255,136,0.2)' }}>{body}</Link>
|
|
48
|
+
: <button type="button" onClick={() => onSelect?.(person)} style={rowStyle}>{body}</button>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function CirclePanel({ person, connections = [], degree2 = [], scanNotes, tierColors, onSelect, onToggle, canScan = true }) {
|
|
52
|
+
const [tier, setTier] = useState(null);
|
|
53
|
+
const [query, setQuery] = useState('');
|
|
54
|
+
const [shown, setShown] = useState(PAGE);
|
|
55
|
+
const index = useMemo(() => circleIndex(connections, degree2), [connections, degree2]);
|
|
56
|
+
const reach = useMemo(() => reachIndex(connections, degree2, scanNotes), [connections, degree2, scanNotes]);
|
|
57
|
+
const members = useMemo(() => [...(index.circles.get(person.id) || [])].sort(rank), [index, person.id]);
|
|
58
|
+
const added = useMemo(() => [...(index.introduced.get(person.id) || [])].sort(rank), [index, person.id]);
|
|
59
|
+
const counts = useMemo(() => {
|
|
60
|
+
const c = Object.fromEntries(TIER_ORDER.map((t) => [t, 0]));
|
|
61
|
+
for (const m of members) if (c[m.tier] != null) c[m.tier] += 1;
|
|
62
|
+
return c;
|
|
63
|
+
}, [members]);
|
|
64
|
+
const q = query.trim().toLowerCase();
|
|
65
|
+
const list = members.filter((m) => (!tier || m.tier === tier)
|
|
66
|
+
&& (q.length < 2 || [m.name, m.title, m.company].some((v) => String(v || '').toLowerCase().includes(q))));
|
|
67
|
+
const color = tierColors[person.tier] || '#888';
|
|
68
|
+
const stateNote = (p) => {
|
|
69
|
+
const s = reachState(p, reach);
|
|
70
|
+
return s === 'ready' ? ['ready to scan', '#00ff88'] : s === 'scanned' ? ['circle scanned', '#74B9FF'] : s === 'hidden' ? ['list hidden', '#888'] : [null];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<button onClick={onToggle} style={{
|
|
76
|
+
display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none',
|
|
77
|
+
color: '#888', fontSize: 12, cursor: 'pointer', padding: '0 0 10px', fontWeight: 600,
|
|
78
|
+
}}>
|
|
79
|
+
<span style={{ fontSize: 16 }}>←</span> Close
|
|
80
|
+
</button>
|
|
81
|
+
|
|
82
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
|
|
83
|
+
<Avatar person={person} size={38} tierColors={tierColors} />
|
|
84
|
+
<div style={{ minWidth: 0 }}>
|
|
85
|
+
<div style={{ fontSize: 10, fontWeight: 700, letterSpacing: 1, color: '#888', textTransform: 'uppercase' }}>You’re in</div>
|
|
86
|
+
<h3 style={{ fontSize: 16, fontWeight: 800, margin: 0, color }}>{first(person.name)}’s circle</h3>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div style={{ fontSize: 11.5, color: '#999', marginBottom: 12 }}>
|
|
90
|
+
{[person.title, person.company].filter(Boolean).join(' · ')}
|
|
91
|
+
<button type="button" onClick={() => onSelect?.(person)} style={{
|
|
92
|
+
display: 'block', marginTop: 6, padding: 0, background: 'none', border: 'none', color: '#74B9FF',
|
|
93
|
+
fontSize: 11, fontWeight: 700, cursor: 'pointer',
|
|
94
|
+
}}>Open {first(person.name)}’s card →</button>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
{/* The same count as the map: the people you added from here were in it too. */}
|
|
98
|
+
<div style={{ fontSize: 22, fontWeight: 800, lineHeight: 1 }}>{(members.length + added.length).toLocaleString('en-US')}</div>
|
|
99
|
+
<div style={{ fontSize: 11, color: '#888', marginBottom: 10 }}>
|
|
100
|
+
{members.length + added.length === 1 ? 'person' : 'people'} in their circle{added.length ? ` · you added ${added.length} of them` : ''}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* Tier chips: tap one to see just them. */}
|
|
104
|
+
<div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginBottom: 10 }}>
|
|
105
|
+
{TIER_ORDER.map((t) => (
|
|
106
|
+
<button key={t} type="button" aria-pressed={tier === t} disabled={!counts[t]}
|
|
107
|
+
onClick={() => { setTier(tier === t ? null : t); setShown(PAGE); }}
|
|
108
|
+
style={{
|
|
109
|
+
padding: '4px 9px', borderRadius: 12, fontSize: 11, fontWeight: 700, cursor: counts[t] ? 'pointer' : 'default',
|
|
110
|
+
border: `1px solid ${tier === t ? tierColors[t] : 'rgba(255,255,255,0.1)'}`,
|
|
111
|
+
background: tier === t ? `${tierColors[t]}26` : 'rgba(255,255,255,0.03)',
|
|
112
|
+
color: counts[t] ? tierColors[t] : '#444',
|
|
113
|
+
}}>
|
|
114
|
+
{t} <span style={{ color: '#aaa', fontWeight: 600 }}>{counts[t]}</span>
|
|
115
|
+
</button>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<input type="search" value={query} placeholder={`Search ${first(person.name)}’s circle`}
|
|
120
|
+
onChange={(e) => { setQuery(e.target.value); setShown(PAGE); }}
|
|
121
|
+
style={{
|
|
122
|
+
width: '100%', padding: '8px 12px', borderRadius: 8, fontSize: 12, marginBottom: 14, boxSizing: 'border-box',
|
|
123
|
+
border: '1px solid rgba(255,255,255,0.1)', background: 'rgba(255,255,255,0.05)', color: '#fff', outline: 'none',
|
|
124
|
+
}} />
|
|
125
|
+
|
|
126
|
+
{added.length > 0 && !tier && q.length < 2 && (
|
|
127
|
+
<div style={{ marginBottom: 14 }}>
|
|
128
|
+
<div style={{ fontSize: 10, fontWeight: 700, color: '#00ff88', letterSpacing: 1, marginBottom: 6, textTransform: 'uppercase' }}>
|
|
129
|
+
You added from here ({added.length})
|
|
130
|
+
</div>
|
|
131
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
|
132
|
+
{added.map((p) => {
|
|
133
|
+
const [note, noteColor] = stateNote(p);
|
|
134
|
+
return note === 'ready to scan' && canScan
|
|
135
|
+
? <Row key={p.id} person={p} tierColors={tierColors} note="Scan… →" noteColor="#00ff88"
|
|
136
|
+
href={`/setup?scan=${encodeURIComponent(p.id)}`} label={`Scan ${p.name}’s circle: opens the Scan page to confirm`} />
|
|
137
|
+
: <Row key={p.id} person={p} tierColors={tierColors} note={note} noteColor={noteColor} onSelect={onSelect} />;
|
|
138
|
+
})}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
<div style={{ fontSize: 10, fontWeight: 700, color: '#888', letterSpacing: 1, marginBottom: 6, textTransform: 'uppercase' }}>
|
|
144
|
+
{tier ? `${tier} tier in their circle` : added.length ? 'Everyone else in their circle' : 'Everyone in their circle'} ({list.length.toLocaleString('en-US')})
|
|
145
|
+
</div>
|
|
146
|
+
{list.length === 0 ? (
|
|
147
|
+
<div style={{ fontSize: 11.5, color: '#888', lineHeight: 1.5 }}>
|
|
148
|
+
{members.length === 0 ? 'Their circle isn’t scanned yet.' : 'No one here matches.'}
|
|
149
|
+
</div>
|
|
150
|
+
) : (
|
|
151
|
+
<div role="list" style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
|
152
|
+
{list.slice(0, shown).map((m) => <Row key={m.id} person={m} tierColors={tierColors} onSelect={onSelect} />)}
|
|
153
|
+
{list.length > shown && (
|
|
154
|
+
<button type="button" onClick={() => setShown((n) => n + PAGE * 3)} style={{
|
|
155
|
+
background: 'none', border: 'none', color: '#888', fontSize: 11, fontWeight: 600, cursor: 'pointer', padding: '6px 0', textAlign: 'left',
|
|
156
|
+
}}>Show more ({(list.length - shown).toLocaleString('en-US')} left)</button>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// Settings → Scores → company scores (app/settings): every company in your network, the score it carries into
|
|
4
4
|
// people's power, where that score comes from, and a control to set your own.
|
|
5
5
|
// Setting a score rescores everyone (app/api/company-scores → lib/scoring.js).
|
|
6
6
|
|
|
@@ -61,7 +61,7 @@ export function ScorePicker({ company, onSet, compact }) {
|
|
|
61
61
|
|
|
62
62
|
export default function CompanyScores({ onRescored, version = 0 }) {
|
|
63
63
|
const { companies, error, setScore, reload } = useCompanyScores();
|
|
64
|
-
// A save beside the list (your sector or tiers,
|
|
64
|
+
// A save beside the list (your sector or tiers, in Settings) rescores
|
|
65
65
|
// everyone: `version` changes, and the list is read again, keeping what you
|
|
66
66
|
// searched and filtered.
|
|
67
67
|
useEffect(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
// The Scan page's one question before the first scan: what field you're in.
|
|
4
|
-
// The same picks as Scores → Your
|
|
4
|
+
// The same picks as Settings → Scores → Your field (SectorPicker), saved the same way
|
|
5
5
|
// (lib/settings-client.js), so the first scan is already scored with them.
|
|
6
6
|
// Optional: Skip for now carries on without one. Either answer is kept
|
|
7
7
|
// (`fieldAsked`, lib/sector-focus.js), so it's asked once; when it's asked at
|
|
@@ -53,7 +53,7 @@ export default function FieldStep({ onDone }) {
|
|
|
53
53
|
<h2 id="field-question" style={{ fontSize: 18, fontWeight: 700, margin: '6px 0 4px' }}>What field are you in?</h2>
|
|
54
54
|
<Body style={{ marginTop: 0, marginBottom: 14 }}>
|
|
55
55
|
Pick up to three. Companies in your field count for more when your network is scored, so the people there rank
|
|
56
|
-
higher. You can change it anytime
|
|
56
|
+
higher. You can change it anytime in Settings.
|
|
57
57
|
</Body>
|
|
58
58
|
|
|
59
59
|
<SectorPicker sectors={sectors} onChange={setSectors} disabled={Boolean(saving)} />
|
|
@@ -82,13 +82,13 @@ export default function FieldStep({ onDone }) {
|
|
|
82
82
|
/** What was answered, above the scan's steps: where it went, and where to change it. */
|
|
83
83
|
export function FieldAnswer({ sectors }) {
|
|
84
84
|
if (!sectors.length) {
|
|
85
|
-
return <Status>No field for now. You can pick one anytime
|
|
85
|
+
return <Status>No field for now. You can pick one anytime in <Link href="/settings#sector" style={link}>Settings</Link>.</Status>;
|
|
86
86
|
}
|
|
87
87
|
// The labels have commas of their own ("Tech, Software & AI"), so a dot separates them.
|
|
88
88
|
return (
|
|
89
89
|
<Status tone="ok">
|
|
90
90
|
Your field: {sectors.map((k) => sectorByKey(k).label).join(' · ')}. Your first scan is scored with it. Change it
|
|
91
|
-
anytime
|
|
91
|
+
anytime in <Link href="/settings#sector" style={link}>Settings</Link>.
|
|
92
92
|
</Status>
|
|
93
93
|
);
|
|
94
94
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import { viewsForMode } from './views';
|
|
5
4
|
import GalaxyLab, { NamesSwitch } from './GalaxyLab';
|
|
5
|
+
import { TIERS, GRID_DEGREES, showing, showingIn, toggleCell, toggleTier, toggleDegree, showAllTiers } from '../../lib/tier-grid';
|
|
6
6
|
|
|
7
7
|
function useIsMobile() {
|
|
8
8
|
const [m, setM] = useState(false);
|
|
@@ -13,19 +13,124 @@ function useIsMobile() {
|
|
|
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'];
|
|
17
|
+
// What each degree is, per tab: Network Circle draws people; Degrees draws bridges and their circles.
|
|
18
|
+
const DEGREE_IS = {
|
|
19
|
+
network: { 1: 'your connections', 2: 'people in a scanned circle', 3: 'people found by a company scan' },
|
|
20
|
+
degrees: { 1: 'your bridges', 2: 'people in their circles' },
|
|
21
|
+
paths: { 1: 'your connections', 2: 'people in a scanned circle', 3: 'people found by a company scan' },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tiers down, six degrees across: tap a tier to hide or show it, a dot to show
|
|
26
|
+
* that degree for that tier, a number at the top for that degree in every tier.
|
|
27
|
+
* The same in Network Circle, Degrees and Paths (lib/tier-grid.js).
|
|
28
|
+
*/
|
|
29
|
+
export function TierGrid({ grid, counts, onChange, mode, isMobile }) {
|
|
30
|
+
const total = showing(grid, counts);
|
|
31
|
+
const dot = isMobile ? 21 : 23;
|
|
32
|
+
const anyAt = (d) => TIERS.some((t) => (counts[t]?.[d] || 0) > 0);
|
|
33
|
+
const what = (d) => DEGREE_IS[mode]?.[d];
|
|
34
|
+
return (
|
|
35
|
+
<div style={{ marginBottom: 18 }}>
|
|
36
|
+
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 6 }}>
|
|
37
|
+
<div style={{ flex: 1, fontSize: 9, fontWeight: 700, color: '#555', letterSpacing: 1, textTransform: 'uppercase' }}>
|
|
38
|
+
Tiers
|
|
39
|
+
</div>
|
|
40
|
+
{/* A degree, for every tier at once */}
|
|
41
|
+
<div style={{ display: 'flex', gap: 3 }}>
|
|
42
|
+
{GRID_DEGREES.map((d) => {
|
|
43
|
+
const any = anyAt(d);
|
|
44
|
+
const on = any && TIERS.every((t) => grid.hidden.includes(t) || !(counts[t]?.[d] > 0) || grid.degrees[t].includes(d));
|
|
45
|
+
return (
|
|
46
|
+
<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` : `Nobody at ${DEGREE_NAMES[d]} degree yet`}
|
|
48
|
+
onClick={() => onChange(toggleDegree(grid, d, counts))}
|
|
49
|
+
style={{
|
|
50
|
+
width: dot, height: 18, padding: 0, border: 'none', background: 'none', cursor: any ? 'pointer' : 'default',
|
|
51
|
+
fontSize: 10, fontWeight: 800, color: !any ? '#333' : on ? '#cfe6f7' : '#778',
|
|
52
|
+
}}>
|
|
53
|
+
{d}
|
|
54
|
+
</button>
|
|
55
|
+
);
|
|
56
|
+
})}
|
|
57
|
+
</div>
|
|
58
|
+
<div style={{ width: 44 }} />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
62
|
+
{TIERS.map((t) => {
|
|
63
|
+
const off = grid.hidden.includes(t);
|
|
64
|
+
const n = showingIn(grid, counts, t);
|
|
65
|
+
const color = TIER_COLORS[t];
|
|
66
|
+
return (
|
|
67
|
+
<div key={t} style={{
|
|
68
|
+
display: 'flex', alignItems: 'center', borderRadius: 8,
|
|
69
|
+
background: off ? 'rgba(255,255,255,0.02)' : `${color}14`,
|
|
70
|
+
borderLeft: `3px solid ${off ? 'transparent' : color}`,
|
|
71
|
+
}}>
|
|
72
|
+
<button type="button" aria-pressed={!off} title={off ? `Show ${t}-Tier` : `Hide ${t}-Tier`}
|
|
73
|
+
onClick={() => onChange(toggleTier(grid, t, counts))}
|
|
74
|
+
style={{
|
|
75
|
+
flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 8, padding: '12px 6px 12px 10px',
|
|
76
|
+
border: 'none', background: 'none', cursor: 'pointer', color: off ? '#555' : color, textAlign: 'left',
|
|
77
|
+
}}>
|
|
78
|
+
<span style={{ width: 11, height: 11, borderRadius: '50%', background: color, opacity: off ? 0.2 : 1, flexShrink: 0 }} />
|
|
79
|
+
<span style={{ fontSize: 13.5, fontWeight: 700, whiteSpace: 'nowrap', textDecoration: off ? 'line-through' : 'none' }}>{t}-Tier</span>
|
|
80
|
+
</button>
|
|
81
|
+
<div style={{ display: 'flex', gap: 3 }}>
|
|
82
|
+
{GRID_DEGREES.map((d) => {
|
|
83
|
+
const people = counts[t]?.[d] || 0;
|
|
84
|
+
const on = !off && people > 0 && grid.degrees[t].includes(d);
|
|
85
|
+
return (
|
|
86
|
+
<button key={d} type="button" disabled={!people} aria-pressed={on}
|
|
87
|
+
aria-label={`${t}-Tier at ${DEGREE_NAMES[d]} degree`}
|
|
88
|
+
title={people
|
|
89
|
+
? `${people.toLocaleString('en-US')} ${t}-Tier at ${DEGREE_NAMES[d]} degree. Tap to ${on ? 'hide' : 'show'} them`
|
|
90
|
+
: `No ${t}-Tier at ${DEGREE_NAMES[d]} degree yet`}
|
|
91
|
+
onClick={() => onChange(toggleCell(grid, t, d, counts))}
|
|
92
|
+
style={{
|
|
93
|
+
width: dot, height: dot, padding: 0, borderRadius: '50%', boxSizing: 'border-box',
|
|
94
|
+
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`,
|
|
98
|
+
opacity: off && people ? 0.45 : 1,
|
|
99
|
+
}}>
|
|
100
|
+
{d}
|
|
101
|
+
</button>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
<div style={{ width: 44, textAlign: 'right', paddingRight: 8, fontSize: 10.5, color: off || !n ? '#445' : '#889', fontVariantNumeric: 'tabular-nums' }}>
|
|
106
|
+
{n.toLocaleString('en-US')}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 8 }}>
|
|
114
|
+
<div style={{ fontSize: 10, color: '#667', lineHeight: 1.4 }}>
|
|
115
|
+
Tap a tier to hide it, or a dot to show that degree.
|
|
116
|
+
</div>
|
|
117
|
+
<div style={{ fontSize: 10.5, color: '#889', whiteSpace: 'nowrap', marginLeft: 8 }}>
|
|
118
|
+
{total.toLocaleString('en-US')} showing
|
|
119
|
+
{grid.hidden.length > 0 && (
|
|
120
|
+
<button type="button" onClick={() => onChange(showAllTiers(grid))} style={{
|
|
121
|
+
marginLeft: 8, padding: '2px 8px', borderRadius: 10, fontSize: 10, cursor: 'pointer',
|
|
122
|
+
border: '1px solid rgba(255,255,255,0.12)', background: 'rgba(255,255,255,0.05)', color: '#aab',
|
|
123
|
+
}}>All tiers</button>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default function FilterPanel({ collapsed, onToggle, mode, visualMode, grid, gridCounts = {}, onGridChange }) {
|
|
17
132
|
const isMobile = useIsMobile();
|
|
18
133
|
const isDegreesMode = mode === 'degrees';
|
|
19
|
-
// Network Circle: each tier switches on and off, so the noisy ones (C, D) can go
|
|
20
|
-
// while the rest stay. Degrees keeps one tier at a time.
|
|
21
|
-
const toggles = !isDegreesMode && !!onHiddenTiersChange;
|
|
22
|
-
// Same source of truth the renderer uses, so the menu can never offer a
|
|
23
|
-
// view that does not exist or miss one that does.
|
|
24
|
-
const modes = viewsForMode(isDegreesMode ? 'degrees' : 'network');
|
|
25
|
-
const counts = isDegreesMode ? (bridgeTierCounts || {}) : (tierCounts || {});
|
|
26
|
-
const allCount = isDegreesMode
|
|
27
|
-
? Object.values(bridgeTierCounts || {}).reduce((s, v) => s + v, 0)
|
|
28
|
-
: Object.values(tierCounts || {}).reduce((s, v) => s + v, 0);
|
|
29
134
|
|
|
30
135
|
if (collapsed) {
|
|
31
136
|
return (
|
|
@@ -80,178 +185,11 @@ export default function FilterPanel({ collapsed, onToggle, mode, filter, onFilte
|
|
|
80
185
|
<span style={{ fontSize: 16 }}>›</span> Close
|
|
81
186
|
</button>
|
|
82
187
|
|
|
83
|
-
{/*
|
|
84
|
-
<
|
|
85
|
-
<div style={{ fontSize: 9, fontWeight: 700, color: '#555', letterSpacing: 1, marginBottom: 8, textTransform: 'uppercase' }}>
|
|
86
|
-
Visual
|
|
87
|
-
</div>
|
|
88
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
89
|
-
{modes.map(m => (
|
|
90
|
-
<button
|
|
91
|
-
key={m.key}
|
|
92
|
-
onClick={() => onVisualModeChange && onVisualModeChange(m.key)}
|
|
93
|
-
style={{
|
|
94
|
-
display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px',
|
|
95
|
-
borderRadius: 6, border: 'none', cursor: 'pointer', width: '100%', textAlign: 'left',
|
|
96
|
-
background: visualMode === m.key ? 'rgba(52,152,219,0.15)' : 'rgba(255,255,255,0.03)',
|
|
97
|
-
color: visualMode === m.key ? '#3498DB' : '#888',
|
|
98
|
-
transition: 'all 0.15s',
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
101
|
-
<span style={{ fontSize: 14, width: 20, textAlign: 'center' }}>{m.icon}</span>
|
|
102
|
-
<div>
|
|
103
|
-
<div style={{ fontSize: 11, fontWeight: 600 }}>{m.label}</div>
|
|
104
|
-
<div style={{ fontSize: 8, color: '#555', marginTop: 1 }}>{m.desc}</div>
|
|
105
|
-
</div>
|
|
106
|
-
</button>
|
|
107
|
-
))}
|
|
108
|
-
</div>
|
|
109
|
-
</div>
|
|
110
|
-
|
|
111
|
-
{/* Tier Filters */}
|
|
112
|
-
{/* Network Circle: which degrees to draw. Any mix, never none: 2nd and
|
|
113
|
-
3rd without your own connections shows who's valuable outside them. */}
|
|
114
|
-
{!isDegreesMode && onDegreesChange && (
|
|
115
|
-
<div style={{ marginBottom: 18 }}>
|
|
116
|
-
<div style={{ fontSize: 9, fontWeight: 700, color: '#555', letterSpacing: 1, marginBottom: 8, textTransform: 'uppercase' }}>
|
|
117
|
-
Degree
|
|
118
|
-
</div>
|
|
119
|
-
<div style={{ display: 'flex', gap: 6 }}>
|
|
120
|
-
{[[1, '1st', 'Your connections'], [2, '2nd', 'In a scanned circle'], [3, '3rd', 'Found by a company scan']].map(([d, label, what]) => {
|
|
121
|
-
const on = degrees.includes(d);
|
|
122
|
-
const count = degreeCounts[d] || 0;
|
|
123
|
-
const last = on && degrees.length === 1;
|
|
124
|
-
return (
|
|
125
|
-
<button
|
|
126
|
-
key={d}
|
|
127
|
-
title={`${what}: ${count.toLocaleString()}${last ? ' (keep at least one)' : ''}`}
|
|
128
|
-
aria-pressed={on}
|
|
129
|
-
disabled={last || (!on && !count)}
|
|
130
|
-
onClick={() => onDegreesChange(on ? degrees.filter((x) => x !== d) : [...degrees, d].sort())}
|
|
131
|
-
style={{
|
|
132
|
-
flex: 1, padding: '7px 4px', borderRadius: 8, fontSize: 11.5, fontWeight: 700,
|
|
133
|
-
cursor: last || (!on && !count) ? 'default' : 'pointer',
|
|
134
|
-
border: on ? '1px solid rgba(52,152,219,0.6)' : '1px solid rgba(255,255,255,0.08)',
|
|
135
|
-
background: on ? 'rgba(52,152,219,0.18)' : 'rgba(255,255,255,0.03)',
|
|
136
|
-
color: on ? '#cfe6f7' : count ? '#888' : '#444',
|
|
137
|
-
}}
|
|
138
|
-
>
|
|
139
|
-
{label}
|
|
140
|
-
<div style={{ fontSize: 9.5, fontWeight: 500, opacity: 0.8, marginTop: 2 }}>{count.toLocaleString()}</div>
|
|
141
|
-
</button>
|
|
142
|
-
);
|
|
143
|
-
})}
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
)}
|
|
147
|
-
|
|
148
|
-
{!isDegreesMode && visualMode === 'galaxy' && <NamesSwitch />}
|
|
188
|
+
{/* Which tiers, at which degrees: the same grid in Network Circle and in Degrees */}
|
|
189
|
+
<TierGrid grid={grid} counts={gridCounts} onChange={onGridChange} mode={isDegreesMode ? 'degrees' : 'network'} isMobile={isMobile} />
|
|
149
190
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
introduces you, not the person reached — Separation has its own
|
|
153
|
-
"Their tier" chips for that, so the two must not read alike. */}
|
|
154
|
-
<div style={{ fontSize: 9, fontWeight: 700, color: '#555', letterSpacing: 1, marginBottom: 8, textTransform: 'uppercase' }}>
|
|
155
|
-
{isDegreesMode ? 'Filter by bridge tier' : 'Filter'}
|
|
156
|
-
</div>
|
|
157
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
158
|
-
{/* All button */}
|
|
159
|
-
<button
|
|
160
|
-
onClick={() => (toggles ? onHiddenTiersChange([]) : onFilterChange && onFilterChange('all'))}
|
|
161
|
-
style={{
|
|
162
|
-
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
163
|
-
padding: '7px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', width: '100%',
|
|
164
|
-
background: (toggles ? !hiddenTiers.length : filter === 'all') ? 'rgba(255,255,255,0.12)' : 'rgba(255,255,255,0.03)',
|
|
165
|
-
color: (toggles ? !hiddenTiers.length : filter === 'all') ? '#fff' : '#888',
|
|
166
|
-
}}
|
|
167
|
-
>
|
|
168
|
-
<span style={{ fontSize: 11, fontWeight: 600 }}>All</span>
|
|
169
|
-
<span style={{ fontSize: 10, color: '#555' }}>{allCount}</span>
|
|
170
|
-
</button>
|
|
171
|
-
{/* Tier buttons */}
|
|
172
|
-
{toggles && (
|
|
173
|
-
<div style={{ fontSize: 10, color: '#667', margin: '2px 2px 4px' }}>Click a tier to hide or show it</div>
|
|
174
|
-
)}
|
|
175
|
-
{toggles && ['S', 'A', 'B', 'C', 'D'].map(t => {
|
|
176
|
-
const on = !hiddenTiers.includes(t);
|
|
177
|
-
const last = on && hiddenTiers.length >= 4;
|
|
178
|
-
return (
|
|
179
|
-
<button
|
|
180
|
-
key={t}
|
|
181
|
-
aria-pressed={on}
|
|
182
|
-
title={last ? 'Keep at least one tier showing' : on ? `Hide ${t}-Tier` : `Show ${t}-Tier`}
|
|
183
|
-
onClick={() => !last && onHiddenTiersChange(on ? [...hiddenTiers, t] : hiddenTiers.filter(x => x !== t))}
|
|
184
|
-
style={{
|
|
185
|
-
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
186
|
-
padding: '7px 10px', borderRadius: 6, border: 'none', cursor: last ? 'default' : 'pointer', width: '100%',
|
|
187
|
-
background: on ? `${TIER_COLORS[t]}14` : 'rgba(255,255,255,0.02)',
|
|
188
|
-
color: on ? TIER_COLORS[t] : '#555',
|
|
189
|
-
borderLeft: on ? `3px solid ${TIER_COLORS[t]}` : '3px solid transparent',
|
|
190
|
-
}}
|
|
191
|
-
>
|
|
192
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
193
|
-
<div style={{ width: 8, height: 8, borderRadius: '50%', background: TIER_COLORS[t], opacity: on ? 1 : 0.2 }} />
|
|
194
|
-
<span style={{ fontSize: 11, fontWeight: 600, textDecoration: on ? 'none' : 'line-through' }}>{t}-Tier</span>
|
|
195
|
-
{!on && <span style={{ fontSize: 9.5, color: '#667' }}>hidden</span>}
|
|
196
|
-
</div>
|
|
197
|
-
<span style={{ fontSize: 10, color: '#555' }}>{counts[t] || 0}</span>
|
|
198
|
-
</button>
|
|
199
|
-
);
|
|
200
|
-
})}
|
|
201
|
-
{!toggles && ['S', 'A', 'B', 'C', 'D'].map(t => (
|
|
202
|
-
<button
|
|
203
|
-
key={t}
|
|
204
|
-
onClick={() => onFilterChange && onFilterChange(t)}
|
|
205
|
-
style={{
|
|
206
|
-
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
207
|
-
padding: '7px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', width: '100%',
|
|
208
|
-
background: filter === t ? `${TIER_COLORS[t]}20` : 'rgba(255,255,255,0.03)',
|
|
209
|
-
color: filter === t ? TIER_COLORS[t] : '#888',
|
|
210
|
-
borderLeft: filter === t ? `3px solid ${TIER_COLORS[t]}` : '3px solid transparent',
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
213
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
|
214
|
-
<div style={{
|
|
215
|
-
width: 8, height: 8, borderRadius: '50%',
|
|
216
|
-
background: TIER_COLORS[t], opacity: filter === t ? 1 : 0.4,
|
|
217
|
-
}} />
|
|
218
|
-
<span style={{ fontSize: 11, fontWeight: 600 }}>{t}-Tier</span>
|
|
219
|
-
</div>
|
|
220
|
-
<span style={{ fontSize: 10, color: '#555' }}>{counts[t] || 0}</span>
|
|
221
|
-
</button>
|
|
222
|
-
))}
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
{/* The Galaxy's physics lab (experimental): sliders and a replay, on a computer. */}
|
|
227
|
-
{!isDegreesMode && visualMode === 'galaxy' && !isMobile && <GalaxyLab />}
|
|
228
|
-
|
|
229
|
-
{/* Stats */}
|
|
230
|
-
<div style={{ borderTop: '1px solid rgba(255,255,255,0.06)', paddingTop: 12 }}>
|
|
231
|
-
<div style={{ fontSize: 9, fontWeight: 700, color: '#555', letterSpacing: 1, marginBottom: 8, textTransform: 'uppercase' }}>
|
|
232
|
-
Stats
|
|
233
|
-
</div>
|
|
234
|
-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
|
|
235
|
-
{Object.entries(counts).filter(([k]) => ['S', 'A', 'B'].includes(k)).map(([tier, count]) => (
|
|
236
|
-
<div key={tier} style={{
|
|
237
|
-
padding: '6px 8px', borderRadius: 6,
|
|
238
|
-
background: `${TIER_COLORS[tier]}10`,
|
|
239
|
-
textAlign: 'center',
|
|
240
|
-
}}>
|
|
241
|
-
<div style={{ fontSize: 16, fontWeight: 800, color: TIER_COLORS[tier] }}>{count}</div>
|
|
242
|
-
<div style={{ fontSize: 8, color: '#555' }}>{tier}-Tier</div>
|
|
243
|
-
</div>
|
|
244
|
-
))}
|
|
245
|
-
<div style={{
|
|
246
|
-
padding: '6px 8px', borderRadius: 6,
|
|
247
|
-
background: 'rgba(255,255,255,0.04)',
|
|
248
|
-
textAlign: 'center',
|
|
249
|
-
}}>
|
|
250
|
-
<div style={{ fontSize: 16, fontWeight: 800, color: '#fff' }}>{allCount}</div>
|
|
251
|
-
<div style={{ fontSize: 8, color: '#555' }}>Total</div>
|
|
252
|
-
</div>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
191
|
+
{/* Network Circle's Galaxy: the physics that lays it out is what this panel is for. */}
|
|
192
|
+
{!isDegreesMode && visualMode === 'galaxy' && (isMobile ? <NamesSwitch /> : <GalaxyLab />)}
|
|
255
193
|
</div>
|
|
256
194
|
</div>
|
|
257
195
|
);
|
|
@@ -6,7 +6,7 @@ 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 } from '../../lib/galaxy-lab';
|
|
9
|
+
import { LAB_DEFAULTS, 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
11
|
|
|
12
12
|
// Connection fields are attacker-reachable: /api/ingest and /api/update-images
|
|
@@ -23,6 +23,7 @@ function esc(value) {
|
|
|
23
23
|
// The id of the node at the centre, you. Not a connection's id (those come from
|
|
24
24
|
// the database), and never shown.
|
|
25
25
|
const CENTER_ID = '__center__';
|
|
26
|
+
const HEAT_GLOWS = 700; // glows drawn under Colour by → Heat, hottest first
|
|
26
27
|
|
|
27
28
|
// Builds the avatar tooltip through the DOM rather than a string, so no value
|
|
28
29
|
// can break out of the attribute it is written into. Only a photo saved on this
|
|
@@ -510,6 +511,30 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
510
511
|
}
|
|
511
512
|
});
|
|
512
513
|
|
|
514
|
+
// Heat (Colour by → Heat): a soft glow behind the hotter dots, screen-blended
|
|
515
|
+
// so the glows add up where powerful people cluster, like a thermal camera.
|
|
516
|
+
// The hottest HEAT_GLOWS only, so a big network stays smooth.
|
|
517
|
+
let heatFn = scheme?.heat || null;
|
|
518
|
+
const heatDefs = svg.select('defs').size() ? svg.select('defs') : svg.append('defs');
|
|
519
|
+
for (let k = 0; k <= 5; k++) {
|
|
520
|
+
if (heatDefs.select(`#heat-glow-${k}`).size()) continue;
|
|
521
|
+
const grad = heatDefs.append('radialGradient').attr('id', `heat-glow-${k}`);
|
|
522
|
+
grad.append('stop').attr('offset', '0').attr('stop-color', heatColour(k / 5)).attr('stop-opacity', 0.14 + 0.06 * k);
|
|
523
|
+
grad.append('stop').attr('offset', '1').attr('stop-color', heatColour(k / 5)).attr('stop-opacity', 0);
|
|
524
|
+
}
|
|
525
|
+
const heatG = g.append('g').attr('class', 'heat-glow').style('pointer-events', 'none').style('mix-blend-mode', 'screen');
|
|
526
|
+
let heatDots = heatG.selectAll('circle');
|
|
527
|
+
const drawHeat = () => {
|
|
528
|
+
const hot = heatFn
|
|
529
|
+
? nodes.filter(n => n.id !== CENTER_ID && heatFn(n) >= 0.5).sort((a, b) => heatFn(b) - heatFn(a)).slice(0, HEAT_GLOWS)
|
|
530
|
+
: [];
|
|
531
|
+
heatDots = heatG.selectAll('circle').data(hot, d => d.id).join('circle')
|
|
532
|
+
.attr('r', d => nodeRadius(d) * 2.5 + 12 + 30 * heatFn(d))
|
|
533
|
+
.attr('fill', d => `url(#heat-glow-${Math.round(heatFn(d) * 5)})`)
|
|
534
|
+
.attr('cx', d => d.x || 0).attr('cy', d => d.y || 0)
|
|
535
|
+
.style('display', d => (clockAt == null || bornById.get(d.id) <= clockAt ? null : 'none'));
|
|
536
|
+
};
|
|
537
|
+
|
|
513
538
|
// forceLink has already swapped each link's ids for the nodes themselves, so
|
|
514
539
|
// the colour is one step away. It used to search every node for every link:
|
|
515
540
|
// about 1.8 s at 30,000 people.
|
|
@@ -612,8 +637,12 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
612
637
|
g.classed('lab-focus', true);
|
|
613
638
|
};
|
|
614
639
|
|
|
640
|
+
// Moving from one dot to the next shouldn't flash the whole Galaxy back on in
|
|
641
|
+
// between: the dimming lifts a moment after the pointer leaves, unless it lands on another.
|
|
642
|
+
let unlight = null;
|
|
615
643
|
node.on('mouseover', function (event, d) {
|
|
616
644
|
easeRadius(this, nodeRadius(d) * 1.5);
|
|
645
|
+
clearTimeout(unlight);
|
|
617
646
|
if (L.on && L.branch && d.id !== CENTER_ID) lightBranch(d);
|
|
618
647
|
if (d.profile_image_url && d.id !== CENTER_ID) {
|
|
619
648
|
const size = Math.max(48, nodeRadius(d) * 5);
|
|
@@ -642,7 +671,8 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
642
671
|
}
|
|
643
672
|
}).on('mouseout', function (event, d) {
|
|
644
673
|
easeRadius(this, nodeRadius(d));
|
|
645
|
-
|
|
674
|
+
clearTimeout(unlight);
|
|
675
|
+
unlight = setTimeout(() => g.classed('lab-focus', false), 120);
|
|
646
676
|
tooltip.style('opacity', 0);
|
|
647
677
|
photoTooltip.style('opacity', 0);
|
|
648
678
|
});
|
|
@@ -686,6 +716,7 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
686
716
|
catalystRings.attr('cx', d => d.x).attr('cy', d => d.y);
|
|
687
717
|
dotRings.attr('transform', d => `translate(${d.x},${d.y})`);
|
|
688
718
|
labels.attr('x', d => d.x).attr('y', d => d.y);
|
|
719
|
+
if (heatFn) heatDots.attr('cx', d => d.x).attr('cy', d => d.y);
|
|
689
720
|
placeRing();
|
|
690
721
|
});
|
|
691
722
|
|
|
@@ -735,7 +766,12 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
735
766
|
const dated = d1.map(n => bornById.get(n.id)).filter(Number.isFinite);
|
|
736
767
|
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 };
|
|
737
768
|
let clockAt = null;
|
|
738
|
-
const showBorn = () =>
|
|
769
|
+
const showBorn = () => {
|
|
770
|
+
const born = d => (clockAt == null || bornById.get(d.id) <= clockAt ? null : 'none');
|
|
771
|
+
labels.style('display', born);
|
|
772
|
+
if (heatFn) heatDots.style('display', born);
|
|
773
|
+
};
|
|
774
|
+
drawHeat();
|
|
739
775
|
const setTime = (at) => {
|
|
740
776
|
if (!L.on) at = null;
|
|
741
777
|
clockAt = at;
|
|
@@ -763,6 +799,8 @@ function renderNetworkMode(svg, ring, box, connections, onSelect, tierColors, fo
|
|
|
763
799
|
// A new colour scheme (Colour by): recolour the dots, lines and names in place.
|
|
764
800
|
const setColours = (next) => {
|
|
765
801
|
colourOf = next.of;
|
|
802
|
+
heatFn = next.heat || null;
|
|
803
|
+
drawHeat();
|
|
766
804
|
node.attr('fill', d => d.id === CENTER_ID ? '#fff' : colourOf(d));
|
|
767
805
|
link.attr('stroke', d => colourOf(d.target) || '#333');
|
|
768
806
|
drawLabels();
|