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
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,193 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-10-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Rank titles your way** (Settings → Scores → Titles). Set how much each kind of title counts, out
|
|
13
|
+
of 10, or start from a preset: Founders first, Investors, or A role I'm looking for. Add the roles
|
|
14
|
+
you're after ("account executive", "head of growth", "recruiter") with their own points, and
|
|
15
|
+
everyone whose title has those words rises, whatever their level: for outbound to one role, or
|
|
16
|
+
hiring for it. Saving rescores everyone and says how many people changed tier, and a card's score
|
|
17
|
+
says "your ranking" where it moved a title. Nothing set scores exactly as before.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **The Scan page is a launch, not a manual.** It opens with what the scanner is and what it never does
|
|
21
|
+
(never posts or messages anyone, never sees your password, nothing leaves this Mac, stops when you
|
|
22
|
+
say), then the four steps on a rail: Get ready, Sign in, Who you know, Who they know. The one
|
|
23
|
+
you're on pulses, with one big button that does it and how long it takes; any other opens with a
|
|
24
|
+
tap. While it runs, "Your scanner is working" and Stop sit right under the step. How many people
|
|
25
|
+
this round and who first are beside the radar; the daily budget, how much of each list, the hidden
|
|
26
|
+
window, retrying hidden lists and paused lists are in Fine-tune, closed until you want them. The
|
|
27
|
+
log is one tap away, and opens by itself when a run stops badly. Scan has the same header as every
|
|
28
|
+
page now.
|
|
29
|
+
- **Paths reads like Network Circle.** Its notch is now Map and Companies: companies count for more
|
|
30
|
+
than industries, and an industry opens from a company's card (its industry is a link there). Whether
|
|
31
|
+
the map's bubbles are companies or your connections (what was the People tab) is a switch at the
|
|
32
|
+
top of the Filters panel. Filters use the same tiers × degrees grid as Network Circle. Colour is by
|
|
33
|
+
sector or by **Heat**: for companies, how strong your people there are (the average power of the
|
|
34
|
+
five strongest); for people, how strong their cluster is, person for person; size still says how
|
|
35
|
+
many. And **Physics**: switch it to Live to drag bubbles (the ones joined to them follow) and change
|
|
36
|
+
Spread, Pull to their group and Pull along lines. Still, as before, is the default. Old links to
|
|
37
|
+
the People or Industries tab still land on the map.
|
|
38
|
+
- **Heat: power as a thermal map.** A new Colour by in Network Circle's Physics: every dot coloured
|
|
39
|
+
from cold violet through red and orange to white hot by where their power score ranks among the
|
|
40
|
+
people showing, with a soft glow behind the hotter ones that pools where powerful people cluster,
|
|
41
|
+
like a thermal camera. Works with every layout, the replay and the filter grid.
|
|
42
|
+
- **Separation all the way right counts every mutual, scanned or not.** It aims at whoever you share
|
|
43
|
+
the most mutual connections with, even when most of them are in circles you haven't scanned: an
|
|
44
|
+
unscanned mutual is still a likely introduction, so it no longer holds someone back. The ones the
|
|
45
|
+
app can draw are lines as before; the rest are grey dots, each with a faint line in, with "+37 more
|
|
46
|
+
mutuals, in circles not scanned yet". The person glows less the less of it is scanned. The Next
|
|
47
|
+
names are in the same order and say their mutual counts. Narrow the tiers (S only, say) first to
|
|
48
|
+
aim at the strongest.
|
|
49
|
+
- **The right panel follows the circle you're in.** Open someone's circle in Bridge Chains and the
|
|
50
|
+
panel is about them: how many people are in their circle, tier chips to see just one tier, a
|
|
51
|
+
search inside it, the people you added from it (with Scan… for the ones ready), then everyone
|
|
52
|
+
else in it, strongest first. Click anyone for their card.
|
|
53
|
+
- **Orbit is a layout of the Galaxy now**, in Physics next to Rings and Clusters, not a separate
|
|
54
|
+
view: every tier held on its own orbit, S nearest you, and each connection's circle tucked in
|
|
55
|
+
behind them. It's still the Galaxy, so you can drag dots and move every slider on it.
|
|
56
|
+
- **Light up a branch on hover starts off.** It dimmed and relit thousands of dots on every hover,
|
|
57
|
+
which flickered; it's still in Physics if you want it, and moving from one dot to the next no
|
|
58
|
+
longer flashes the whole Galaxy in between.
|
|
59
|
+
- **Social is part of Outlink.** Outlink has the same header as every page, and its views sit in
|
|
60
|
+
the notch: Circles, To add, Pending, and Messages & follow-ups, which is the whole Social tab (your
|
|
61
|
+
LinkedIn export, the live messages sync, the CRM with its inbox, follow-ups, pipeline, sent and
|
|
62
|
+
received). Adding people and keeping track of them are now one place. The Social tab is gone from
|
|
63
|
+
the header, and old links to it land on Messages & follow-ups.
|
|
64
|
+
- **Notifications open.** Click one and it takes over the right panel with the detail: who it is
|
|
65
|
+
about, their tier and score, who you met them through, and, for an S or A, that they're a
|
|
66
|
+
valuable person to know; with buttons to open their card, their circle in Bridge Chains, or
|
|
67
|
+
them in Separation. A notification naming several people lists them all.
|
|
68
|
+
- **New notifications:** someone you met through a bridge added you back ("Ana added you back,
|
|
69
|
+
through Yuki"); a circle scan found people at top companies (a company score of 8 or more), named
|
|
70
|
+
in one notification; and a scan finished ("Scan done: Yuki's circle", "Check for new: done").
|
|
71
|
+
Accepted requests and high-value new connections now say who they are about, and older ones are
|
|
72
|
+
matched by the name in their title.
|
|
73
|
+
- **Paths has the same header as the map.** The app's tabs stay at the top, with Paths lit, and Map,
|
|
74
|
+
People, Industries and Companies sit in the notch under them, as the map page's views do; a tab
|
|
75
|
+
for Network Circle, Degrees or Separation takes you straight to it. Paths' filters moved into a
|
|
76
|
+
Filters panel on the left, like the map page's, with new ways to read the map: what a bubble's
|
|
77
|
+
size means (your people there, the S and A among them, or the ones you already know; on People,
|
|
78
|
+
cluster value, size or S tier) and which names show (the biggest, all, or none).
|
|
79
|
+
- **All the way to easy, Separation aims at whoever the most of your connections lead to**, with
|
|
80
|
+
every one of those lines drawn, the strongest first among equals; the "Next" names are the
|
|
81
|
+
runners-up by the same measure. It used to aim at the top of the list, which could be someone
|
|
82
|
+
with many mutual connections by LinkedIn's count but only one the app could draw.
|
|
83
|
+
- **The Scan page has a radar, and a speed: Slow, Medium or Fast.** Mapping the 2nd degree starts
|
|
84
|
+
from a round Scan button in the middle of a radar: its sweep turns while a scan runs, and the
|
|
85
|
+
ring round it fills with today's searches against your budget. Beside it, three speeds. Fast is
|
|
86
|
+
how the scanner has always run, and nothing is faster; Medium waits 45 seconds before each page
|
|
87
|
+
of results and 3 minutes after every 10, Slow 90 seconds and 5 minutes, and both keep profiles
|
|
88
|
+
further apart (90 and 120 seconds). Each says about how many searches an hour it makes and how
|
|
89
|
+
long the rest of today's budget would take. The daily budget stays the cap at every speed:
|
|
90
|
+
slower spreads it out, it doesn't shrink it. The speed is saved with the budget and the scanner
|
|
91
|
+
says which it is using at the start of every run; the Scan page's time estimates follow it.
|
|
92
|
+
- **Scores moved into Settings.** Your field, how tiers are graded and every company's score are a
|
|
93
|
+
Scores section in Settings (the gear), and the Scores tab is gone from the header. Old links to
|
|
94
|
+
the Scores tab land in the same place in Settings.
|
|
95
|
+
- **Profile has Insights.** An Insights button beside Profile shows network health: how much of
|
|
96
|
+
your 2nd degree you reach two or more ways, the effective reach of your connections, and the
|
|
97
|
+
connections who reach the most people no one else does. It was at the top of the Scores tab.
|
|
98
|
+
- **Paths → People.** A new view beside the company map, drawn the same way but with your
|
|
99
|
+
connections as the bubbles: each is sized by the value of the cluster behind them (S tier counts
|
|
100
|
+
3, A tier 2, everyone else 1; or by cluster size, or by S tier inside), coloured by their sector
|
|
101
|
+
and grouped like the companies. A white centre is the share of their cluster you can only reach
|
|
102
|
+
through them, a gold ring means S-tier people inside, and a line joins two connections whose
|
|
103
|
+
clusters share people. Point at someone for their numbers and how their cluster compares to the
|
|
104
|
+
rest you've scanned; click to open it in Bridge Chains. Shows the connections you've scanned,
|
|
105
|
+
or all of them, with the ones not scanned yet as small grey dots.
|
|
106
|
+
- **Separation's map draws connections as pills and people as cards** on a computer (a phone keeps
|
|
107
|
+
the dots). Each connection shows their initials, tier and what they are to the people on the
|
|
108
|
+
map: the only door to someone, the best way in for some, or only another way in; the ones with
|
|
109
|
+
a solid line are lit. Each person is a card with rank, role, score and how many ways in. With
|
|
110
|
+
the slider all the way to easy, the one person becomes a large card with LinkedIn's mutual
|
|
111
|
+
count and how many of those are drawn. Everything still glides as the slider moves.
|
|
112
|
+
- **Separation's list is a ledger.** A title bar says how many are shown and that the order
|
|
113
|
+
follows the slider. The way in shows the connection's face with "only way in" or how many more
|
|
114
|
+
ways there are under it, a new column shows how rare the way in is as a bar (full and cyan for
|
|
115
|
+
one door, short and orange for the warmest), and the power score sits beside its bar.
|
|
116
|
+
- **Separation is a tab of its own**, beside Degrees in the header, instead of a view inside
|
|
117
|
+
Degrees. It uses the same Filters grid as Degrees. Degrees keeps Bridge Chains, Pyramid and List.
|
|
118
|
+
- **Separation has a slider.** One slider reorders everyone as you drag it: the rarest ways in
|
|
119
|
+
at one end (strong people only one of your connections knows), the easiest at the other
|
|
120
|
+
(strong people you share the most mutual connections with), and each person's own score alone
|
|
121
|
+
in the middle, as the list has always been. A line under it says in words what it is
|
|
122
|
+
surfacing. It only reorders: scores, tiers and ranks stay as they are, and it claims no odds.
|
|
123
|
+
Double-click the slider, or tap 50, to go back to the middle. It replaces the Power / Ways in
|
|
124
|
+
switch. The tier chips and the rarity chips ("Only show") sit in the same card.
|
|
125
|
+
- **Separation's map follows the slider, and glides.** From the rare end to the middle it fans out
|
|
126
|
+
to ten people, each with the one or two doors that lead to them. Towards the easy end it
|
|
127
|
+
closes in on fewer people, and at the end on one: the person you're aiming at, with rings
|
|
128
|
+
round them and every connection of yours who leads to them drawn in, the top-scored one solid.
|
|
129
|
+
As the slider moves, people, connections and lines slide to their new places and newcomers
|
|
130
|
+
fade in (still, with Reduce Motion on). Pick anyone in the list, or one of the "Next" names
|
|
131
|
+
under the map, to aim at them instead. Beside each name it says how many mutual connections
|
|
132
|
+
lead to them: "only way in", "3 ways in", or, where LinkedIn's own count is higher than the
|
|
133
|
+
connections the app can draw, "37 mutuals · 6 mapped".
|
|
134
|
+
- **Path, in Separation.** A Path button beside the tier chips narrows everyone to one sector or
|
|
135
|
+
one company. The picker opens under the button with the sectors (inferred, as in Paths), the
|
|
136
|
+
biggest companies and a box to type in, and goes away once you've picked; the button then
|
|
137
|
+
shows your pick with a × to clear it.
|
|
138
|
+
- **Separation's heading no longer sits under the notch.**
|
|
139
|
+
- **One filter for tiers and degrees, the same in Network Circle and in Degrees.** The Filters
|
|
140
|
+
panel shows the five tiers as bigger rows, each with six dots, one for each degree. Tap a tier
|
|
141
|
+
to hide or show it, as before. Tap a dot to show or hide that degree for that tier alone:
|
|
142
|
+
S tier at 2nd degree without everyone else's, say. Tap a number at the top to switch a degree
|
|
143
|
+
for every tier at once. A dot with nobody behind it is faint and can't be tapped, each row
|
|
144
|
+
says how many people it is showing, and the last people on screen can't be switched off.
|
|
145
|
+
Network Circle starts with your own connections; Degrees starts with everything. In Degrees
|
|
146
|
+
the rows used to pick one bridge tier at a time; now 1st degree is your bridges and 2nd is
|
|
147
|
+
the people in their circles, each by their own tier.
|
|
148
|
+
- **The Galaxy's physics is the main thing in Network Circle's Filters panel.** The sliders,
|
|
149
|
+
layouts, colours, find and replay sit straight in the panel under the tiers, always on, with
|
|
150
|
+
no switch to turn on first and no "experimental" box. With no slider moved the Galaxy is laid
|
|
151
|
+
out as before; pointing at a dot now lights up its branch unless you turn that off.
|
|
152
|
+
- **Pyramid and List left Network Circle.** Its views are the Galaxy and Orbit. Pyramid and List
|
|
153
|
+
stay in Degrees.
|
|
154
|
+
- **An opened circle shows the chains that lead on from it.** Anyone you added from that circle
|
|
155
|
+
and then scanned keeps their dot in the circle, and the cluster their scan formed sits outside
|
|
156
|
+
it, joined to that dot by a line, so you can see where each path came from. Inside a cluster,
|
|
157
|
+
anyone you added and scanned in turn has their own dot marked and a line on to their cluster,
|
|
158
|
+
for as far as your scans reach. Every dot in a cluster is joined to its middle by a faint line.
|
|
159
|
+
Each cluster says whose circle it is, how many are in it and which degree that is. Point at
|
|
160
|
+
one and the way back to the middle lights up; click and it opens with the whole trail. The
|
|
161
|
+
people you added sit together on the side the chains lead off to, their names taking turns
|
|
162
|
+
above and below so they don't overlap, and the view zooms to fit when it opens.
|
|
163
|
+
- **Bridge Chains shows the bridges you connected with yourself.** Someone you met through a
|
|
164
|
+
bridge's circle is a link in that bridge's chain, not a bridge of their own: they're inside its
|
|
165
|
+
circle.
|
|
166
|
+
- **A bridge's ring now counts the people you added through it.** The ring sits tight on the
|
|
167
|
+
dot, with one bar for each person: orange once they've been scanned and have a cluster of
|
|
168
|
+
their own, green while they're ready for a scan. A small green number at the top right says
|
|
169
|
+
how many are ready, in place of the "ready" label. How much of a bridge's own list is scanned
|
|
170
|
+
is in its tooltip.
|
|
171
|
+
- **A crowded Bridge Chains is easier to read.** When your bridges need more than one ring, the
|
|
172
|
+
rings sit further apart, each with its own guide line, and the counts under each name wait
|
|
173
|
+
until you point at the bridge.
|
|
174
|
+
- **Orbit moved to Network Circle.** It shows the size of every circle at once, which is a
|
|
175
|
+
picture of the network, not a way to follow a chain. The corner switch in Degrees is gone.
|
|
176
|
+
- **Views are in the notch, under the tabs.** Galaxy, Orbit, Pyramid and List in Network Circle,
|
|
177
|
+
and Bridge Chains, Separation, Pyramid and List in Degrees, switch from the notch that hangs
|
|
178
|
+
under the top tabs, instead of a list at the top of the Filters panel. A scan's progress
|
|
179
|
+
shows in the same notch, beside them. The Filters panel now only filters.
|
|
180
|
+
|
|
181
|
+
### Fixed
|
|
182
|
+
- The scanner's log, shown on the Scan page, says "scanning" throughout; a few lines still said the
|
|
183
|
+
old word.
|
|
184
|
+
- The "Scanning…" dot in the right panel pulses again; the animation it named was never defined.
|
|
185
|
+
- **Separation's map lines now meet every box.** Each connection sits level with the people it
|
|
186
|
+
leads to, so a connection with one person is on that person's row and the line between them runs
|
|
187
|
+
straight; lines start and end exactly on the pill and the card, with a small dot where they plug in.
|
|
188
|
+
- **"Hide the Chrome window while scanning" did nothing for scans started on the Scan page.**
|
|
189
|
+
Its own buttons (Scan, Check for new, Auto scan and the rest) sent their request without the
|
|
190
|
+
setting, so Chrome opened anyway; only scans started from a person's card or Resume were hidden.
|
|
191
|
+
Every scan start now goes through one place that adds it.
|
|
192
|
+
- **"Only way in" could show beside someone with two ways in drawn.** Rarity trusted the mutual
|
|
193
|
+
count a scan saved even when the app had since found them through more of your connections
|
|
194
|
+
than that. It now takes whichever is larger.
|
|
195
|
+
|
|
9
196
|
## [0.4.11] - 2026-09-29
|
|
10
197
|
|
|
11
198
|
### Added
|
package/lib/chain-layout.js
CHANGED
|
@@ -17,19 +17,19 @@ function capacity(radius, spacing, sweep) {
|
|
|
17
17
|
return Math.max(1, sweep >= TAU - 1e-9 ? Math.floor(arc / spacing) : Math.floor(arc / spacing) + 1);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** The rings from `inner` outwards, `
|
|
21
|
-
function ringsFor(n, inner, spacing, sweep) {
|
|
20
|
+
/** The rings from `inner` outwards, `gap` apart (at least `spacing`), until `n` dots fit. */
|
|
21
|
+
function ringsFor(n, inner, spacing, sweep, gap = spacing) {
|
|
22
22
|
const radii = [];
|
|
23
23
|
let room = 0;
|
|
24
|
-
for (let r = inner; room < n; r += spacing) {
|
|
24
|
+
for (let r = inner; room < n; r += Math.max(spacing, gap)) {
|
|
25
25
|
radii.push(r);
|
|
26
26
|
room += capacity(r, spacing, sweep);
|
|
27
27
|
}
|
|
28
28
|
return radii;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const outerOf = (n, inner, spacing, sweep) => {
|
|
32
|
-
const radii = ringsFor(n, inner, spacing, sweep);
|
|
31
|
+
const outerOf = (n, inner, spacing, sweep, gap) => {
|
|
32
|
+
const radii = ringsFor(n, inner, spacing, sweep, gap);
|
|
33
33
|
return radii[radii.length - 1];
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -46,43 +46,44 @@ const outerOf = (n, inner, spacing, sweep) => {
|
|
|
46
46
|
* neighbouring rings don't line up in spokes.
|
|
47
47
|
*
|
|
48
48
|
* `start` and `sweep` fill an arc instead of the whole circle, each ring from
|
|
49
|
-
* its middle outwards.
|
|
49
|
+
* its middle outwards. `ringGap` keeps the rings at least that far apart, for
|
|
50
|
+
* dots that carry a name under them and need the room.
|
|
50
51
|
*
|
|
51
52
|
* @returns {{ spacing: number, inner: number, rings: { radius: number, count: number }[],
|
|
52
53
|
* points: { x: number, y: number, angle: number, ring: number }[] }}
|
|
53
54
|
*/
|
|
54
55
|
export function ringLayout(n, {
|
|
55
56
|
inner, outer, spacing, minSpacing = spacing / 3, innerMin = inner,
|
|
56
|
-
start = -Math.PI / 2, sweep = TAU,
|
|
57
|
+
start = -Math.PI / 2, sweep = TAU, ringGap = 0,
|
|
57
58
|
} = {}) {
|
|
58
59
|
const count = Math.max(0, Math.floor(n) || 0);
|
|
59
60
|
if (!count) return { spacing, inner, rings: [], points: [] };
|
|
60
61
|
|
|
61
62
|
let s = spacing;
|
|
62
63
|
let r0 = inner;
|
|
63
|
-
if (outerOf(count, r0, s, sweep) > outer) {
|
|
64
|
+
if (outerOf(count, r0, s, sweep, ringGap) > outer) {
|
|
64
65
|
// Start further in, as far as innerMin, before closing anything up.
|
|
65
|
-
if (outerOf(count, innerMin, s, sweep) <= outer) {
|
|
66
|
+
if (outerOf(count, innerMin, s, sweep, ringGap) <= outer) {
|
|
66
67
|
let lo = innerMin, hi = inner;
|
|
67
68
|
for (let i = 0; i < 24; i++) {
|
|
68
69
|
const mid = (lo + hi) / 2;
|
|
69
|
-
if (outerOf(count, mid, s, sweep) <= outer) lo = mid; else hi = mid;
|
|
70
|
+
if (outerOf(count, mid, s, sweep, ringGap) <= outer) lo = mid; else hi = mid;
|
|
70
71
|
}
|
|
71
72
|
r0 = lo;
|
|
72
73
|
} else {
|
|
73
74
|
r0 = innerMin;
|
|
74
75
|
let lo = minSpacing, hi = spacing;
|
|
75
|
-
if (outerOf(count, r0, lo, sweep) <= outer) {
|
|
76
|
+
if (outerOf(count, r0, lo, sweep, ringGap) <= outer) {
|
|
76
77
|
for (let i = 0; i < 24; i++) {
|
|
77
78
|
const mid = (lo + hi) / 2;
|
|
78
|
-
if (outerOf(count, r0, mid, sweep) <= outer) lo = mid; else hi = mid;
|
|
79
|
+
if (outerOf(count, r0, mid, sweep, ringGap) <= outer) lo = mid; else hi = mid;
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
s = lo;
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
const radii = ringsFor(count, r0, s, sweep);
|
|
86
|
+
const radii = ringsFor(count, r0, s, sweep, ringGap);
|
|
86
87
|
const caps = radii.map((r) => capacity(r, s, sweep));
|
|
87
88
|
const room = caps.reduce((a, b) => a + b, 0);
|
|
88
89
|
|
|
@@ -162,8 +163,9 @@ const TIER_BANDS = ['S', 'A', 'B', 'C', 'D'];
|
|
|
162
163
|
*
|
|
163
164
|
* @returns {{ spacing: number, edge: number, bands: { tier: string, inner: number, outer: number }[],
|
|
164
165
|
* points: { x: number, y: number, angle: number, slot: number }[] }}
|
|
166
|
+
* `start` is the angle each ring's first dot sits at (the top, unless told).
|
|
165
167
|
*/
|
|
166
|
-
export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spacing / 3, gap = spacing }) {
|
|
168
|
+
export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spacing / 3, gap = spacing, start = -Math.PI / 2 }) {
|
|
167
169
|
const groups = TIER_BANDS.map((tier) => ({ tier, idx: [] }));
|
|
168
170
|
rows.forEach((row, i) => {
|
|
169
171
|
const t = TIER_BANDS.indexOf(row?.tier);
|
|
@@ -175,7 +177,7 @@ export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spaci
|
|
|
175
177
|
const bands = [];
|
|
176
178
|
let r = inner;
|
|
177
179
|
for (const g of used) {
|
|
178
|
-
const l = ringLayout(g.idx.length, { inner: r, outer: Infinity, spacing: s, minSpacing: s });
|
|
180
|
+
const l = ringLayout(g.idx.length, { inner: r, outer: Infinity, spacing: s, minSpacing: s, start });
|
|
179
181
|
g.idx.forEach((rowIndex, j) => {
|
|
180
182
|
const p = l.points[j];
|
|
181
183
|
points[rowIndex] = { x: p.x, y: p.y, angle: p.angle, slot: TAU / l.rings[p.ring].count };
|
|
@@ -201,28 +203,96 @@ export function tierBandLayout(rows, { inner, outer, spacing, minSpacing = spaci
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
/**
|
|
204
|
-
* The
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* behind its own person. At most `cap` dots a fan and about `total` in all, so
|
|
208
|
-
* a big network stays quick; `more` is how many weren't drawn.
|
|
206
|
+
* The chains that lead on from an opened circle (Blake, 2026-10-02: "once they
|
|
207
|
+
* are clicked on and can view the cluster i want any scanned d2,3,4 or whatever
|
|
208
|
+
* to be show inside with lines out of the d1 leading to the d2s").
|
|
209
209
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
210
|
+
* `parents[i]` is the index of the link that introduced link i, or -1 when the
|
|
211
|
+
* person in the middle did. The links one step on sit on a ring at `from`, each
|
|
212
|
+
* as near its own angle in `angles` as it can get (where that person's dot is
|
|
213
|
+
* in the circle), or fanned about `toward` without one; each later step sits `hop` further out,
|
|
214
|
+
* as near the angle of the link it came from as it can get without touching a
|
|
215
|
+
* neighbour on its own ring (`spacing` apart). So a chain reads outwards from
|
|
216
|
+
* the middle: the bridge, someone met through them, someone met through *them*.
|
|
217
|
+
*
|
|
218
|
+
* @param {number[]} parents
|
|
219
|
+
* @returns {{ rings: { radius: number, count: number, depth: number }[],
|
|
220
|
+
* points: { x: number, y: number, angle: number, radius: number, depth: number, parent: number }[] }}
|
|
212
221
|
*/
|
|
213
|
-
export function
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
export function chainTree(parents = [], { from = 0, hop = 60, spacing = 46, toward = 0, angles = [] } = {}) {
|
|
223
|
+
const n = parents.length;
|
|
224
|
+
const parent = parents.map((p, i) => (Number.isInteger(p) && p >= 0 && p < n && p !== i ? p : -1));
|
|
225
|
+
|
|
226
|
+
// How many steps out each one is. A loop (it shouldn't happen) is cut where
|
|
227
|
+
// it closes, so everyone still gets a place.
|
|
228
|
+
const depth = new Array(n).fill(0);
|
|
229
|
+
for (let i = 0; i < n; i++) {
|
|
230
|
+
const trail = [];
|
|
231
|
+
let k = i;
|
|
232
|
+
while (k >= 0 && !depth[k] && !trail.includes(k)) { trail.push(k); k = parent[k]; }
|
|
233
|
+
if (k >= 0 && !depth[k]) { parent[trail[trail.length - 1]] = -1; k = -1; }
|
|
234
|
+
let d = k >= 0 ? depth[k] + 1 : 1;
|
|
235
|
+
for (let j = trail.length - 1; j >= 0; j--) depth[trail[j]] = d++;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const points = new Array(n);
|
|
239
|
+
const rings = [];
|
|
240
|
+
for (let d = 1; d <= Math.max(0, ...depth); d++) {
|
|
241
|
+
const here = [];
|
|
242
|
+
for (let i = 0; i < n; i++) if (depth[i] === d) here.push(i);
|
|
243
|
+
const radius = from + (d - 1) * hop;
|
|
244
|
+
const placed = spread(here.map((i) => (parent[i] < 0 ? (Number.isFinite(angles[i]) ? angles[i] : toward) : points[parent[i]].angle)),
|
|
245
|
+
Math.min(spacing / Math.max(1, radius), TAU / here.length));
|
|
246
|
+
here.forEach((i, j) => {
|
|
247
|
+
const angle = placed[j];
|
|
248
|
+
points[i] = { x: radius * Math.cos(angle), y: radius * Math.sin(angle), angle, radius, depth: d, parent: parent[i] };
|
|
225
249
|
});
|
|
226
|
-
|
|
250
|
+
rings.push({ radius, count: here.length, depth: d });
|
|
251
|
+
}
|
|
252
|
+
return { rings, points };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Angles as near the ones asked for as they can be while staying `sep` apart:
|
|
257
|
+
* neighbours that would touch move apart by the same amount each way, so two
|
|
258
|
+
* people met through the same person sit either side of them. Comes back in
|
|
259
|
+
* the order given.
|
|
260
|
+
*/
|
|
261
|
+
function spread(wanted, sep) {
|
|
262
|
+
const m = wanted.length;
|
|
263
|
+
if (m < 2) return wanted.slice();
|
|
264
|
+
const turn = (a) => ((a % TAU) + TAU) % TAU;
|
|
265
|
+
const order = wanted.map((a, i) => i).sort((i, j) => turn(wanted[i]) - turn(wanted[j]) || i - j);
|
|
266
|
+
// Start counting just after the widest empty stretch, so no group straddles the join.
|
|
267
|
+
let cut = 0;
|
|
268
|
+
let widest = -1;
|
|
269
|
+
order.forEach((i, k) => {
|
|
270
|
+
const next = order[(k + 1) % m];
|
|
271
|
+
const gap = turn(turn(wanted[next]) - turn(wanted[i])) || (m > 1 && k === m - 1 ? TAU : 0);
|
|
272
|
+
if (gap > widest) { widest = gap; cut = (k + 1) % m; }
|
|
227
273
|
});
|
|
274
|
+
const seq = order.slice(cut).concat(order.slice(0, cut));
|
|
275
|
+
const first = turn(wanted[seq[0]]);
|
|
276
|
+
const at = seq.map((i) => first + turn(turn(wanted[i]) - first));
|
|
277
|
+
|
|
278
|
+
// Least movement that keeps them `sep` apart: take the spacing out, pool any
|
|
279
|
+
// neighbours left out of order to their average, put the spacing back.
|
|
280
|
+
const blocks = [];
|
|
281
|
+
at.forEach((a, k) => {
|
|
282
|
+
blocks.push({ sum: a - k * sep, count: 1 });
|
|
283
|
+
while (blocks.length > 1 && blocks[blocks.length - 2].sum / blocks[blocks.length - 2].count > blocks[blocks.length - 1].sum / blocks[blocks.length - 1].count) {
|
|
284
|
+
const last = blocks.pop();
|
|
285
|
+
blocks[blocks.length - 1].sum += last.sum;
|
|
286
|
+
blocks[blocks.length - 1].count += last.count;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
let placed = blocks.flatMap((b) => new Array(b.count).fill(b.sum / b.count)).map((q, k) => q + k * sep);
|
|
290
|
+
// Round the join too: if the last has come back round onto the first, share the ring evenly.
|
|
291
|
+
if (placed[m - 1] - placed[0] > TAU - sep) {
|
|
292
|
+
const mid = (placed[0] + placed[m - 1]) / 2;
|
|
293
|
+
placed = placed.map((_, k) => mid + (k - (m - 1) / 2) * (TAU / m));
|
|
294
|
+
}
|
|
295
|
+
const out = new Array(m);
|
|
296
|
+
seq.forEach((i, k) => { out[i] = placed[k]; });
|
|
297
|
+
return out;
|
|
228
298
|
}
|
package/lib/degrees.js
CHANGED
|
@@ -22,9 +22,3 @@ export function peopleByDegree(degree1 = [], degree2 = [], degree3 = []) {
|
|
|
22
22
|
};
|
|
23
23
|
return { 1: take(degree1, 1), 2: take(degree2, 2), 3: take(degree3, 3) };
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
export function tierCountsOf(rows = []) {
|
|
27
|
-
const counts = {};
|
|
28
|
-
for (const row of rows) counts[row.tier] = (counts[row.tier] || 0) + 1;
|
|
29
|
-
return counts;
|
|
30
|
-
}
|
package/lib/dot-rings.js
CHANGED
|
@@ -18,4 +18,29 @@ export function ringSegments(r) {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The ring round a bridge in Bridge Chains (Blake, 2026-10-02: "the bars on the
|
|
23
|
+
* ring should signify how many dots in the clusters are ready to be scanned
|
|
24
|
+
* and new clusters formed"). One bar for each person you added through that
|
|
25
|
+
* circle: the ones scanned since, with a cluster of their own (`formed`),
|
|
26
|
+
* then the ones waiting for a scan (`ready`). Past `max` bars the two kinds
|
|
27
|
+
* share them in proportion, each keeping at least one.
|
|
28
|
+
*
|
|
29
|
+
* @returns {{ d: string, kind: 'formed' | 'ready' }[]}
|
|
30
|
+
*/
|
|
31
|
+
export function reachSegments(r, { formed = 0, ready = 0 } = {}, max = 12) {
|
|
32
|
+
const total = Math.max(0, formed) + Math.max(0, ready);
|
|
33
|
+
if (!total) return [];
|
|
34
|
+
const n = Math.min(total, max);
|
|
35
|
+
let nFormed = formed > 0 ? Math.max(1, Math.round((n * formed) / total)) : 0;
|
|
36
|
+
if (ready > 0 && nFormed >= n) nFormed = n - 1;
|
|
37
|
+
const gap = n === 1 ? 0.5 : Math.min(GAP, ((2 * Math.PI) / n) * 0.35);
|
|
38
|
+
const span = (2 * Math.PI) / n - gap;
|
|
39
|
+
const p = (a) => `${(r * Math.cos(a)).toFixed(2)} ${(r * Math.sin(a)).toFixed(2)}`;
|
|
40
|
+
return Array.from({ length: n }, (_, i) => {
|
|
41
|
+
const a0 = -Math.PI / 2 + gap / 2 + i * (span + gap);
|
|
42
|
+
return { kind: i < nFormed ? 'formed' : 'ready', d: `M${p(a0)} A${r} ${r} 0 ${span > Math.PI ? 1 : 0} 1 ${p(a0 + span)}` };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
export const RING = { filled: '#1abc9c', empty: 'rgba(255,255,255,0.16)', badge: '#7F77DD' };
|
package/lib/galaxy-lab.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// which isn't. Both follow useSyncExternalStore's rules: a snapshot only changes
|
|
8
8
|
// when something in it does.
|
|
9
9
|
|
|
10
|
-
import { keyFor } from './separation.js';
|
|
10
|
+
import { keyFor, score } from './separation.js';
|
|
11
11
|
import { warmthOf } from './linkedin-export.js';
|
|
12
12
|
|
|
13
13
|
const KEY = 'six-degrees-galaxy-lab';
|
|
@@ -15,7 +15,7 @@ const LAYOUTS_KEY = 'six-degrees-galaxy-layouts';
|
|
|
15
15
|
|
|
16
16
|
/** The layout as it has always been. Every slider is a multiple of today's value, or 0 for off. */
|
|
17
17
|
export const LAB_DEFAULTS = Object.freeze({
|
|
18
|
-
on:
|
|
18
|
+
on: true, // always: the sliders are the Galaxy's own controls now (it was an experimental switch)
|
|
19
19
|
gravity: 0, // a pull on every dot towards you, in the middle
|
|
20
20
|
rings: 1, // how hard each tier (or degree) holds its ring; 0 lets it find its own shape
|
|
21
21
|
push: 15, // how hard dots push each other apart
|
|
@@ -25,11 +25,12 @@ export const LAB_DEFAULTS = Object.freeze({
|
|
|
25
25
|
lines: 1,
|
|
26
26
|
sizeBy: 'power', // 'power' (power score) or 'reach' (how many hang off them)
|
|
27
27
|
names: 'key', // 'key' (you, S tier, catalysts) or 'all' (all your connections)
|
|
28
|
-
branch:
|
|
29
|
-
colourBy: 'tier', // 'tier', 'degree', 'company' or 'warmth' (from the Social tab)
|
|
28
|
+
branch: false, // hovering a dot lights up its branch and dims the rest: off to start (Blake, 2026-10-02: it glitched)
|
|
29
|
+
colourBy: 'tier', // 'tier', 'heat' (power as a thermal map), 'degree', 'company' or 'warmth' (from the Social tab)
|
|
30
30
|
speed: 15, // a replay's length, in seconds
|
|
31
31
|
loop: false, // start the replay again when it ends
|
|
32
32
|
labels: true, // names on the Galaxy at all: the Filter panel's switch, honoured with the lab off too
|
|
33
|
+
v: 2, // which defaults a saved setting was made under (2: the branch starts off)
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
/** The slider settings a saved layout keeps (not whether the lab is on, nor the names switch). */
|
|
@@ -38,13 +39,33 @@ const LAYOUT_KEYS = ['gravity', 'rings', 'push', 'pull', 'distance', 'dotSize',
|
|
|
38
39
|
/** Let the layout find its own shape: no rings, each connection's circle bunched round them. */
|
|
39
40
|
export const CLUSTERS = Object.freeze({ gravity: 0.25, rings: 0, push: 25, pull: 2.5, distance: 0.35, sizeBy: 'reach' });
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Orbit, the old view, as a layout of the Galaxy (Blake, 2026-10-02: "add that as a
|
|
44
|
+
* visual option instead so the person can interact with it"): every tier held hard
|
|
45
|
+
* on its orbit, and each connection's circle drawn in close behind them. It's still
|
|
46
|
+
* the Galaxy, so dragging and every slider work on it.
|
|
47
|
+
*/
|
|
48
|
+
export const ORBIT = Object.freeze({ gravity: 0, rings: 3, push: 6, pull: 4, distance: 0.22, sizeBy: 'power' });
|
|
49
|
+
|
|
50
|
+
/** Which layout the sliders are set to: 'rings', 'clusters', 'orbit', or null for your own. */
|
|
51
|
+
export function layoutOf(lab) {
|
|
52
|
+
const is = (preset) => Object.entries(preset).every(([k, v]) => lab[k] === v);
|
|
53
|
+
if (['gravity', 'rings', 'push', 'pull', 'distance'].every((k) => lab[k] === LAB_DEFAULTS[k])) return 'rings';
|
|
54
|
+
if (is(CLUSTERS)) return 'clusters';
|
|
55
|
+
if (is(ORBIT)) return 'orbit';
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
41
59
|
const listeners = new Set();
|
|
42
60
|
let cache = null;
|
|
43
61
|
|
|
44
62
|
function read() {
|
|
45
63
|
try {
|
|
46
64
|
const raw = JSON.parse(localStorage.getItem(KEY) || 'null');
|
|
47
|
-
|
|
65
|
+
// `on` was a switch once; a setting saved back then mustn't turn the sliders off.
|
|
66
|
+
// Saved before v2, the branch light-up was on by default; it starts off now.
|
|
67
|
+
if (!raw || typeof raw !== 'object') return LAB_DEFAULTS;
|
|
68
|
+
return { ...LAB_DEFAULTS, ...raw, ...(raw.v === LAB_DEFAULTS.v ? {} : { branch: false }), v: LAB_DEFAULTS.v, on: true };
|
|
48
69
|
} catch { return LAB_DEFAULTS; }
|
|
49
70
|
}
|
|
50
71
|
|
|
@@ -54,7 +75,7 @@ export function labNow() {
|
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
export function setLab(patch) {
|
|
57
|
-
cache = patch === null ? { ...LAB_DEFAULTS,
|
|
78
|
+
cache = patch === null ? { ...LAB_DEFAULTS, labels: labNow().labels } : { ...labNow(), ...patch, on: true };
|
|
58
79
|
try { localStorage.setItem(KEY, JSON.stringify(cache)); } catch {}
|
|
59
80
|
listeners.forEach((f) => f());
|
|
60
81
|
}
|
|
@@ -249,6 +270,36 @@ const DEGREE_COLOURS = { 1: '#4FC3F7', 2: '#BA68C8', 3: '#FFB74D' };
|
|
|
249
270
|
const COMPANY_COLOURS = ['#FF6B6B', '#4ECDC4', '#FFD93D', '#A29BFE', '#00B894', '#FD79A8', '#74B9FF', '#E17055'];
|
|
250
271
|
const WARMTH_COLOURS = { warm: '#FF7043', cool: '#4FC3F7', dormant: '#8D6E63', never: '#546E7A' };
|
|
251
272
|
const OTHER = '#4a5568';
|
|
273
|
+
|
|
274
|
+
// Heat (Blake, 2026-10-02: "a thermal visual of power based on the dots"): a
|
|
275
|
+
// thermal camera's colours, cold violet through red and orange to white hot.
|
|
276
|
+
const HEAT_STOPS = ['#2b1b6b', '#6a1b9a', '#c2185b', '#f4511e', '#ffb300', '#fff3b0'];
|
|
277
|
+
|
|
278
|
+
/** The thermal colour at `t` (0 cold … 1 hottest). */
|
|
279
|
+
export function heatColour(t) {
|
|
280
|
+
const x = Math.max(0, Math.min(1, Number(t) || 0)) * (HEAT_STOPS.length - 1);
|
|
281
|
+
const i = Math.min(HEAT_STOPS.length - 2, Math.floor(x));
|
|
282
|
+
const f = x - i;
|
|
283
|
+
const a = HEAT_STOPS[i], b = HEAT_STOPS[i + 1];
|
|
284
|
+
const mix = (k) => Math.round(parseInt(a.slice(k, k + 2), 16) * (1 - f) + parseInt(b.slice(k, k + 2), 16) * f);
|
|
285
|
+
return `#${[1, 3, 5].map((k) => mix(k).toString(16).padStart(2, '0')).join('')}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Everyone's heat, 0 to 1: where their power score ranks among the people shown,
|
|
290
|
+
* so the colours spread across whoever is on screen (a network of mostly B tier
|
|
291
|
+
* still has its hot spots). Equal scores share a heat.
|
|
292
|
+
*/
|
|
293
|
+
export function heatOf(people = []) {
|
|
294
|
+
return heatBy(people, score);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** The same for anything with a value: each one's heat, 0 to 1, from where its value ranks. */
|
|
298
|
+
export function heatBy(items = [], valueOf = () => 0) {
|
|
299
|
+
const values = [...new Set(items.map(valueOf))].sort((a, b) => a - b);
|
|
300
|
+
const at = new Map(values.map((v, i) => [v, values.length > 1 ? i / (values.length - 1) : 1]));
|
|
301
|
+
return (d) => at.get(valueOf(d)) ?? 0;
|
|
302
|
+
}
|
|
252
303
|
const companyKey = (c) => String(c || '').trim().toLowerCase();
|
|
253
304
|
|
|
254
305
|
/**
|
|
@@ -280,6 +331,14 @@ export function colourScheme(mode, people, tierColors, social) {
|
|
|
280
331
|
legend: [...top.map(([, c], i) => [c.name, COMPANY_COLOURS[i]]), ['Other', OTHER]],
|
|
281
332
|
};
|
|
282
333
|
}
|
|
334
|
+
if (mode === 'heat') {
|
|
335
|
+
const heat = heatOf(people);
|
|
336
|
+
return {
|
|
337
|
+
of: (d) => heatColour(heat(d)),
|
|
338
|
+
heat,
|
|
339
|
+
legend: [['Cold', heatColour(0.1)], ['Warm', heatColour(0.45)], ['Hot', heatColour(0.75)], ['Hottest', heatColour(1)]],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
283
342
|
if (mode === 'warmth' && social) {
|
|
284
343
|
return {
|
|
285
344
|
of: (d) => ((d.degree || 1) === 1 ? WARMTH_COLOURS[warmthOf(social.merged.get(keyFor(d)), social.asOf)] : '#2d3748'),
|
package/lib/ingest.js
CHANGED
|
@@ -103,6 +103,8 @@ export function refreshNotifications({ added = [], checked = 0, tierOf = () => n
|
|
|
103
103
|
out.push({
|
|
104
104
|
user_id, type: 'new_elite_connection', title: `High-value connection: ${r.name}`,
|
|
105
105
|
message: r.headline?.substring(0, 60), icon: '👑',
|
|
106
|
+
// Who it is, so the notification can open them (lib/notifications.js noteSubject).
|
|
107
|
+
data: { profileUrl: r.profile_url },
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
return out;
|
package/lib/island.js
CHANGED
|
@@ -34,3 +34,43 @@ export function clearActivity(id) {
|
|
|
34
34
|
activities = activities.filter((x) => x.id !== id);
|
|
35
35
|
listeners.forEach((f) => f());
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
// The notch's buttons: the views of the page you're on (Bridge Chains,
|
|
39
|
+
// Separation…), or any page's own sub-tabs. A page sets them while it's on
|
|
40
|
+
// screen and clears them when it leaves; the notch draws them under the header's
|
|
41
|
+
// tabs (Blake, 2026-10-02: "the notch… under the ui buttons at top so then we
|
|
42
|
+
// can use that for button selection too").
|
|
43
|
+
//
|
|
44
|
+
// setNotchTabs({ items: [{ key, label, icon?, title? }], current, onPick })
|
|
45
|
+
// setNotchTabs(null)
|
|
46
|
+
const tabListeners = new Set();
|
|
47
|
+
let notchTabs = null;
|
|
48
|
+
|
|
49
|
+
export const notchTabsNow = () => notchTabs;
|
|
50
|
+
export const noNotchTabs = () => null;
|
|
51
|
+
|
|
52
|
+
export function watchNotchTabs(f) {
|
|
53
|
+
tabListeners.add(f);
|
|
54
|
+
return () => tabListeners.delete(f);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Whether the notch is on screen at all (tabs, a scan, or another job), so a
|
|
58
|
+
// page with a heading at the top can leave room for it only when it's there.
|
|
59
|
+
const shownListeners = new Set();
|
|
60
|
+
let notchShown = false;
|
|
61
|
+
export const notchShownNow = () => notchShown;
|
|
62
|
+
export const noNotch = () => false;
|
|
63
|
+
export function watchNotchShown(f) {
|
|
64
|
+
shownListeners.add(f);
|
|
65
|
+
return () => shownListeners.delete(f);
|
|
66
|
+
}
|
|
67
|
+
export function setNotchShown(shown) {
|
|
68
|
+
if (notchShown === Boolean(shown)) return;
|
|
69
|
+
notchShown = Boolean(shown);
|
|
70
|
+
shownListeners.forEach((f) => f());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function setNotchTabs(tabs) {
|
|
74
|
+
notchTabs = tabs && Array.isArray(tabs.items) && tabs.items.length > 1 ? tabs : null;
|
|
75
|
+
tabListeners.forEach((f) => f());
|
|
76
|
+
}
|