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
@@ -1,737 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * Orbit — the galaxy from the v2 build, ported.
5
- *
6
- * Different from the original Galaxy view in three ways that matter:
7
- * - Tiers are *orbits*. Higher leverage sits closer to you, so distance from
8
- * the centre means something rather than being wherever the force settled.
9
- * - Second-degree people render as small dots fanned in an arc just outside
10
- * the connection who leads to them, so a bridge with a big circle behind it
11
- * reads as a cluster at a glance.
12
- * - Drag a 1st-degree node and it stays where you drop it if it is a bridge;
13
- * leaves re-settle. Double-click releases it.
14
- *
15
- * Imperative D3 over a ref'd <svg>: React renders the shell, d3-selection owns
16
- * everything inside it, so the simulation mutates positions without React churn.
17
- */
18
-
19
- import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
20
- import useRequests from './useRequests';
21
- import { hasRequest } from '../../lib/requests-client';
22
- import {
23
- drag,
24
- forceCollide,
25
- forceLink,
26
- forceManyBody,
27
- forceRadial,
28
- forceSimulation,
29
- select,
30
- zoom,
31
- zoomIdentity,
32
- zoomTransform,
33
- } from 'd3';
34
- import { TIER_COLORS, TIER_ORDER, TIER_RING_RADIUS, initialsFor } from '../../lib/tiers';
35
- import { localPhoto } from '../../lib/photos';
36
-
37
- const GOLDEN_ANGLE = 2.399963;
38
- const LABEL_ZOOM_THRESHOLD = 1.1;
39
- const USER_RADIUS = 26;
40
- const HUB_FALLBACK = 14; // hubs to show when no circle has been mapped yet
41
- const STANDARD_RADIUS = 4.5; // context nodes: one circle each, nothing more
42
-
43
- function safeId(id) {
44
- return String(id).replace(/[^a-zA-Z0-9_-]/g, '_');
45
- }
46
-
47
- // Every string here came from a scraped web page. TRAPS §11.
48
- function escapeHtml(s) {
49
- return String(s ?? '').replace(/[&<>"']/g, (c) =>
50
- c === '&' ? '&amp;' : c === '<' ? '&lt;' : c === '>' ? '&gt;' : c === '"' ? '&quot;' : '&#39;'
51
- );
52
- }
53
-
54
- function useIsMobile() {
55
- const [mobile, setMobile] = useState(false);
56
- useEffect(() => {
57
- const check = () => setMobile(window.innerWidth < 768);
58
- check();
59
- window.addEventListener('resize', check);
60
- return () => window.removeEventListener('resize', check);
61
- }, []);
62
- return mobile;
63
- }
64
-
65
- const OrbitGraph = forwardRef(function OrbitGraph(
66
- { connections = [], degree2 = [], onSelect, userName = 'You', userImage = null, selectedId = null, mode = 'network' },
67
- ref
68
- ) {
69
- const containerRef = useRef(null);
70
- const svgRef = useRef(null);
71
- const tooltipRef = useRef(null);
72
- const onSelectRef = useRef(onSelect);
73
- const selectedIdRef = useRef(selectedId);
74
- const zoomKRef = useRef(1);
75
- const savedTransformRef = useRef(null);
76
- const applySelectionRef = useRef(() => {});
77
- const applyRequestsRef = useRef(() => {});
78
- const focusNodeRef = useRef(() => {});
79
- const [size, setSize] = useState({ w: 0, h: 0 });
80
- const isMobile = useIsMobile();
81
-
82
- // The parent recomputes these arrays on every render, so depending on their
83
- // identity rebuilds the whole scene constantly — and a rebuild that happens
84
- // before the simulation's first frame means it never ticks at all. With a
85
- // small network that settled in the gaps and looked fine; at 749 nodes and
86
- // 598 links every node stayed at 0,0. Depend on what is actually in them.
87
- const signature = useMemo(
88
- () => `${connections.length}:${degree2.length}:` +
89
- `${connections.map((c) => c.id).join(',')}|${degree2.map((c) => c.id).join(',')}`,
90
- [connections, degree2]
91
- );
92
- const dataRef = useRef({ connections, degree2 });
93
-
94
- useImperativeHandle(ref, () => ({ focusNode: (id) => focusNodeRef.current(id) }), []);
95
-
96
- useEffect(() => { onSelectRef.current = onSelect; }, [onSelect]);
97
-
98
- // Declared before the scene effect so it has already run when that rebuilds.
99
- useEffect(() => { dataRef.current = { connections, degree2 }; }, [connections, degree2]);
100
-
101
- // Measure synchronously first — first paint must not wait on ResizeObserver.
102
- useEffect(() => {
103
- const el = containerRef.current;
104
- if (!el) return undefined;
105
- const measure = () => {
106
- const rect = el.getBoundingClientRect();
107
- setSize((prev) => {
108
- const w = Math.round(rect.width);
109
- const h = Math.round(rect.height);
110
- return prev.w === w && prev.h === h ? prev : { w, h };
111
- });
112
- };
113
- measure();
114
- const ro = new ResizeObserver(measure);
115
- ro.observe(el);
116
- window.addEventListener('resize', measure);
117
- return () => {
118
- ro.disconnect();
119
- window.removeEventListener('resize', measure);
120
- };
121
- }, []);
122
-
123
- // Selection restyles existing nodes; it never rebuilds the simulation.
124
- useEffect(() => {
125
- selectedIdRef.current = selectedId;
126
- applySelectionRef.current(selectedId);
127
- }, [selectedId]);
128
-
129
- // So do requests you've sent (lib/requests-client.js): a request sent from
130
- // any view rings that person's dots here at once.
131
- const requests = useRequests();
132
- const requestsRef = useRef(requests);
133
- useEffect(() => {
134
- requestsRef.current = requests;
135
- applyRequestsRef.current(requests);
136
- }, [requests]);
137
-
138
- useEffect(() => {
139
- const svgEl = svgRef.current;
140
- if (!svgEl || size.w < 40 || size.h < 40) return undefined;
141
-
142
- const width = size.w;
143
- const height = size.h;
144
-
145
- // ---- nodes & links ---------------------------------------------------
146
- const { connections: liveConnections, degree2: liveDegree2 } = dataRef.current;
147
- // In Degrees the subject is the circles, so only the people you've mapped
148
- // are drawn, each with room for everyone behind them.
149
- const degreesMode = mode === 'degrees';
150
- const mappedIds = new Set(liveDegree2.map((c) => c.source_connection_id).filter(Boolean));
151
- const visibleD1 = degreesMode ? liveConnections.filter((c) => mappedIds.has(c.id)) : liveConnections;
152
- const d1ById = new Map(visibleD1.map((c) => [c.id, c]));
153
- const visibleD2 = liveDegree2.filter(
154
- (c) => c.source_connection_id && d1ById.has(c.source_connection_id)
155
- );
156
-
157
- // Only draw rings for tiers actually on screen — the filter lives upstream.
158
- const activeTiers = new Set(visibleD1.map((c) => c.tier).filter(Boolean));
159
-
160
- // Three weights, not one.
161
- //
162
- // Drawing all 754 connections at full weight — glow, avatar, clip path,
163
- // label — is both the lag and the mess: nothing stands out because
164
- // everything is equally loud. The people whose circle you have opened are
165
- // the subject; everyone else is context. A hub is someone with a mapped
166
- // circle, falling back to the strongest people when nothing is mapped yet,
167
- // so the view is never empty of structure on a fresh network.
168
- const bridgeIds = new Set(liveDegree2.map((c) => c.source_connection_id).filter(Boolean));
169
- const hubIds = bridgeIds.size
170
- ? new Set(visibleD1.filter((c) => bridgeIds.has(c.id)).map((c) => c.id))
171
- : new Set(
172
- [...visibleD1]
173
- .sort((a, b) => (Number(b.power_score) || 0) - (Number(a.power_score) || 0))
174
- .slice(0, HUB_FALLBACK)
175
- .map((c) => c.id)
176
- );
177
-
178
- const userNode = {
179
- id: '__user__', kind: 'user', tier: null, r: USER_RADIUS,
180
- targetRadius: 0, connection: null, x: 0, y: 0, fx: 0, fy: 0,
181
- };
182
-
183
- const tierIndex = { S: 0, A: 0, B: 0, C: 0, D: 0 };
184
- const d1Nodes = [];
185
- const d1NodeById = new Map();
186
- const d1AngleById = new Map();
187
- for (const c of visibleD1) {
188
- const tier = c.tier || 'D';
189
- const angle = (tierIndex[tier] = (tierIndex[tier] || 0) + 1, (tierIndex[tier] - 1) * GOLDEN_ANGLE);
190
- const ring = TIER_RING_RADIUS[tier] || TIER_RING_RADIUS.D;
191
- const isHub = hubIds.has(c.id);
192
- const n = {
193
- id: c.id, kind: 'd1', hub: isHub, tier,
194
- r: isHub ? 7 + Number(c.power_score || 0) * 1.1 : STANDARD_RADIUS,
195
- targetRadius: ring, connection: c,
196
- x: Math.cos(angle) * ring, y: Math.sin(angle) * ring,
197
- };
198
- d1Nodes.push(n);
199
- d1NodeById.set(c.id, n);
200
- d1AngleById.set(c.id, angle);
201
- }
202
-
203
- // Group each bridge's circle so it can be fanned beside them.
204
- const clusters = new Map();
205
- for (const c of visibleD2) {
206
- const key = c.source_connection_id;
207
- const list = clusters.get(key);
208
- if (list) list.push(c);
209
- else clusters.set(key, [c]);
210
- }
211
-
212
- const d2Nodes = [];
213
- const links = [];
214
- let sceneRadius = TIER_RING_RADIUS.B + 70;
215
- if (degreesMode) {
216
- // Someone you added through a circle, whose own circle is scanned, sits in
217
- // the fan of the circle they came from, and their people fan out behind
218
- // them a band further out, and theirs behind them, to 6th degree (Blake,
219
- // 2026-09-29: Orbit stopped at each connection's 2nd degree).
220
- const nestedIn = new Map();
221
- const nested = new Set();
222
- for (const d of d1Nodes) {
223
- const from = d.connection?.unlocked_from_bridge_id;
224
- if (from == null || from === d.id || !d1NodeById.has(from)) continue;
225
- nested.add(d.id);
226
- const list = nestedIn.get(from);
227
- if (list) list.push(d); else nestedIn.set(from, [d]);
228
- }
229
- // Everyone reachable from someone on the ring is drawn in a fan; anyone
230
- // else (a loop of introductions) goes on the ring.
231
- const onRing = d1Nodes.filter((d) => !nested.has(d.id));
232
- const inFans = new Set();
233
- const walk = (d) => { for (const p of nestedIn.get(d.id) || []) if (!inFans.has(p.id)) { inFans.add(p.id); walk(p); } };
234
- onRing.forEach(walk);
235
- for (const d of d1Nodes) if (nested.has(d.id) && !inFans.has(d.id)) onRing.push(d);
236
- const behind = new Map();
237
- const reachOf = (d, stack = new Set()) => {
238
- if (behind.has(d.id)) return behind.get(d.id);
239
- if (stack.has(d.id)) return 0;
240
- stack.add(d.id);
241
- let count = clusters.get(d.id)?.length || 0;
242
- for (const p of nestedIn.get(d.id) || []) count += 1 + reachOf(p, stack);
243
- stack.delete(d.id);
244
- behind.set(d.id, count);
245
- return count;
246
- };
247
-
248
- // One ring of the people you've mapped, biggest reach first, each given
249
- // a wedge; their circle fans outward in arcs, the most powerful closest.
250
- const ordered = [...onRing].sort((a, b) => reachOf(b) - reachOf(a));
251
- const n = Math.max(1, ordered.length);
252
- const R = Math.max(140, n * 9);
253
- // Room in proportion to how many are behind each person (square-rooted,
254
- // so one huge circle can't take the whole ring): big circles spread into
255
- // fans instead of shooting out as thin spikes.
256
- const weight = (d) => Math.sqrt(reachOf(d) + 4);
257
- const total = ordered.reduce((sum, d) => sum + weight(d), 0) || 1;
258
-
259
- // `owner`'s people in rows across `wedge`, from rStart out; someone they
260
- // added gets their own narrower fan behind them. Returns the outer edge.
261
- const placeFan = (owner, angle, wedge, rStart, depth) => {
262
- const people = [
263
- ...(clusters.get(owner.id) || []).map((c) => ({ c })),
264
- ...(nestedIn.get(owner.id) || []).filter((p) => inFans.has(p.id)).map((p) => ({ added: p, c: p.connection })),
265
- ].sort((p, q) => (Number(q.c?.power_score) || 0) - (Number(p.c?.power_score) || 0));
266
- const far = depth > 2;
267
- const step = far ? 8 : 9;
268
- const subs = [];
269
- let placed = 0;
270
- let r = rStart;
271
- while (placed < people.length) {
272
- const fit = Math.max(1, Math.floor((r * wedge) / step));
273
- const row = people.slice(placed, placed + fit);
274
- row.forEach((p, j) => {
275
- const a = angle + (row.length === 1 ? 0 : (j / (row.length - 1) - 0.5) * wedge);
276
- const x = Math.cos(a) * r;
277
- const y = Math.sin(a) * r;
278
- if (p.added) {
279
- const d = p.added;
280
- d.x = d.fx = x; d.y = d.fy = y;
281
- d.hub = false; d.added = true;
282
- d.r = 5 + Math.min(5, reachOf(d) / 60);
283
- subs.push({ d, a });
284
- return;
285
- }
286
- const node = {
287
- id: p.c.id, kind: 'd2', depth, tier: p.c.tier || 'D',
288
- r: (far ? 1.3 : 1.8) + Math.min(10, Number(p.c.power_score) || 0) * (far ? 0.24 : 0.32),
289
- connection: p.c, x, y,
290
- };
291
- node.fx = node.x; node.fy = node.y;
292
- d2Nodes.push(node);
293
- if (owner.added) links.push({ source: owner, target: node, faint: true });
294
- });
295
- placed += row.length;
296
- r += step;
297
- }
298
- // Their people go behind this fan, centred on it (not on wherever the
299
- // person they came through sits), side by side in the order those people
300
- // sit, so no fan spills into the next one's space; a line runs back to
301
- // each. Wider than the wedge, they close up to fit it.
302
- let edge = r;
303
- const next = subs.filter(({ d }) => depth < 6 && reachOf(d)).sort((x, y) => x.a - y.a);
304
- if (next.length) {
305
- const from = r + 24;
306
- const gap = 10 / from;
307
- const want = next.map(({ d }) => Math.max(0.12, (Math.min(reachOf(d), 60) * 8) / from));
308
- const need = want.reduce((sum, w) => sum + w, 0) + gap * (next.length - 1);
309
- const scale = need > wedge ? wedge / need : 1;
310
- let at = angle - (need * scale) / 2;
311
- next.forEach(({ d }, k) => {
312
- const w = want[k] * scale;
313
- edge = Math.max(edge, placeFan(d, at + w / 2, w, from, depth + 1));
314
- at += w + gap * scale;
315
- });
316
- }
317
- return edge;
318
- };
319
-
320
- let cursor = -Math.PI / 2;
321
- ordered.forEach((b) => {
322
- const share = (weight(b) / total) * Math.PI * 2;
323
- const angle = cursor + share / 2;
324
- cursor += share;
325
- const wedge = Math.min(share * 0.88, 1.6);
326
- b.x = b.fx = Math.cos(angle) * R;
327
- b.y = b.fy = Math.sin(angle) * R;
328
- b.hub = true;
329
- b.r = 9 + Math.min(10, (clusters.get(b.id)?.length || 0) / 40);
330
- sceneRadius = Math.max(sceneRadius, placeFan(b, angle, wedge, R + b.r + 16, 2) + 10);
331
- });
332
- }
333
- for (const [bridgeId, cluster] of degreesMode ? [] : clusters) {
334
- const bridgeNode = d1NodeById.get(bridgeId);
335
- if (!bridgeNode) continue;
336
- const baseAngle = d1AngleById.get(bridgeId) ?? 0;
337
- const ring = bridgeNode.targetRadius;
338
- const fanStep = Math.min(0.15, 1.5 / Math.max(1, cluster.length));
339
- cluster.forEach((c, j) => {
340
- const angle = baseAngle + (j - (cluster.length - 1) / 2) * fanStep;
341
- const radius = ring + 46 + (j % 3) * 13;
342
- const n = {
343
- id: c.id, kind: 'd2', tier: c.tier || 'D', r: 3.5,
344
- targetRadius: ring + 60, connection: c,
345
- x: Math.cos(angle) * radius, y: Math.sin(angle) * radius,
346
- };
347
- d2Nodes.push(n);
348
- links.push({ source: n, target: bridgeNode });
349
- });
350
- }
351
-
352
- const simNodes = [userNode, ...d1Nodes, ...d2Nodes];
353
- // Paint order: faint D2 first, then D1, the user on top.
354
- const renderNodes = [...d2Nodes, ...d1Nodes, userNode];
355
-
356
- // ---- static scene ----------------------------------------------------
357
- const svg = select(svgEl);
358
- svg.selectAll('*').remove();
359
-
360
- const defs = svg.append('defs');
361
- const grad = defs.append('radialGradient').attr('id', 'og-user-grad');
362
- grad.append('stop').attr('offset', '0%').attr('stop-color', '#f8fafc');
363
- grad.append('stop').attr('offset', '55%').attr('stop-color', '#c7d2fe');
364
- grad.append('stop').attr('offset', '100%').attr('stop-color', '#818cf8');
365
- defs.append('filter').attr('id', 'og-blur')
366
- .attr('x', '-75%').attr('y', '-75%').attr('width', '250%').attr('height', '250%')
367
- .append('feGaussianBlur').attr('stdDeviation', 6);
368
-
369
- const viewport = svg.append('g').attr('class', 'viewport');
370
- const ringsLayer = viewport.append('g');
371
- const spokesLayer = viewport.append('g');
372
- const linksLayer = viewport.append('g');
373
- const nodesLayer = viewport.append('g');
374
-
375
- const hubNodes = d1Nodes.filter((d) => d.hub);
376
- const spoke = spokesLayer.selectAll('line').data(hubNodes, (d) => d.id).join('line')
377
- .attr('stroke', (d) => TIER_COLORS[d.tier] || TIER_COLORS.D)
378
- .attr('stroke-opacity', 0.09)
379
- .attr('stroke-width', 0.5);
380
-
381
- if (degreesMode && d1Nodes.length) {
382
- const R = Math.hypot(d1Nodes[0].x, d1Nodes[0].y);
383
- ringsLayer.append('circle').attr('r', R).attr('fill', 'none')
384
- .attr('stroke', '#FF6B35').attr('stroke-opacity', 0.18).attr('stroke-width', 1)
385
- .attr('stroke-dasharray', '3 7');
386
- }
387
- for (const tier of degreesMode ? [] : TIER_ORDER) {
388
- if (!activeTiers.has(tier)) continue;
389
- ringsLayer.append('circle')
390
- .attr('r', TIER_RING_RADIUS[tier])
391
- .attr('fill', 'none')
392
- .attr('stroke', TIER_COLORS[tier])
393
- .attr('stroke-opacity', 0.14)
394
- .attr('stroke-width', 1)
395
- .attr('stroke-dasharray', '3 7');
396
- }
397
-
398
- const link = linksLayer.selectAll('line').data(links).join('line')
399
- .attr('stroke', (l) => (l.faint ? 'rgba(0,255,136,0.12)' : 'rgba(255,255,255,0.08)'))
400
- .attr('stroke-width', (l) => (l.faint ? 0.5 : 1));
401
-
402
- const node = nodesLayer.selectAll('g').data(renderNodes, (d) => d.id).join('g')
403
- .attr('cursor', 'pointer');
404
-
405
- node.each(function build(d) {
406
- const g = select(this);
407
-
408
- if (d.kind === 'd2') {
409
- g.attr('opacity', degreesMode ? (d.depth > 2 ? 0.6 : 0.85) : 0.55);
410
- g.append('circle').attr('class', 'og-base').attr('r', d.r)
411
- .attr('fill', TIER_COLORS[d.tier] || TIER_COLORS.D);
412
- return; // selection ring is created on demand, see applySelection
413
- }
414
-
415
- const isUser = d.kind === 'user';
416
- const conn = d.connection;
417
- const color = isUser ? '#818cf8' : (TIER_COLORS[d.tier] || TIER_COLORS.D);
418
-
419
- // Context: one circle, no avatar, no initials, no label. Hovering or
420
- // selecting promotes it — the tooltip carries the detail, so nothing is
421
- // hidden, it is just not all shouted at once.
422
- if (!isUser && !d.hub) {
423
- g.attr('opacity', d.added ? 1 : 0.75);
424
- // Someone you added through a circle: green-ringed, with their own fan behind them.
425
- g.append('circle').attr('class', 'og-base').attr('r', d.r).attr('fill', color)
426
- .attr('stroke', d.added ? '#00ff88' : 'none').attr('stroke-width', d.added ? 1.5 : 0);
427
- return; // selection ring is created on demand, see applySelection
428
- }
429
-
430
- if (isUser || d.tier === 'S' || d.tier === 'A') {
431
- g.append('circle').attr('r', d.r * 1.8).attr('fill', color)
432
- .attr('opacity', 0.25).attr('filter', 'url(#og-blur)');
433
- }
434
-
435
- if (conn && conn.is_catalyst) {
436
- g.append('circle').attr('r', d.r + 3).attr('fill', 'none')
437
- .attr('stroke', TIER_COLORS.S).attr('stroke-width', 1).attr('stroke-opacity', 0.9);
438
- g.append('text').text('⚡').attr('x', d.r * 0.85).attr('y', -d.r * 0.7)
439
- .attr('font-size', 9).attr('text-anchor', 'middle').style('pointer-events', 'none');
440
- }
441
-
442
- // Initials always render; an avatar draws on top and removes itself on
443
- // error, so a dead image never leaves an empty circle.
444
- g.append('circle').attr('class', 'og-base').attr('r', d.r)
445
- .attr('fill', isUser ? 'url(#og-user-grad)' : color);
446
-
447
- const name = isUser ? userName : (conn?.name ?? '');
448
- g.append('text').attr('class', 'og-initials').text(initialsFor(name))
449
- .attr('text-anchor', 'middle').attr('dy', '0.35em')
450
- .attr('font-size', d.r * 0.8).attr('font-weight', 700)
451
- .attr('fill', '#0b0b16').style('pointer-events', 'none');
452
-
453
- // Only a photo saved on this computer (lib/photos.js).
454
- const imageUrl = localPhoto(isUser ? userImage : conn?.profile_image_url);
455
- if (imageUrl) {
456
- const clipId = `og-clip-${safeId(d.id)}`;
457
- defs.append('clipPath').attr('id', clipId).append('circle').attr('r', d.r);
458
- g.append('image')
459
- .attr('href', imageUrl)
460
- .attr('x', -d.r).attr('y', -d.r)
461
- .attr('width', d.r * 2).attr('height', d.r * 2)
462
- .attr('clip-path', `url(#${clipId})`)
463
- .attr('preserveAspectRatio', 'xMidYMid slice')
464
- .on('error', function onErr() { select(this).remove(); });
465
- }
466
-
467
- g.append('circle').attr('class', 'og-sel').attr('r', d.r + 5)
468
- .attr('fill', 'none').attr('stroke', '#ffffff').attr('stroke-width', 1.5)
469
- .style('opacity', 0);
470
-
471
- if (isUser) {
472
- g.append('text').text(userName).attr('y', d.r + 18)
473
- .attr('text-anchor', 'middle').attr('font-size', 12).attr('font-weight', 600)
474
- .attr('fill', '#e2e8f0').attr('paint-order', 'stroke')
475
- .attr('stroke', 'rgba(5,5,16,0.85)').attr('stroke-width', 3)
476
- .style('pointer-events', 'none');
477
- } else {
478
- g.append('text').attr('class', 'og-label').text(conn?.name ?? '')
479
- .attr('y', d.r + 13).attr('text-anchor', 'middle').attr('font-size', 10)
480
- .attr('fill', '#cbd5e1').attr('paint-order', 'stroke')
481
- .attr('stroke', 'rgba(5,5,16,0.8)').attr('stroke-width', 3)
482
- .style('pointer-events', 'none').style('opacity', 0)
483
- .style('transition', 'opacity 160ms ease');
484
- }
485
- });
486
-
487
- // ---- labels ----------------------------------------------------------
488
- const labels = node.select('text.og-label');
489
- let hoveredId = null;
490
- // S-tier and catalysts stay labelled so the map is scannable zoomed out.
491
- // Hubs carry their name; context nodes have no label element at all, which
492
- // is most of what was removed.
493
- const alwaysLabeled = (d) => d.hub === true;
494
- const refreshLabels = () => {
495
- labels.style('opacity', (d) =>
496
- alwaysLabeled(d) ||
497
- zoomKRef.current > LABEL_ZOOM_THRESHOLD ||
498
- d.id === hoveredId ||
499
- d.id === selectedIdRef.current ? 1 : 0
500
- );
501
- };
502
-
503
- // ---- interaction -----------------------------------------------------
504
- node.on('click', (event, d) => {
505
- if (event.defaultPrevented) return;
506
- event.stopPropagation();
507
- onSelectRef.current?.(d.connection);
508
- });
509
- svg.on('click', () => onSelectRef.current?.(null));
510
-
511
- const tip = tooltipRef.current;
512
- const moveTip = (event) => {
513
- const el = containerRef.current;
514
- if (!tip || !el) return;
515
- const rect = el.getBoundingClientRect();
516
- tip.style.left = `${Math.min(event.clientX - rect.left + 14, rect.width - 190)}px`;
517
- tip.style.top = `${Math.max(event.clientY - rect.top - 8, 8)}px`;
518
- };
519
- const showTip = (event, d) => {
520
- if (!tip || !d.connection) return;
521
- const c = d.connection;
522
- const meta = [c.role || c.headline, c.company].filter(Boolean).map(String);
523
- tip.innerHTML =
524
- `<div style="font-weight:600;color:#f1f5f9">${escapeHtml(c.name)}</div>` +
525
- (meta.length ? `<div style="color:#94a3b8;margin-top:1px">${escapeHtml(meta.join(' · '))}</div>` : '') +
526
- `<div style="margin-top:3px;color:${TIER_COLORS[c.tier] || TIER_COLORS.D}">` +
527
- `${escapeHtml(c.tier || '?')} · power ${Number(c.power_score || 0).toFixed(1)}` +
528
- `${c.is_catalyst ? ' · ⚡ catalyst' : ''}</div>`;
529
- tip.style.opacity = '1';
530
- moveTip(event);
531
- };
532
- const hideTip = () => { if (tip) tip.style.opacity = '0'; };
533
-
534
- if (!isMobile) {
535
- node
536
- .on('mouseenter', (event, d) => { hoveredId = d.id; refreshLabels(); showTip(event, d); })
537
- .on('mousemove', (event) => moveTip(event))
538
- .on('mouseleave', () => { hoveredId = null; refreshLabels(); hideTip(); });
539
- }
540
-
541
- // ---- positions -------------------------------------------------------
542
- const ticked = () => {
543
- link
544
- .attr('x1', (l) => l.source.x ?? 0).attr('y1', (l) => l.source.y ?? 0)
545
- .attr('x2', (l) => l.target.x ?? 0).attr('y2', (l) => l.target.y ?? 0);
546
- spoke
547
- .attr('x1', 0).attr('y1', 0)
548
- .attr('x2', (d) => d.x ?? 0).attr('y2', (d) => d.y ?? 0);
549
- node.attr('transform', (d) => `translate(${d.x ?? 0},${d.y ?? 0})`);
550
- };
551
-
552
- let sim = null;
553
- if (isMobile || degreesMode) {
554
- ticked(); // static layouts: golden-angle on phones, wedges in Degrees
555
- } else {
556
- const simulation = forceSimulation(simNodes)
557
- // A 2nd-degree person belongs to their bridge, not to a ring. Giving
558
- // them their own radial target fought the link holding them to that
559
- // bridge, and the result was dots strewn across everyone else's space.
560
- // Zero here lets the link force gather each circle around its own hub.
561
- .force('radial', forceRadial((d) => d.targetRadius, 0, 0)
562
- .strength((d) => (d.kind === 'd1' ? 0.9 : 0)))
563
- .force('collide', forceCollide((d) => d.r + 2.5))
564
- .force('charge', forceManyBody().strength(-18))
565
- .on('tick', ticked);
566
- if (links.length > 0) {
567
- simulation.force('link', forceLink(links).distance(26).strength(0.7));
568
- }
569
- sim = simulation;
570
-
571
- // Place everyone at their computed starting positions right away. The
572
- // simulation ticks on requestAnimationFrame, which does not run in a
573
- // background or hidden tab — without this the whole graph sits stacked at
574
- // the origin until the tab is looked at.
575
- ticked();
576
-
577
- const dragBehavior = drag()
578
- .on('start', (event, d) => {
579
- if (!event.active) simulation.alphaTarget(0.3).restart();
580
- d.fx = d.x; d.fy = d.y;
581
- })
582
- .on('drag', (event, d) => { d.fx = event.x; d.fy = event.y; })
583
- .on('end', (event, d) => {
584
- if (!event.active) simulation.alphaTarget(0);
585
- // Bridges stay where you drop them; leaves re-settle.
586
- const isBridge = d.connection?.circle_power !== null && d.connection?.circle_power !== undefined;
587
- if (!isBridge) { d.fx = null; d.fy = null; }
588
- });
589
- node.filter((d) => d.kind === 'd1').call(dragBehavior);
590
- node.on('dblclick', (event, d) => {
591
- if (d.kind !== 'd1') return;
592
- event.stopPropagation();
593
- d.fx = null; d.fy = null;
594
- simulation.alphaTarget(0.2).restart();
595
- window.setTimeout(() => simulation.alphaTarget(0), 400);
596
- });
597
- }
598
-
599
- // ---- zoom & pan ------------------------------------------------------
600
- const zoomBehavior = zoom().scaleExtent([0.15, 3]).on('zoom', (event) => {
601
- viewport.attr('transform', event.transform.toString());
602
- // Persist only user-driven camera moves; saving the programmatic initial
603
- // transform would pin the view to stale dimensions across resizes.
604
- if (event.sourceEvent) savedTransformRef.current = event.transform;
605
- if (event.transform.k !== zoomKRef.current) {
606
- zoomKRef.current = event.transform.k;
607
- refreshLabels();
608
- }
609
- });
610
- svg.call(zoomBehavior).on('dblclick.zoom', null);
611
-
612
- const fitK = Math.max(0.15, Math.min(3, Math.min(width, height) / (sceneRadius * 2)));
613
- const initialTransform =
614
- savedTransformRef.current ?? zoomIdentity.translate(width / 2, height / 2).scale(fitK);
615
- svg.call(zoomBehavior.transform, initialTransform);
616
-
617
- // ---- selection -------------------------------------------------------
618
- applySelectionRef.current = (id) => {
619
- // Hubs keep a permanent ring; everyone else gets one only while selected.
620
- // Creating 1,400 invisible rings up front was most of the element count.
621
- nodesLayer.selectAll('circle.og-sel-temp').remove();
622
- node.select('circle.og-sel').style('opacity', (d) => (d.id === id ? 1 : 0));
623
- node.select('circle.og-base').attr('r', (d) => (d.id === id && d.kind !== 'user' ? d.r + 1.5 : d.r));
624
- if (id) {
625
- node.filter((d) => d.id === id && !d.hub && d.kind !== 'user')
626
- .append('circle')
627
- .attr('class', 'og-sel-temp')
628
- .attr('r', (d) => d.r + 3)
629
- .attr('fill', 'none')
630
- .attr('stroke', '#ffffff')
631
- .attr('stroke-width', 1.2);
632
- }
633
- refreshLabels();
634
- };
635
- applySelectionRef.current(selectedIdRef.current);
636
-
637
- // ---- requests you've sent ---------------------------------------------
638
- // A dashed gold ring on every 2nd-degree dot you've asked, in step with
639
- // the shared list, without rebuilding the scene.
640
- applyRequestsRef.current = (state) => {
641
- nodesLayer.selectAll('circle.og-asked').remove();
642
- node.filter((d) => d.kind === 'd2' && hasRequest(d.connection, state))
643
- .append('circle')
644
- .attr('class', 'og-asked')
645
- .attr('r', (d) => d.r + 2.2)
646
- .attr('fill', 'none')
647
- .attr('stroke', '#FFD700')
648
- .attr('stroke-width', 1)
649
- .attr('stroke-dasharray', '2 1.6')
650
- .style('pointer-events', 'none');
651
- };
652
- applyRequestsRef.current(requestsRef.current);
653
-
654
- // ---- fly-to ----------------------------------------------------------
655
- const nodeById = new Map(renderNodes.map((n) => [n.id, n]));
656
- focusNodeRef.current = (id) => {
657
- const target = nodeById.get(id);
658
- if (!target) return;
659
- const tx = target.x ?? 0;
660
- const ty = target.y ?? 0;
661
- const start = zoomTransform(svgEl);
662
- const end = zoomIdentity.translate(width / 2, height / 2).scale(2.5).translate(-tx, -ty);
663
- const t0 = performance.now();
664
- const flyDur = 650;
665
- const fly = (t) => {
666
- const p = Math.min(1, (t - t0) / flyDur);
667
- const e = 1 - Math.pow(1 - p, 3);
668
- zoomBehavior.transform(svg, zoomIdentity
669
- .translate(start.x + (end.x - start.x) * e, start.y + (end.y - start.y) * e)
670
- .scale(start.k + (end.k - start.k) * e));
671
- if (p < 1) requestAnimationFrame(fly);
672
- };
673
- requestAnimationFrame(fly);
674
- // rAF is throttled in background tabs; make sure the camera still lands.
675
- window.setTimeout(() => zoomBehavior.transform(svg, end), flyDur + 80);
676
-
677
- onSelectRef.current?.(target.connection);
678
- selectedIdRef.current = id;
679
- applySelectionRef.current(id);
680
-
681
- const pulse = nodesLayer.append('circle')
682
- .attr('cx', tx).attr('cy', ty).attr('r', target.r + 2)
683
- .attr('fill', 'none').attr('stroke', '#ffffff').attr('stroke-width', 2)
684
- .attr('opacity', 0.9);
685
- const p0 = performance.now();
686
- const pulseDur = 700;
687
- const puls = (t) => {
688
- const p = Math.min(1, (t - p0) / pulseDur);
689
- pulse.attr('r', target.r + 2 + 26 * p).attr('opacity', 0.9 * (1 - p));
690
- if (p < 1) requestAnimationFrame(puls);
691
- else pulse.remove();
692
- };
693
- requestAnimationFrame(puls);
694
- window.setTimeout(() => pulse.remove(), pulseDur + 100);
695
- };
696
-
697
- return () => {
698
- sim?.stop();
699
- applySelectionRef.current = () => {};
700
- applyRequestsRef.current = () => {};
701
- focusNodeRef.current = () => {};
702
- svg.on('.zoom', null);
703
- svg.on('click', null);
704
- svg.selectAll('*').remove();
705
- };
706
- }, [signature, userName, userImage, isMobile, size, mode]);
707
-
708
- return (
709
- <div
710
- ref={containerRef}
711
- // The view area is a flex column, so a percentage height resolves
712
- // against nothing and collapses to zero. Flex into the space instead,
713
- // the way the other views do.
714
- style={{ position: 'relative', flex: 1, minHeight: 0, width: '100%' }}
715
- >
716
- <svg
717
- ref={svgRef}
718
- style={{ display: 'block', height: '100%', width: '100%', touchAction: 'none', userSelect: 'none' }}
719
- role="img"
720
- aria-label="Your network as an orbital galaxy — closer orbits are higher-leverage tiers"
721
- />
722
- <div
723
- ref={tooltipRef}
724
- aria-hidden
725
- style={{
726
- position: 'absolute', zIndex: 20, maxWidth: 190, pointerEvents: 'none',
727
- padding: '6px 10px', fontSize: 11.5, lineHeight: 1.45, borderRadius: 8,
728
- background: 'rgba(10,10,26,0.92)', border: '1px solid rgba(255,255,255,0.12)',
729
- backdropFilter: 'blur(8px)', opacity: 0, transition: 'opacity 150ms ease',
730
- left: 0, top: 0,
731
- }}
732
- />
733
- </div>
734
- );
735
- });
736
-
737
- export default OrbitGraph;