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.
Files changed (375) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +9 -9
  4. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.rsc +12 -12
  13. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  14. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +11 -11
  15. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/api/admin-delete/route.js +1 -1
  17. package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/api/admin-update/route.js +1 -1
  19. package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/bridges/route.js +1 -1
  21. package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/bulk-import/route.js +1 -1
  23. package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +1 -1
  25. package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/company-scores/route.js +1 -1
  27. package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/connections/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/delete-cluster/route.js +1 -1
  36. package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/ingest/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/network/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/notifications/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/outreach/route.js +1 -1
  44. package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/queue/route.js +1 -1
  46. package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/scraper/route.js +3 -2
  48. package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/settings/route.js +1 -1
  50. package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +1 -1
  52. package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +1 -1
  54. package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/setup-profile/route.js +1 -1
  56. package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/social/route.js +3 -3
  60. package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/api/unlock/route.js +1 -1
  62. package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/api/update-images/route.js +1 -1
  65. package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/api/users/route.js +1 -1
  67. package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/import.html +1 -1
  72. package/.next/standalone/.next/server/app/import.rsc +14 -14
  73. package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +14 -14
  74. package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +1 -1
  75. package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +13 -13
  76. package/.next/standalone/.next/server/app/index.html +1 -1
  77. package/.next/standalone/.next/server/app/index.rsc +14 -14
  78. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  79. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  80. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  81. package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/launch.html +2 -2
  84. package/.next/standalone/.next/server/app/launch.rsc +14 -14
  85. package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +14 -14
  86. package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +1 -1
  87. package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +13 -13
  88. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  90. package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
  92. package/.next/standalone/.next/server/app/paths.html +1 -1
  93. package/.next/standalone/.next/server/app/paths.rsc +14 -14
  94. package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +14 -14
  95. package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +1 -1
  96. package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +13 -13
  97. package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
  98. package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
  99. package/.next/standalone/.next/server/app/profile.html +1 -1
  100. package/.next/standalone/.next/server/app/profile.rsc +14 -14
  101. package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +14 -14
  102. package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +1 -1
  103. package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +13 -13
  104. package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
  106. package/.next/standalone/.next/server/app/queue.html +1 -1
  107. package/.next/standalone/.next/server/app/queue.rsc +14 -14
  108. package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +14 -14
  109. package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +1 -1
  110. package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +13 -13
  111. package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
  113. package/.next/standalone/.next/server/app/scores.html +1 -1
  114. package/.next/standalone/.next/server/app/scores.rsc +14 -14
  115. package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +14 -14
  116. package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +1 -1
  117. package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +13 -13
  118. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  119. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/app/settings.html +1 -1
  121. package/.next/standalone/.next/server/app/settings.rsc +14 -14
  122. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  123. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  124. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +13 -13
  125. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  126. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  127. package/.next/standalone/.next/server/app/setup.html +1 -1
  128. package/.next/standalone/.next/server/app/setup.rsc +14 -14
  129. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +14 -14
  130. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  131. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +13 -13
  132. package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
  133. package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
  134. package/.next/standalone/.next/server/app/social.html +1 -1
  135. package/.next/standalone/.next/server/app/social.rsc +14 -14
  136. package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +14 -14
  137. package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +1 -1
  138. package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +13 -13
  139. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0bat8nx._.js → [root-of-the-server]__13dx4sd._.js} +2 -2
  140. package/.next/standalone/.next/server/chunks/[root-of-the-server]__15wc9e-._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +8 -0
  143. package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
  145. package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +8 -0
  148. package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +8 -0
  149. package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +8 -0
  150. package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +8 -8
  151. package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +3 -0
  152. package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +3 -0
  153. package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +3 -0
  154. package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +3 -0
  155. package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +3 -0
  156. package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +3 -0
  157. package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +3 -0
  158. package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +3 -0
  159. package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +3 -0
  160. package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +3 -0
  161. package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +3 -0
  162. package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +3 -0
  163. package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +3 -0
  164. package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +3 -0
  165. package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +3 -0
  166. package/.next/standalone/.next/server/chunks/lib_scoring_1-e4ur0.js +1 -1
  167. package/.next/standalone/.next/server/chunks/ssr/_01kacen._.js +3 -0
  168. package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +3 -0
  169. package/.next/standalone/.next/server/chunks/ssr/{_1ap1tu_._.js → _0gtv9jb._.js} +2 -2
  170. package/.next/standalone/.next/server/chunks/ssr/_0jdkqgi._.js +3 -0
  171. package/.next/standalone/.next/server/chunks/ssr/_0xi-fbs._.js +3 -0
  172. package/.next/standalone/.next/server/chunks/ssr/_0xmwmfv._.js +3 -0
  173. package/.next/standalone/.next/server/chunks/ssr/_1-b9hj7._.js +9 -0
  174. package/.next/standalone/.next/server/chunks/ssr/_112ewem._.js +3 -0
  175. package/.next/standalone/.next/server/chunks/ssr/_16ab63q._.js +3 -0
  176. package/.next/standalone/.next/server/chunks/ssr/_1fxm0-s._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/ssr/_1ra1d3r._.js +9 -0
  178. package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/ssr/app_components_ui_0pdmz2z.js +3 -0
  180. package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +22 -10
  181. package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
  182. package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
  183. package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +6 -5
  184. package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
  185. package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +19 -3
  186. package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -2
  187. package/.next/standalone/.next/server/chunks/ssr/lib_0i8v-s3._.js +1 -1
  188. package/.next/standalone/.next/server/chunks/ssr/lib_13rapwj._.js +3 -0
  189. package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
  190. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  191. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  192. package/.next/standalone/.next/server/pages/404.html +1 -1
  193. package/.next/standalone/.next/server/pages/500.html +1 -1
  194. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  195. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  196. package/.next/standalone/.next/static/chunks/03bziijw5srol.js +1 -0
  197. package/.next/standalone/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
  198. package/.next/standalone/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
  199. package/.next/standalone/.next/static/chunks/0g_fy2034bp0x.js +34 -0
  200. package/.next/standalone/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
  201. package/.next/standalone/.next/static/chunks/0w-byiwb2zax8.js +2 -0
  202. package/.next/standalone/.next/static/chunks/1g7jcovdejc9-.js +1 -0
  203. package/.next/standalone/.next/static/chunks/1i51yylxdzht1.js +1 -0
  204. package/.next/standalone/.next/static/chunks/1zf8swaasjf1e.js +43 -0
  205. package/.next/standalone/.next/static/chunks/2rqtpccruavf2.js +1 -0
  206. package/.next/standalone/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
  207. package/.next/standalone/.next/static/chunks/30x3oq_st7a5r.js +1 -0
  208. package/.next/standalone/.next/static/chunks/355ryio1b7sm1.js +1 -0
  209. package/.next/standalone/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
  210. package/.next/standalone/.next/static/chunks/3fci6qi39-1n5.js +1 -0
  211. package/.next/standalone/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
  212. package/.next/standalone/CHANGELOG.md +187 -0
  213. package/.next/standalone/app/api/ingest/route.js +40 -1
  214. package/.next/standalone/app/api/scraper/route.js +7 -1
  215. package/.next/standalone/app/api/settings/sector-preview/route.js +2 -1
  216. package/.next/standalone/app/components/AppTabs.js +59 -0
  217. package/.next/standalone/app/components/ChainView.js +303 -89
  218. package/.next/standalone/app/components/CirclePanel.js +162 -0
  219. package/.next/standalone/app/components/CompanyScores.js +2 -2
  220. package/.next/standalone/app/components/FieldStep.js +4 -4
  221. package/.next/standalone/app/components/FilterPanel.js +121 -183
  222. package/.next/standalone/app/components/ForceGraph.js +41 -3
  223. package/.next/standalone/app/components/GalaxyLab.js +26 -31
  224. package/.next/standalone/app/components/LegacyScoresCard.js +2 -2
  225. package/.next/standalone/app/components/MapControls.js +154 -0
  226. package/.next/standalone/app/components/NoteDetail.js +118 -0
  227. package/.next/standalone/app/components/PathsAnalyzer.js +147 -110
  228. package/.next/standalone/app/components/PeopleMap.js +308 -0
  229. package/.next/standalone/app/components/ScanRadar.js +129 -0
  230. package/.next/standalone/app/components/ScanStatusBar.js +71 -27
  231. package/.next/standalone/app/components/SeparationView.js +626 -118
  232. package/.next/standalone/app/components/Sidebar.js +28 -2
  233. package/.next/standalone/app/components/settings/NetworkHealthSection.js +1 -1
  234. package/.next/standalone/app/components/settings/SectorSection.js +2 -2
  235. package/.next/standalone/app/components/settings/TierSection.js +1 -1
  236. package/.next/standalone/app/components/settings/TitleSection.js +149 -0
  237. package/.next/standalone/app/components/views.js +15 -14
  238. package/.next/standalone/app/page.js +89 -156
  239. package/.next/standalone/app/paths/page.js +39 -27
  240. package/.next/standalone/app/profile/page.js +33 -5
  241. package/.next/standalone/app/queue/page.js +56 -34
  242. package/.next/standalone/app/scores/page.js +16 -66
  243. package/.next/standalone/app/settings/page.js +27 -13
  244. package/.next/standalone/app/setup/page.js +298 -205
  245. package/.next/standalone/app/social/SocialHub.js +374 -0
  246. package/.next/standalone/app/social/page.js +10 -362
  247. package/.next/standalone/lib/chain-layout.js +105 -35
  248. package/.next/standalone/lib/degrees.js +0 -6
  249. package/.next/standalone/lib/dot-rings.js +25 -0
  250. package/.next/standalone/lib/galaxy-lab.js +65 -6
  251. package/.next/standalone/lib/ingest.js +2 -0
  252. package/.next/standalone/lib/island.js +40 -0
  253. package/.next/standalone/lib/linkedin-limits.js +6 -2
  254. package/.next/standalone/lib/map-heat.js +20 -0
  255. package/.next/standalone/lib/network-health.js +1 -1
  256. package/.next/standalone/lib/notifications.js +121 -0
  257. package/.next/standalone/lib/people-map.js +82 -0
  258. package/.next/standalone/lib/rarity.js +45 -3
  259. package/.next/standalone/lib/reach.js +7 -6
  260. package/.next/standalone/lib/rpc.js +15 -4
  261. package/.next/standalone/lib/scan-pace.js +46 -0
  262. package/.next/standalone/lib/scoring.js +39 -8
  263. package/.next/standalone/lib/scraper-client.js +19 -6
  264. package/.next/standalone/lib/sector-focus.js +4 -4
  265. package/.next/standalone/lib/separation.js +137 -10
  266. package/.next/standalone/lib/settings-effects.js +20 -11
  267. package/.next/standalone/lib/settings.js +3 -0
  268. package/.next/standalone/lib/tier-grid.js +99 -0
  269. package/.next/standalone/lib/title-ranking.js +83 -0
  270. package/.next/standalone/package-lock.json +2 -2
  271. package/.next/standalone/package.json +1 -1
  272. package/.next/standalone/scripts/scrape.py +51 -13
  273. package/.next/static/chunks/03bziijw5srol.js +1 -0
  274. package/.next/static/chunks/0bgq8wmopl_ig.js +7 -0
  275. package/.next/static/chunks/0ezus6p2eb-vg.js +1 -0
  276. package/.next/static/chunks/0g_fy2034bp0x.js +34 -0
  277. package/.next/static/chunks/0q5dru4q6gxl5.js +22 -0
  278. package/.next/static/chunks/0w-byiwb2zax8.js +2 -0
  279. package/.next/static/chunks/1g7jcovdejc9-.js +1 -0
  280. package/.next/static/chunks/1i51yylxdzht1.js +1 -0
  281. package/.next/static/chunks/1zf8swaasjf1e.js +43 -0
  282. package/.next/static/chunks/2rqtpccruavf2.js +1 -0
  283. package/.next/static/chunks/2wgz9ietkqqe6.js +1 -0
  284. package/.next/static/chunks/30x3oq_st7a5r.js +1 -0
  285. package/.next/static/chunks/355ryio1b7sm1.js +1 -0
  286. package/.next/static/chunks/3ap5hgma7n0o7.js +1 -0
  287. package/.next/static/chunks/3fci6qi39-1n5.js +1 -0
  288. package/.next/static/chunks/{24tvdun1cb3g1.js → 3sowbvbaj1s6n.js} +1 -1
  289. package/CHANGELOG.md +187 -0
  290. package/lib/chain-layout.js +105 -35
  291. package/lib/degrees.js +0 -6
  292. package/lib/dot-rings.js +25 -0
  293. package/lib/galaxy-lab.js +65 -6
  294. package/lib/ingest.js +2 -0
  295. package/lib/island.js +40 -0
  296. package/lib/linkedin-limits.js +6 -2
  297. package/lib/map-heat.js +20 -0
  298. package/lib/network-health.js +1 -1
  299. package/lib/notifications.js +121 -0
  300. package/lib/people-map.js +82 -0
  301. package/lib/rarity.js +45 -3
  302. package/lib/reach.js +7 -6
  303. package/lib/rpc.js +15 -4
  304. package/lib/scan-pace.js +46 -0
  305. package/lib/scoring.js +39 -8
  306. package/lib/scraper-client.js +19 -6
  307. package/lib/sector-focus.js +4 -4
  308. package/lib/separation.js +137 -10
  309. package/lib/settings-effects.js +20 -11
  310. package/lib/settings.js +3 -0
  311. package/lib/tier-grid.js +99 -0
  312. package/lib/title-ranking.js +83 -0
  313. package/package.json +1 -1
  314. package/scripts/scrape.py +51 -13
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17hjtzl._.js +0 -8
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20tmem7._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/lib_rpc_03i8xef.js +0 -3
  318. package/.next/standalone/.next/server/chunks/lib_rpc_05gt5ya.js +0 -3
  319. package/.next/standalone/.next/server/chunks/lib_rpc_08fc3rp.js +0 -3
  320. package/.next/standalone/.next/server/chunks/lib_rpc_08r2t_q.js +0 -3
  321. package/.next/standalone/.next/server/chunks/lib_rpc_0btpv1t.js +0 -3
  322. package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +0 -8
  323. package/.next/standalone/.next/server/chunks/lib_rpc_0cm2ev3.js +0 -3
  324. package/.next/standalone/.next/server/chunks/lib_rpc_0iovswz.js +0 -3
  325. package/.next/standalone/.next/server/chunks/lib_rpc_0is8flp.js +0 -3
  326. package/.next/standalone/.next/server/chunks/lib_rpc_0v8ajtd.js +0 -3
  327. package/.next/standalone/.next/server/chunks/lib_rpc_0xgmvvt.js +0 -3
  328. package/.next/standalone/.next/server/chunks/lib_rpc_144mzbf.js +0 -3
  329. package/.next/standalone/.next/server/chunks/lib_rpc_1bggi0n.js +0 -3
  330. package/.next/standalone/.next/server/chunks/lib_rpc_1dkcu9f.js +0 -3
  331. package/.next/standalone/.next/server/chunks/lib_rpc_1o0jb6w.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/_0-85h6g._.js +0 -3
  333. package/.next/standalone/.next/server/chunks/ssr/_08cfj2b._.js +0 -3
  334. package/.next/standalone/.next/server/chunks/ssr/_0c_y09g._.js +0 -3
  335. package/.next/standalone/.next/server/chunks/ssr/_0j4-lc3._.js +0 -3
  336. package/.next/standalone/.next/server/chunks/ssr/_0l8cxmo._.js +0 -3
  337. package/.next/standalone/.next/server/chunks/ssr/_0xkur4p._.js +0 -3
  338. package/.next/standalone/.next/server/chunks/ssr/_14iv_zy._.js +0 -3
  339. package/.next/standalone/.next/server/chunks/ssr/_15x6q32._.js +0 -3
  340. package/.next/standalone/.next/server/chunks/ssr/_212xw8q._.js +0 -3
  341. package/.next/standalone/.next/server/chunks/ssr/app_components_19c2d9c._.js +0 -3
  342. package/.next/standalone/.next/server/chunks/ssr/app_scores_page_09wepvv.js +0 -3
  343. package/.next/standalone/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
  344. package/.next/standalone/.next/static/chunks/15sjx07xhpn6p.js +0 -1
  345. package/.next/standalone/.next/static/chunks/160wjb73ddwqd.js +0 -6
  346. package/.next/standalone/.next/static/chunks/1a74dky22dxd6.js +0 -1
  347. package/.next/standalone/.next/static/chunks/1c_n0973t07l0.js +0 -2
  348. package/.next/standalone/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
  349. package/.next/standalone/.next/static/chunks/236f0jtp68j_h.js +0 -1
  350. package/.next/standalone/.next/static/chunks/297e1z34yi--z.js +0 -1
  351. package/.next/standalone/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
  352. package/.next/standalone/.next/static/chunks/2kfhc58df2xyo.js +0 -1
  353. package/.next/standalone/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
  354. package/.next/standalone/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
  355. package/.next/standalone/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
  356. package/.next/standalone/app/components/OrbitGraph.js +0 -737
  357. package/.next/static/chunks/0a5iadnmt9u9k.js +0 -1
  358. package/.next/static/chunks/15sjx07xhpn6p.js +0 -1
  359. package/.next/static/chunks/160wjb73ddwqd.js +0 -6
  360. package/.next/static/chunks/1a74dky22dxd6.js +0 -1
  361. package/.next/static/chunks/1c_n0973t07l0.js +0 -2
  362. package/.next/static/chunks/1e1n0kkz-psdb.js +0 -1
  363. package/.next/static/chunks/236f0jtp68j_h.js +0 -1
  364. package/.next/static/chunks/297e1z34yi--z.js +0 -1
  365. package/.next/static/chunks/2_ak6qjxb6oa5.js +0 -1
  366. package/.next/static/chunks/2kfhc58df2xyo.js +0 -1
  367. package/.next/static/chunks/39hr8d-b2ibtw.js +0 -31
  368. package/.next/static/chunks/3ui4d-uqkqj5d.js +0 -2
  369. package/.next/static/chunks/3zw8bv7-mc4n6.js +0 -33
  370. /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
  371. /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
  372. /package/.next/standalone/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
  373. /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_buildManifest.js +0 -0
  374. /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_clientMiddlewareManifest.js +0 -0
  375. /package/.next/static/{jqE2D3zWqSGz5su948Azf → I-cj79yE2IZ8taX2dZGp3}/_ssgManifest.js +0 -0
@@ -2512,18 +2512,47 @@ export function readPerson(row) {
2512
2512
  * points whatever else they list. `companyFor(name)` returns companyScore();
2513
2513
  * `read` is the row's readPerson(), when it has been read already.
2514
2514
  */
2515
- /** A role's title points as they count: 70% for a former role, capped for a student. */
2516
- function rolePoints(role, student) {
2517
- const points = role.title.points * (role.former ? FORMER_WEIGHT : 1);
2515
+ /** Does this title text have the words? Whole words only: "vc" isn't in "dvc", "cto" isn't in "doctor". */
2516
+ const titlePatterns = new Map();
2517
+ export function titleHas(text, words) {
2518
+ let re = titlePatterns.get(words);
2519
+ if (!re) {
2520
+ const esc = words.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2521
+ re = new RegExp(`(^|[^a-z0-9])${esc}($|[^a-z0-9])`);
2522
+ if (titlePatterns.size > 500) titlePatterns.clear();
2523
+ titlePatterns.set(words, re);
2524
+ }
2525
+ return re.test(String(text || '').toLowerCase());
2526
+ }
2527
+
2528
+ /**
2529
+ * A title's points under your title ranking (Settings → Scores → Titles,
2530
+ * lib/title-ranking.js): a role whose words are in it counts what you gave
2531
+ * that role (the highest, if several match), else the level counts your
2532
+ * points for it, else the model's. No ranking: the model's.
2533
+ */
2534
+ export function rankedPoints(role, titles) {
2535
+ if (!titles) return role.title.points;
2536
+ let match = null;
2537
+ for (const r of titles.roles || []) {
2538
+ if (titleHas(role.part, r.text) && (match == null || r.points > match)) match = r.points;
2539
+ }
2540
+ if (match != null) return match;
2541
+ return titles.points?.[role.title.key] ?? role.title.points;
2542
+ }
2543
+
2544
+ /** A role's title points as they count: yours where you ranked them, 70% for a former role, capped for a student. */
2545
+ function rolePoints(role, student, titles) {
2546
+ const points = rankedPoints(role, titles) * (role.former ? FORMER_WEIGHT : 1);
2518
2547
  return student ? Math.min(points, STUDENT_CAP) : points;
2519
2548
  }
2520
2549
 
2521
- export function scorePerson(row, companyFor = (name) => companyScore(name), read = readPerson(row)) {
2550
+ export function scorePerson(row, companyFor = (name) => companyScore(name), read = readPerson(row), titles = null) {
2522
2551
  const { roles, student, reach } = read;
2523
2552
  let best = null;
2524
2553
  for (const role of roles) {
2525
2554
  const co = companyFor(role.company);
2526
- const points = rolePoints(role, student);
2555
+ const points = rolePoints(role, student, titles);
2527
2556
  const core = points * (0.45 + 0.055 * co.score);
2528
2557
  if (!best || core > best.core) best = { role, co, points, core };
2529
2558
  }
@@ -2535,6 +2564,8 @@ export function scorePerson(row, companyFor = (name) => companyScore(name), read
2535
2564
  const bonus = reachBonus(row?.headline, unknown ? 4 : best.co.base ?? best.co.score, reach);
2536
2565
  const power = round1(best.core + bonus.points);
2537
2566
  const title = { ...best.role.title, former: best.role.former, student, points: round1(best.points) };
2567
+ // Your ranking moved it: the card's "why" says so.
2568
+ if (titles && rankedPoints(best.role, titles) !== best.role.title.points) title.ranked = true;
2538
2569
  if (best.role.former) title.label = `Former ${title.label}`;
2539
2570
  if (student && best.role.title.level > 0) title.label = `Student · ${title.label}`;
2540
2571
  const out = { title, company: best.role.company, companyScore: best.co.score, companySource: best.co.source, bonus, power, tier: tierFor(power) };
@@ -2652,7 +2683,7 @@ export function explainScore(s, boost = 0, { sectorLabel } = {}) {
2652
2683
  const co = s.companySector
2653
2684
  ? `${s.company} (${s.companyScore}/10: ${s.companySector.base} + ${s.companySector.bonus} your sector${which ? `: ${which}` : ''})`
2654
2685
  : `${s.company || 'no company found'} (${s.companyScore}/10${s.companySource === 'yours' ? ', your score' : ''})`;
2655
- const bits = [`${s.title.label} (${s.title.points})`, co];
2686
+ const bits = [`${s.title.label} (${s.title.points}${s.title.ranked ? ', your ranking' : ''})`, co];
2656
2687
  if (s.bonus.points) bits.push(`+${s.bonus.points} ${s.bonus.reasons.join(', ')}`);
2657
2688
  if (boost) bits.push(`+${boost} strong circle`);
2658
2689
  return bits.join(' · ');
@@ -2716,7 +2747,7 @@ export function readNetwork(rows = [], { industryOf, sectorsOf, groupOf } = {})
2716
2747
  * (companyScore(), by name) and the curve's cut-offs (null on the fixed scale).
2717
2748
  */
2718
2749
  export function scoreNetwork(rows = [], {
2719
- overrides = new Map(), industryOf, sectorsOf, groupOf, focus, tierScale = 'fixed',
2750
+ overrides = new Map(), industryOf, sectorsOf, groupOf, focus, tierScale = 'fixed', titles = null,
2720
2751
  read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
2721
2752
  } = {}) {
2722
2753
  // Each company is scored once; everyone there shares the answer.
@@ -2726,7 +2757,7 @@ export function scoreNetwork(rows = [], {
2726
2757
  return companyScores.get(name);
2727
2758
  };
2728
2759
  const out = new Map();
2729
- for (const r of rows) out.set(r.id, { ...scorePerson(r, companyFor, read.people.get(r)), boost: 0 });
2760
+ for (const r of rows) out.set(r.id, { ...scorePerson(r, companyFor, read.people.get(r), titles), boost: 0 });
2730
2761
  // The curve's cut-offs come from your 1st-degree people before any circle
2731
2762
  // boost, so a boost (which reads the graded tiers) can't move its own cut-off.
2732
2763
  const cutoffs = tierScale === 'fixed' ? null
@@ -15,11 +15,27 @@ export async function scraperStatus() {
15
15
  return r.json();
16
16
  }
17
17
 
18
+ /**
19
+ * Scan page → "Hide the Chrome window while scanning", remembered in this
20
+ * browser. Every way a scan starts asks here: the Scan page's own buttons used
21
+ * to post straight to the scanner without it, so the switch did nothing for
22
+ * the scans started from the very page it sits on.
23
+ */
24
+ export const HIDE_CHROME_KEY = 'six-degrees-hide-chrome';
25
+ export function hideChromeOn(storage = typeof localStorage !== 'undefined' ? localStorage : null) {
26
+ try { return JSON.parse(storage?.getItem(HIDE_CHROME_KEY) || 'false') === true; } catch { return false; }
27
+ }
28
+
29
+ /** What a scan start sends: the action, its details, and `headless` when Chrome is to stay hidden. */
30
+ export function scanRequest(action, extra = {}, hidden = hideChromeOn()) {
31
+ return { action, ...extra, ...(hidden ? { headless: true } : {}) };
32
+ }
33
+
18
34
  export async function startScrape(action, name, extra = {}) {
19
35
  const r = await fetch('/api/scraper', {
20
36
  method: 'POST',
21
37
  headers: { 'Content-Type': 'application/json' },
22
- body: JSON.stringify({ action, ...(name ? { name } : {}), ...extra }),
38
+ body: JSON.stringify(scanRequest(action, { ...(name ? { name } : {}), ...extra })),
23
39
  });
24
40
  const d = await r.json().catch(() => ({}));
25
41
  if (!r.ok) throw new Error(d.error || 'Could not start the scanner.');
@@ -211,11 +227,8 @@ export async function beginScrape(action, { name, id, onLog, ...extra } = {}) {
211
227
  });
212
228
  let started;
213
229
  try {
214
- // Scan page → "Hide the Chrome window while scanning", remembered in this
215
- // browser, applies to every scan however it was started.
216
- let hidden = false;
217
- try { hidden = JSON.parse(localStorage.getItem('six-degrees-hide-chrome') || 'false') === true; } catch {}
218
- started = await startScrape(action, name, { ...(id ? { id } : {}), ...(hidden ? { headless: true } : {}), ...extra });
230
+ // startScrape adds "Hide the Chrome window" itself (scanRequest), however the scan was started.
231
+ started = await startScrape(action, name, { ...(id ? { id } : {}), ...extra });
219
232
  } catch (err) {
220
233
  // Refused, often because something else runs: ask what, straight away.
221
234
  // Until the answer comes, only our own claim is taken back.
@@ -141,11 +141,11 @@ export function tierMoves(rows, tierBefore, tierAfter, { examples = 5, companyOf
141
141
  */
142
142
  export function previewSectorFocus(rows = [], {
143
143
  overrides = new Map(), industryOf, sectorsOf = sectorMatcher(), groupOf = sectorGroup, from = NO_FOCUS, to = NO_FOCUS, examples = 5,
144
- tierScale, read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
144
+ tierScale, titles = null, read = readNetwork(rows, { industryOf, sectorsOf, groupOf }),
145
145
  } = {}) {
146
- // Both on the tier scale saved now (Scores → Tiers), as rescoring grades them.
147
- const before = scoreNetwork(rows, { overrides, focus: from, tierScale, read });
148
- const after = scoreNetwork(rows, { overrides, focus: to, tierScale, read });
146
+ // Both on the tier scale and title ranking saved now (Scores → Tiers, Titles), as rescoring grades them.
147
+ const before = scoreNetwork(rows, { overrides, focus: from, tierScale, titles, read });
148
+ const after = scoreNetwork(rows, { overrides, focus: to, tierScale, titles, read });
149
149
 
150
150
  // Companies whose score moves, biggest first (by your people there now).
151
151
  // Every company anyone names counts, a former employer too: its score moves
@@ -86,11 +86,6 @@ export function comparePower(a, b) {
86
86
  }
87
87
  const NO_ROUTE = { id: null, bridge: null };
88
88
 
89
- /** "Ways in" order: most ways in first, then the power order. The rank shown stays the power rank. */
90
- export function compareWaysIn(a, b) {
91
- return b.waysIn - a.waysIn || comparePower(a, b);
92
- }
93
-
94
89
  /**
95
90
  * @param {object[]} degree2 2nd-degree rows (one per bridge per person)
96
91
  * @param {object[]} connections 1st-degree rows, the bridges
@@ -236,6 +231,10 @@ export function dotRadius(waysIn) {
236
231
 
237
232
  export const MIN_BRIDGE_GAP = 18;
238
233
  const TOP_PAD = 16;
234
+ /** Grey dots for mutual connections in circles not scanned yet, at most this many drawn. */
235
+ export const GHOST_MAX = 60;
236
+ const GHOST_LABEL = 40; // room under the last connection for the line that counts them
237
+ const PILL_W_LAYOUT = 236; // SeparationView's PILL_W: the connection pills' width as cards
239
238
 
240
239
  /**
241
240
  * The summit map: You → the bridges → the top of the list, every route drawn.
@@ -249,10 +248,13 @@ const TOP_PAD = 16;
249
248
  *
250
249
  * @param {object[]} top the first K people of whatever the list shows
251
250
  * @param {number} width map width in px
251
+ * @param {{ cards?: boolean }} opts cards: people drawn as cards and
252
+ * connections as pills (Blake, 2026-10-02), which need more room per row
252
253
  * @returns {{ width, height, you, people, bridges, links, spokes }}
253
254
  */
254
- export function summitLayout(top = [], width = 800, isMobile = false) {
255
- const gap = isMobile ? 36 : 26;
255
+ export function summitLayout(top = [], width = 800, isMobile = false, { cards = false } = {}) {
256
+ const gap = cards ? 50 : isMobile ? 36 : 26;
257
+ const bridgeGap = cards ? 36 : MIN_BRIDGE_GAP;
256
258
  const youX = 20;
257
259
  const bx = Math.round(width * 0.42);
258
260
  const px = Math.round(width * 0.58);
@@ -262,7 +264,7 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
262
264
  p,
263
265
  x: px,
264
266
  y: TOP_PAD + i * gap,
265
- r: dotRadius(p.waysIn),
267
+ r: cards ? 0 : dotRadius(p.waysIn),
266
268
  label: fit(`#${p.rank}${p.tied ? '=' : ''} ${p.person.name || ''}`, Math.floor((width - px - 60) / 6.2)),
267
269
  }));
268
270
 
@@ -288,11 +290,16 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
288
290
 
289
291
  const y0 = TOP_PAD;
290
292
  const y1 = people.length ? people[people.length - 1].y : TOP_PAD;
291
- const step = bridges.length > 1 ? Math.max(MIN_BRIDGE_GAP, (y1 - y0) / (bridges.length - 1)) : 0;
293
+ // Each connection sits level with the people they lead to (the middle of
294
+ // them), moved no more than it takes to keep `bridgeGap` from the next one,
295
+ // so a connection with one person sits on that person's row and the line
296
+ // between them runs straight (Blake, 2026-10-02: "the lines should line up
297
+ // with every box").
298
+ const ys = stackColumn(bridges.map((b) => b.bary), bridgeGap, y0);
292
299
  const nameBudget = Math.max(4, Math.floor((bx - 10 - (youX + 22)) / 6));
293
300
  bridges.forEach((b, j) => {
294
301
  b.x = bx;
295
- b.y = bridges.length === 1 ? (y0 + y1) / 2 : y0 + j * step;
302
+ b.y = ys[j];
296
303
  b.r = 5;
297
304
  if (b.unresolved) b.label = fit(`can't be named (${b.count})`, nameBudget);
298
305
  else {
@@ -302,6 +309,15 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
302
309
  delete b.ys;
303
310
  });
304
311
  const nodeFor = new Map(bridges.map((b) => [b.unresolved ? UNRESOLVED : b.id, b]));
312
+ // What each connection is to the people on the map: the only door to some,
313
+ // the best way in to others, or only another way in.
314
+ for (const b of bridges) { b.onlyFor = 0; b.bestFor = 0; }
315
+ for (const pp of people) {
316
+ const first = pp.p.routes[0];
317
+ const bestNode = first ? nodeFor.get(first.bridge ? first.id : UNRESOLVED) : null;
318
+ if (bestNode) bestNode.bestFor++;
319
+ if (pp.p.waysIn === 1 && bestNode) bestNode.onlyFor++;
320
+ }
305
321
 
306
322
  // Routes, per person: routes[0] is the top-scored bridge. Two unnamed routes
307
323
  // for one person are one line to the one grey node.
@@ -326,3 +342,114 @@ export function summitLayout(top = [], width = 800, isMobile = false) {
326
342
  const spokes = bridges.map((b) => ({ id: b.id, x1: you.x, y1: you.y, x2: b.x, y2: b.y }));
327
343
  return { width, height, you, people, bridges, links, spokes };
328
344
  }
345
+
346
+ /**
347
+ * Heights as near the ones asked for as they can be while staying `gap`
348
+ * apart, in the order given (already sorted), never above `top`: neighbours
349
+ * that would overlap move apart by the same amount each way.
350
+ */
351
+ export function stackColumn(wanted = [], gap = MIN_BRIDGE_GAP, top = 0) {
352
+ const blocks = [];
353
+ wanted.forEach((y, k) => {
354
+ blocks.push({ sum: y - k * gap, count: 1 });
355
+ while (blocks.length > 1 && blocks[blocks.length - 2].sum / blocks[blocks.length - 2].count > blocks[blocks.length - 1].sum / blocks[blocks.length - 1].count) {
356
+ const last = blocks.pop();
357
+ blocks[blocks.length - 1].sum += last.sum;
358
+ blocks[blocks.length - 1].count += last.count;
359
+ }
360
+ });
361
+ const out = blocks.flatMap((b) => new Array(b.count).fill(b.sum / b.count)).map((q, k) => q + k * gap);
362
+ const lift = out.length && out[0] < top ? top - out[0] : 0;
363
+ return out.map((y) => y + lift);
364
+ }
365
+
366
+ /**
367
+ * Who the map aims at with the slider all the way to easy: whoever you share the
368
+ * most mutual connections with, scanned or not (Blake, 2026-10-02: "if we have
369
+ * mutuals but not scanned thats still a high probability … i dont want to hinder
370
+ * the algorithm since we havent scanned that person"). `mutualsOf` is LinkedIn's
371
+ * count where a scan saved one, never less than the ways in the app has drawn
372
+ * (lib/rarity.js). Then the most drawn, then the stronger person. Narrowing the
373
+ * tiers first (S only, say) keeps it to the people worth it.
374
+ */
375
+ export function mostMutuals(people = [], mutualsOf = () => 0, limit = 7) {
376
+ return [...people].sort((a, b) => (Math.max(mutualsOf(b) || 0, b.waysIn) - Math.max(mutualsOf(a) || 0, a.waysIn))
377
+ || b.waysIn - a.waysIn
378
+ || comparePower(a, b)).slice(0, limit);
379
+ }
380
+
381
+ /**
382
+ * How many people the map draws with the slider at `at` (lib/rarity.js): all
383
+ * `k` from the rare end to the middle, then fewer and fewer, down to the one
384
+ * person you're aiming at with the slider all the way to easy. Blake,
385
+ * 2026-10-02: at the rare end "it will show that one door"; at the other "all
386
+ * the connections going into that one person".
387
+ */
388
+ export function mapCount(at, k = 10) {
389
+ const v = Math.max(0, Math.min(100, Number(at) || 0));
390
+ if (v <= 50) return k;
391
+ return Math.max(1, Math.round((k * (100 - v)) / 50));
392
+ }
393
+
394
+ /**
395
+ * The map for one person: You → every connection of yours who leads to them →
396
+ * them, with rings round them. Routes the map can't name share one grey node,
397
+ * as in summitLayout, and it comes back in summitLayout's shape (`people` holds
398
+ * the one person, marked `big`), so one map draws both and can glide from one
399
+ * to the other. Pure, so it is tested without a browser.
400
+ *
401
+ * @returns {{ width, height, you, target, people, bridges, links, spokes }}
402
+ */
403
+ export function convergeLayout(p, width = 800, isMobile = false, { cards = false, mutuals = 0 } = {}) {
404
+ const gap = cards ? 46 : isMobile ? 40 : 34;
405
+ const youX = 20;
406
+ const bx = Math.round(width * (isMobile ? 0.44 : 0.36));
407
+ const tx = Math.round(width * (isMobile ? 0.8 : cards ? 0.6 : 0.62));
408
+ const nodes = [];
409
+ let unnamed = null;
410
+ for (const r of p?.routes || []) {
411
+ if (r.bridge) nodes.push({ id: r.id, bridge: r.bridge, unresolved: false, count: 1 });
412
+ else if (unnamed) unnamed.count++;
413
+ else nodes.push((unnamed = { id: null, bridge: null, unresolved: true, count: 1 }));
414
+ }
415
+ // Mutual connections the app can't draw yet: they're in circles you haven't
416
+ // scanned. Grey dots under the connections who do lead there, each with a faint
417
+ // line in, so the count reads at a glance (GHOST_MAX at most, then "+N").
418
+ const unscanned = Math.max(0, Math.floor(Number(mutuals) || 0) - (p?.waysIn || 0));
419
+ const ghostCount = Math.min(GHOST_MAX, unscanned);
420
+ const ghostStep = cards ? 12 : 11;
421
+ const ghostW = cards ? PILL_W_LAYOUT - 16 : isMobile ? 96 : 120;
422
+ const perRow = Math.max(1, Math.floor(ghostW / ghostStep) + 1);
423
+ const ghostRows = Math.ceil(ghostCount / perRow);
424
+ const ghostH = ghostCount ? GHOST_LABEL + ghostRows * ghostStep : 0;
425
+ const block = Math.max(0, nodes.length - 1) * gap + ghostH;
426
+ const height = Math.max(isMobile ? 190 : cards ? 250 : 220, TOP_PAD * 2 + block + 48);
427
+ const mid = height / 2;
428
+ const top = mid - block / 2;
429
+ const nameBudget = Math.max(4, Math.floor((bx - 10 - (youX + 22)) / 6));
430
+ const bridges = nodes.map((n, j) => {
431
+ const full = n.unresolved ? `can't be named (${n.count})` : n.bridge.name || '';
432
+ return {
433
+ ...n, x: bx, y: top + j * gap, r: 6,
434
+ primary: j === 0 && !n.unresolved,
435
+ label: full.length <= nameBudget ? full : fit(n.unresolved ? full : shortName(full), nameBudget),
436
+ };
437
+ });
438
+ const target = { x: tx, y: mid, r: cards ? 0 : 17, rings: cards ? [] : [32, 50, 70].filter((r) => r <= mid - 4) };
439
+ const links = bridges.map((b) => ({
440
+ key: p?.key, bridgeId: b.id, primary: b.primary, unresolved: b.unresolved, tier: b.bridge?.tier ?? null,
441
+ x1: b.x, y1: b.y, x2: target.x, y2: target.y,
442
+ }));
443
+ const you = { x: youX, y: mid, r: 14 };
444
+ const spokes = bridges.map((b) => ({ id: b.id, x1: you.x, y1: you.y, x2: b.x, y2: b.y }));
445
+ const people = p ? [{ key: p.key, p, x: target.x, y: target.y, r: target.r, rings: target.rings, big: true, label: '' }] : [];
446
+ const gy = top + Math.max(0, nodes.length - 1) * gap + GHOST_LABEL;
447
+ const gx = cards ? bx - PILL_W_LAYOUT + 8 : bx - ghostW;
448
+ const ghosts = Array.from({ length: ghostCount }, (_, i) => ({ x: gx + (i % perRow) * ghostStep, y: gy + Math.floor(i / perRow) * ghostStep }));
449
+ // How bright the person is: the share of your mutual connections with them that the map can draw.
450
+ const drawn = p?.waysIn ? p.waysIn / (p.waysIn + unscanned) : 1;
451
+ return {
452
+ width, height, you, target, people, bridges, links, spokes,
453
+ ghosts, unscanned, ghostLabel: ghostCount ? { x: gx, y: gy - 13 } : null, drawn,
454
+ };
455
+ }
@@ -5,28 +5,37 @@
5
5
 
6
6
  import { rescoreAll } from './rpc.js';
7
7
  import { sameFocus, NO_FOCUS, tierMoves } from './sector-focus.js';
8
+ import { rankingFingerprint } from './title-ranking.js';
8
9
 
9
10
  // Each entry: `changed(before, after)` says whether a change matters (by
10
11
  // default, any change to the stored value), and `run(db, after, before)` does
11
12
  // the work and returns what to tell the page.
13
+ // Rescore everyone and count who changed tier, from the stored tiers before and after.
14
+ function rescoreCountingMoves(db) {
15
+ const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
16
+ try {
17
+ const before = new Map(rows().map((r) => [r.id, r.tier]));
18
+ const { scored } = rescoreAll();
19
+ const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
20
+ return { scored, people, moved: up + down, up, down };
21
+ } catch (err) {
22
+ throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
23
+ }
24
+ }
25
+
12
26
  const EFFECTS = {
27
+ // Title points are part of everyone's score, so a new ranking rescores everyone, as a new tier scale does.
28
+ titleRanking: {
29
+ changed: (before, after) => rankingFingerprint(before) !== rankingFingerprint(after),
30
+ run: (db) => rescoreCountingMoves(db),
31
+ },
13
32
  tierScale: {
14
33
  // Tiers are graded when everyone is scored, so a new scale rescores
15
34
  // everyone. The answer counts who changed tier, from the stored tiers
16
35
  // before and after, with the sector focus's own counter (tierMoves): a
17
36
  // person reachable through two bridges is two rows but one person, counted
18
37
  // once at their closest degree.
19
- run(db) {
20
- const rows = () => db.prepare('SELECT id, degree, profile_url, tier FROM linkedin_connections').all();
21
- try {
22
- const before = new Map(rows().map((r) => [r.id, r.tier]));
23
- const { scored } = rescoreAll();
24
- const { people, up, down } = tierMoves(rows(), (r) => before.get(r.id), (r) => r.tier, { examples: 0 });
25
- return { scored, people, moved: up + down, up, down };
26
- } catch (err) {
27
- throw new Error(`Saved, but rescoring your network failed (${err.message}). It will try again the next time the map loads.`);
28
- }
29
- },
38
+ run: (db) => rescoreCountingMoves(db),
30
39
  },
31
40
  sectorFocus: {
32
41
  // Only a change to what scoring uses: a strength with no sectors picked
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { SECTOR_FOCUS_SETTING, FIELD_ASKED_SETTING } from './sector-focus.js';
14
14
  import { TIER_SCALE_SETTING } from './scoring.js';
15
+ import { TITLE_RANKING_SETTING } from './title-ranking.js';
15
16
 
16
17
  export const SETTINGS = {
17
18
  // Scores → Your sector: industries whose companies count for more (lib/sector-focus.js).
@@ -20,6 +21,8 @@ export const SETTINGS = {
20
21
  fieldAsked: FIELD_ASKED_SETTING,
21
22
  // Scores → Tiers: graded on your network's curve, or on the fixed scale (lib/scoring.js).
22
23
  tierScale: TIER_SCALE_SETTING,
24
+ // Scores → Titles: how much each kind of title counts, and roles you're looking for (lib/title-ranking.js).
25
+ titleRanking: TITLE_RANKING_SETTING,
23
26
  };
24
27
 
25
28
  const KEY = 'settings';
@@ -0,0 +1,99 @@
1
+ // The Filters panel's grid: five tiers down, six degrees across, the same in
2
+ // Network Circle and in Degrees.
3
+ //
4
+ // Blake, 2026-10-02: "keeping the tiers as is but enlarging them and having 6
5
+ // dots to let the user tap on to toggle or not if they want specific degrees
6
+ // showing for whatever tier … it will be universal for both".
7
+ //
8
+ // A grid is { hidden: [tiers switched off], degrees: { S: [1, 2], … } }: a tier
9
+ // row switches on and off as it always did, and remembers which degrees it
10
+ // shows while it's off. Counts are { S: { 1: n, 2: n, … }, … }: how many people
11
+ // there are at each tier and degree, so a dot with nobody behind it can't be
12
+ // tapped, and the last people on screen can't be switched off.
13
+
14
+ export const TIERS = ['S', 'A', 'B', 'C', 'D'];
15
+ export const GRID_DEGREES = [1, 2, 3, 4, 5, 6];
16
+
17
+ const tierOf = (tier) => (TIERS.includes(tier) ? tier : 'D'); // no tier: with D, as the views draw it
18
+
19
+ export const makeGrid = (degrees = GRID_DEGREES) => ({
20
+ hidden: [],
21
+ degrees: Object.fromEntries(TIERS.map((t) => [t, degrees.slice()])),
22
+ });
23
+
24
+ /** Network Circle starts with your own connections; you pick the rest. */
25
+ export const NETWORK_GRID = Object.freeze(makeGrid([1]));
26
+ /** Degrees starts with everything it has. */
27
+ export const DEGREES_GRID = Object.freeze(makeGrid());
28
+
29
+ /** Is someone of this tier, at this degree, on screen? */
30
+ export function shows(grid, tier, degree) {
31
+ const t = tierOf(tier);
32
+ return !grid.hidden.includes(t) && grid.degrees[t].includes(degree);
33
+ }
34
+
35
+ const countAt = (counts, tier, degree) => counts?.[tier]?.[degree] || 0;
36
+
37
+ /** How many people one tier is showing. */
38
+ export function showingIn(grid, counts, tier) {
39
+ if (grid.hidden.includes(tier)) return 0;
40
+ return grid.degrees[tier].reduce((n, d) => n + countAt(counts, tier, d), 0);
41
+ }
42
+
43
+ /** How many people the whole grid is showing. */
44
+ export const showing = (grid, counts) => TIERS.reduce((n, t) => n + showingIn(grid, counts, t), 0);
45
+
46
+ // A change that would leave nobody on screen isn't made.
47
+ const keep = (grid, next, counts) => (counts && showing(next, counts) === 0 ? grid : next);
48
+
49
+ /** One dot: that degree, for that tier. Tapping a dot in a tier that's off switches the tier on with it. */
50
+ export function toggleCell(grid, tier, degree, counts) {
51
+ const has = grid.degrees[tier].includes(degree);
52
+ const off = grid.hidden.includes(tier);
53
+ const degrees = off || !has
54
+ ? [...new Set([...grid.degrees[tier], degree])].sort((a, b) => a - b)
55
+ : grid.degrees[tier].filter((d) => d !== degree);
56
+ return keep(grid, {
57
+ hidden: off ? grid.hidden.filter((t) => t !== tier) : grid.hidden,
58
+ degrees: { ...grid.degrees, [tier]: degrees },
59
+ }, counts);
60
+ }
61
+
62
+ /** A whole tier, off or back on with the degrees it had. */
63
+ export function toggleTier(grid, tier, counts) {
64
+ if (!grid.hidden.includes(tier)) return keep(grid, { ...grid, hidden: [...grid.hidden, tier] }, counts);
65
+ // Back on with nothing to show would look broken: give it what the others show.
66
+ const own = grid.degrees[tier];
67
+ const others = [...new Set(TIERS.flatMap((t) => (t === tier ? [] : grid.degrees[t])))].sort((a, b) => a - b);
68
+ return {
69
+ hidden: grid.hidden.filter((t) => t !== tier),
70
+ degrees: own.length ? grid.degrees : { ...grid.degrees, [tier]: others.length ? others : [1] },
71
+ };
72
+ }
73
+
74
+ /** A whole degree: off if every tier with people there is showing it, otherwise on for all of them. */
75
+ export function toggleDegree(grid, degree, counts) {
76
+ const tiers = TIERS.filter((t) => !grid.hidden.includes(t) && (!counts || countAt(counts, t, degree) > 0));
77
+ if (!tiers.length) return grid;
78
+ const allOn = tiers.every((t) => grid.degrees[t].includes(degree));
79
+ const degrees = { ...grid.degrees };
80
+ for (const t of tiers) {
81
+ degrees[t] = allOn ? degrees[t].filter((d) => d !== degree) : [...new Set([...degrees[t], degree])].sort((a, b) => a - b);
82
+ }
83
+ return keep(grid, { ...grid, degrees }, counts);
84
+ }
85
+
86
+ /** Every tier back on, each with the degrees it had. */
87
+ export const showAllTiers = (grid) => (grid.hidden.length ? { ...grid, hidden: [] } : grid);
88
+
89
+ /** People by tier and degree, for the dots: `byDegree` is { 1: rows, 2: rows, … }. */
90
+ export function gridCounts(byDegree = {}) {
91
+ const counts = Object.fromEntries(TIERS.map((t) => [t, {}]));
92
+ for (const [degree, rows] of Object.entries(byDegree)) {
93
+ for (const row of rows || []) {
94
+ const t = tierOf(row?.tier);
95
+ counts[t][degree] = (counts[t][degree] || 0) + 1;
96
+ }
97
+ }
98
+ return counts;
99
+ }
@@ -0,0 +1,83 @@
1
+ // Settings → Scores → Titles: how much each kind of title counts, your way
2
+ // (Blake, 2026-10-02: "let the user change the rankings of titles as well …
3
+ // incase they value certain people or is using this for outreaching new
4
+ // founders", and people who "find more in that role" for outbound or hiring).
5
+ //
6
+ // Two parts, both in title points (the 0–10 a title is worth before the
7
+ // company weighs it, lib/scoring.js LEVELS):
8
+ // points a level's points, yours: Owner / Entrepreneur at 10, say, to put
9
+ // founders of small companies level with CEOs
10
+ // roles words in a title, and what anyone whose title has them counts:
11
+ // "account executive" at 10 puts every AE you can reach in S
12
+ // Nothing set scores exactly as before. The model applies it (scorePerson);
13
+ // this file is the setting and its presets.
14
+
15
+ import { LEVELS } from './scoring.js';
16
+
17
+ /** The ladder shown in Settings, top to bottom (the main kinds of title). */
18
+ export const RANKED_LEVELS = ['csuite', 'owner', 'vp', 'director', 'manager', 'senior', 'ic', 'unknown', 'intern', 'student'];
19
+ export const NO_RANKING = Object.freeze({ points: Object.freeze({}), roles: Object.freeze([]) });
20
+ export const MAX_ROLES = 20;
21
+ const MAX_ROLE_TEXT = 60;
22
+
23
+ const clampPoints = (v) => {
24
+ const n = Number(v);
25
+ if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error('Title points go from 0 to 10.');
26
+ return Math.round(n * 2) / 2;
27
+ };
28
+
29
+ export const TITLE_RANKING_SETTING = {
30
+ default: NO_RANKING,
31
+ parse(value) {
32
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('A title ranking is { points, roles }.');
33
+ const points = {};
34
+ for (const [key, v] of Object.entries(value.points || {})) {
35
+ if (!Object.hasOwn(LEVELS, key)) throw new Error(`There is no kind of title called '${key}'.`);
36
+ const p = clampPoints(v);
37
+ if (p !== LEVELS[key].points) points[key] = p; // the default needn't be stored
38
+ }
39
+ const roles = [];
40
+ const seen = new Set();
41
+ for (const r of Array.isArray(value.roles) ? value.roles : []) {
42
+ const text = String(r?.text ?? '').trim().toLowerCase().replace(/\s+/g, ' ');
43
+ if (text.length < 2) throw new Error('A role needs at least two letters.');
44
+ if (text.length > MAX_ROLE_TEXT) throw new Error(`Keep a role under ${MAX_ROLE_TEXT} letters.`);
45
+ if (seen.has(text)) continue;
46
+ seen.add(text);
47
+ roles.push({ text, points: clampPoints(r.points) });
48
+ }
49
+ if (roles.length > MAX_ROLES) throw new Error(`Up to ${MAX_ROLES} roles.`);
50
+ return { points, roles };
51
+ },
52
+ };
53
+
54
+ /** The same ranking always gives the same text, so the stored scores can say which they were made with. */
55
+ export function rankingFingerprint(r = NO_RANKING) {
56
+ const points = Object.keys(r.points || {}).sort().map((k) => `${k}=${r.points[k]}`).join(',');
57
+ const roles = (r.roles || []).map((x) => `${x.text}=${x.points}`).sort().join(',');
58
+ return points || roles ? `${points}|${roles}` : 'none';
59
+ }
60
+
61
+ export const isDefaultRanking = (r) => rankingFingerprint(r) === 'none';
62
+
63
+ /** Starting points for the common reasons to change it. Each replaces the whole ranking; you can adjust from there. */
64
+ export const RANKING_PRESETS = [
65
+ { key: 'default', label: 'As the app ranks them', ranking: NO_RANKING, note: 'C-suite and founders first, then VPs, directors, managers.' },
66
+ {
67
+ key: 'founders', label: 'Founders first',
68
+ note: 'Owners and entrepreneurs count like CEOs, and anyone who says founder counts as much: for meeting founders, or selling to them.',
69
+ ranking: { points: { owner: 10 }, roles: [{ text: 'founder', points: 10 }, { text: 'co-founder', points: 10 }] },
70
+ },
71
+ {
72
+ key: 'investors', label: 'Investors',
73
+ note: 'Anyone who invests for a living rises to the top: for raising money.',
74
+ ranking: { points: {}, roles: ['investor', 'venture', 'angel', 'general partner', 'managing partner', 'principal at', 'vc'].map((text) => ({ text, points: 10 })) },
75
+ },
76
+ {
77
+ key: 'role', label: 'A role I’m looking for',
78
+ note: 'For outbound or hiring: add the role below, and everyone in it rises, whatever their level.',
79
+ ranking: null, // keeps what you have and opens the role box
80
+ },
81
+ ];
82
+
83
+ export { titleHas } from './scoring.js';
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.4.11",
3
+ "version": "0.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "six-degrees",
9
- "version": "0.4.11",
9
+ "version": "0.5.0",
10
10
  "license": "MIT",
11
11
  "os": [
12
12
  "darwin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "six-degrees",
3
- "version": "0.4.11",
3
+ "version": "0.5.0",
4
4
  "description": "Map your professional network as a galaxy — leverage tiers, bridges into new circles, and the shortest path to anyone. Runs locally on your own data.",
5
5
  "license": "MIT",
6
6
  "author": "Blake Burford",