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
@@ -4,10 +4,13 @@ import { Suspense, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { useSearchParams } from 'next/navigation';
5
5
  import OnboardingGate from '../components/OnboardingGate';
6
6
  import Link from 'next/link';
7
- import { stopScrape, pickedPerson } from '../../lib/scraper-client';
7
+ import { stopScrape, pickedPerson, scanRequest, HIDE_CHROME_KEY } from '../../lib/scraper-client';
8
8
  import { setupStep, askForField } from '../../lib/scanner-setup';
9
9
  import { IS_DEMO } from '../../lib/demo';
10
10
  import { circleScanCost } from '../../lib/reach';
11
+ import ScanRadar from '../components/ScanRadar';
12
+ import AppTabs from '../components/AppTabs';
13
+ import { paceOf } from '../../lib/scan-pace';
11
14
  import { BudgetBox, CooldownBanner, PausedList } from '../components/LinkedInLimits';
12
15
  import FieldStep, { FieldAnswer } from '../components/FieldStep';
13
16
 
@@ -68,8 +71,10 @@ function SetupInner() {
68
71
  // Experimental Auto-Bridge: all-day pacing and LinkedIn's own data (scripts/scrape.py --experimental).
69
72
  const [experimental, setExperimental] = useRemembered('six-degrees-experimental-auto', false);
70
73
  // Scanning with no Chrome window (scripts/scrape.py --headless); lib/scraper-client.js reads it for every scan.
71
- const [hideChrome, setHideChrome] = useRemembered('six-degrees-hide-chrome', false);
74
+ const [hideChrome, setHideChrome] = useRemembered(HIDE_CHROME_KEY, false);
72
75
  const [error, setError] = useState(null);
76
+ // The step open on the rail: null follows the one you're on.
77
+ const [view, setView] = useState(null);
73
78
  const logRef = useRef(null);
74
79
  // What's saved, for the question about your field: undefined while it
75
80
  // loads, null when it couldn't be read. And the answer given here, if any.
@@ -143,7 +148,9 @@ function SetupInner() {
143
148
  const r = await fetch('/api/scraper', {
144
149
  method: 'POST',
145
150
  headers: { 'Content-Type': 'application/json' },
146
- body: JSON.stringify({ action, ...extra }),
151
+ // With "Hide the Chrome window" from the switch below; this page's own
152
+ // buttons used to leave it out, so it never hid anything started here.
153
+ body: JSON.stringify(scanRequest(action, extra, hideChrome)),
147
154
  });
148
155
  const d = await r.json();
149
156
  if (!r.ok) setError(d.error || 'Could not start.');
@@ -173,6 +180,9 @@ function SetupInner() {
173
180
  // you know, then the people found through them, then company scans.
174
181
  const net = s?.network || { first: 0, second: 0, third: 0 };
175
182
  const mapped = net.first;
183
+ // The step you're on: the first not done. Any other opens from the rail.
184
+ const current = !s || !step1.done ? 1 : !c.signedIn ? 2 : mapped === 0 ? 3 : 4;
185
+ const shown = view ?? current;
176
186
  // Your field, asked once before the first scan (lib/scanner-setup.js
177
187
  // askForField). The steps wait while that isn't known yet (null).
178
188
  const askField = IS_DEMO || field ? false : askForField(s, settings);
@@ -191,29 +201,46 @@ function SetupInner() {
191
201
  minHeight: '100vh', background: BG, color: '#fff',
192
202
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
193
203
  }}>
194
- <header style={{ padding: '16px 24px', borderBottom: LINE, display: 'flex', alignItems: 'center', gap: 16 }}>
195
- <Link href="/" style={{
196
- display: 'flex', alignItems: 'center', gap: 6, color: '#888', textDecoration: 'none',
197
- fontSize: 13, fontWeight: 600, padding: '6px 14px', borderRadius: 6,
198
- background: 'rgba(255,255,255,0.06)', border: LINE,
199
- }}>← Back to Map</Link>
200
- <h1 style={{
201
- fontSize: 22, fontWeight: 700, margin: 0,
202
- background: 'linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)',
203
- WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
204
- }}>Scan your network</h1>
204
+ {/* The same header as every page, Scan lit (Blake, 2026-10-02: continuity) */}
205
+ <header style={{ padding: '20px 30px', borderBottom: LINE }}>
206
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
207
+ <Link href="/" style={{ textDecoration: 'none' }}>
208
+ <span style={{ fontSize: 28, fontWeight: 700, background: 'linear-gradient(135deg, #FFD700, #9B59B6, #3498DB)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
209
+ Six Degrees
210
+ </span>
211
+ </Link>
212
+ <AppTabs active="scan" />
213
+ <Link href="/settings" title="Settings" aria-label="Settings" style={{
214
+ width: 32, height: 32, borderRadius: '50%', flexShrink: 0, background: 'rgba(255,255,255,0.06)', color: '#888',
215
+ fontSize: 16, textDecoration: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center',
216
+ }}>⚙</Link>
217
+ </div>
205
218
  </header>
206
219
 
207
220
  <div style={{ maxWidth: 720, margin: '0 auto', padding: '32px 24px 64px' }}>
208
221
 
209
- <p style={{ color: '#9aa', fontSize: 15, lineHeight: 1.6, marginTop: 0 }}>
210
- This reads your own LinkedIn connections in a real Chrome window on this
211
- machine and saves them here. Nothing leaves your computer, and you will never
212
- be asked for your password — you sign in yourself, once.
213
- {!mapped && (askField
214
- ? ' One optional question, then three steps and your galaxy appears.'
215
- : ' Three steps and your galaxy appears; each one ticks itself off.')}
216
- </p>
222
+ {/* What the scanner is, in one breath, and what it never does */}
223
+ <div style={{
224
+ padding: '26px 26px 22px', borderRadius: 16, marginBottom: 22, position: 'relative', overflow: 'hidden',
225
+ background: 'radial-gradient(120% 140% at 0% 0%, rgba(0,255,136,0.10), rgba(52,152,219,0.06) 45%, rgba(255,255,255,0.02) 75%)',
226
+ border: '1px solid rgba(0,255,136,0.22)',
227
+ }}>
228
+ <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: 1.6, color: '#00ff88' }}>YOUR SCANNER</div>
229
+ <h1 style={{ fontSize: 27, fontWeight: 800, margin: '6px 0 8px', lineHeight: 1.2 }}>
230
+ {mapped > 0 ? 'Your network is mapped. Send the scanner further.' : 'Map your network in four steps.'}
231
+ </h1>
232
+ <div style={{ fontSize: 14.5, color: '#aab7c4', lineHeight: 1.6, maxWidth: 560 }}>
233
+ It works in your own Chrome on this Mac, slowly and in the open, and keeps everything here.
234
+ You launch it; it does the reading; you watch it go.
235
+ </div>
236
+ <div style={{ display: 'flex', gap: '8px 18px', flexWrap: 'wrap', marginTop: 16 }}>
237
+ {['Never posts or messages anyone', 'Never sees your password', 'Nothing leaves this Mac', 'Stops the moment you say'].map((t) => (
238
+ <span key={t} style={{ fontSize: 12.5, color: '#cfe8dc', display: 'flex', alignItems: 'center', gap: 6 }}>
239
+ <span style={{ color: '#00ff88', fontWeight: 900 }}>✓</span>{t}
240
+ </span>
241
+ ))}
242
+ </div>
243
+ </div>
217
244
 
218
245
  {/* The first look at the scanner can take a while (it looks for Python): say so, never a blank page. */}
219
246
  {askField === null && <p style={{ color: '#778', fontSize: 13 }}>Checking your setup…</p>}
@@ -314,90 +341,85 @@ function SetupInner() {
314
341
  <Box tone={step1.done ? undefined : 'bad'}>{step1.note}</Box>
315
342
  )}
316
343
 
317
- {/* ---- step 1 : the scanner itself ---- */}
318
- <Step
319
- n={1}
320
- done={step1.done}
321
- title="Set up the scanner"
322
- body={s ? step1.text : ''}
323
- action={
324
- s && step1.button && (
325
- <Btn onClick={() => run(step1.button.action)} disabled={busy || running}>
326
- {step1.button.label}
327
- </Btn>
328
- )
329
- }
330
- />
331
-
332
- {/* ---- step 2 : sign in ---- */}
333
- <Step
334
- n={2}
335
- done={!!c.signedIn}
336
- title="Sign into LinkedIn"
337
- body={
338
- c.signedIn
339
- ? 'Signed in on this machine. If LinkedIn ever asks for a security check, or a scan says you were signed out, open LinkedIn here and finish it by hand.'
340
- : 'Opens a Chrome window. Sign in with your email and password — “Continue with Google” cannot work here, because Google blocks its sign-in inside automated browsers.'
341
- }
342
- action={
343
- // Shown after sign-in too: a security check survives the session
344
- // cookie, so "signed in" can still need a person (TRAPS §35).
345
- c.dependencies && (
346
- <Btn onClick={() => run('login')} disabled={busy || running}>
347
- {running && s.action === 'login' ? 'Waiting for you…' : 'Open LinkedIn'}
348
- </Btn>
349
- )
350
- }
351
- />
352
-
353
- {/* ---- step 3 : the people you know ---- */}
354
- <Step
355
- n={3}
356
- done={mapped > 0}
357
- title="1st degree — the people you know"
358
- body="The first scan walks your whole connections list, about a minute and a half for 750 people. After that, Check for new only looks at what has been added since."
359
- action={
360
- <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
361
- <Btn onClick={() => run('full')} disabled={!canSearch} primary>
362
- {running && s.action === 'full' ? 'Scanning…' : 'Scan my whole network'}
363
- </Btn>
364
- <Btn onClick={() => run('refresh')} disabled={!canSearch}>
344
+ {/* ---- the journey (Blake, 2026-10-02: "simple and easy to understand … like they are
345
+ launching an agent"): four steps on a rail, the one you're on open, one button to
346
+ launch it. Everything else waits in Fine-tune. ---- */}
347
+ <Journey current={current} view={shown} onView={setView} steps={[
348
+ { key: 1, label: 'Get ready', done: step1.done },
349
+ { key: 2, label: 'Sign in', done: !!c.signedIn },
350
+ { key: 3, label: 'Who you know', done: mapped > 0 },
351
+ { key: 4, label: 'Who they know', done: net.second > 0 },
352
+ ]} />
353
+
354
+ {shown === 1 && (
355
+ <Mission step={1} title="Get your scanner ready" time="A minute or two, once" body={s ? step1.text : 'Checking…'}
356
+ launch={s && step1.button && (
357
+ <Launch onClick={() => run(step1.button.action)} disabled={busy || running}>{step1.button.label}</Launch>
358
+ )} />
359
+ )}
360
+ {shown === 2 && (
361
+ <Mission step={2} title="Sign in to LinkedIn, once" time="You do this part"
362
+ body={c.signedIn
363
+ ? 'Signed in on this Mac. If LinkedIn ever asks for a security check, or a scan says you were signed out, open LinkedIn here and finish it by hand.'
364
+ : 'A Chrome window opens. Sign in with your email and password there: the scanner never sees them. (“Continue with Google” can’t work in it, because Google blocks its sign-in in automated browsers.)'}
365
+ launch={c.dependencies && (
366
+ // Shown after sign-in too: a security check survives the session cookie (TRAPS §35).
367
+ <Launch onClick={() => run('login')} disabled={busy || running} quiet={!!c.signedIn}>
368
+ {running && s.action === 'login' ? 'Waiting for you…' : c.signedIn ? 'Open LinkedIn again' : 'Open LinkedIn'}
369
+ </Launch>
370
+ )} />
371
+ )}
372
+ {shown === 3 && (
373
+ <Mission step={3} title="Map the people you know" time="About a minute and a half for 750 people"
374
+ body="It reads your connections list and saves everyone here. After the first time, Check for new only looks at who you've added since."
375
+ launch={(
376
+ <Launch onClick={() => run('full')} disabled={!canSearch} quiet={mapped > 0}>
377
+ {running && s.action === 'full' ? 'Scanning…' : mapped > 0 ? 'Scan it all again' : 'Scan my network'}
378
+ </Launch>
379
+ )}
380
+ more={(
381
+ <Btn onClick={() => run('refresh')} disabled={!canSearch} primary={mapped > 0}>
365
382
  {running && s.action === 'refresh' ? 'Checking…' : 'Check for new'}
366
383
  </Btn>
367
- </div>
368
- }
369
- />
370
-
371
- {/* ---- step 4 : who they know ---- */}
372
- <Step
373
- n={4}
374
- done={false}
375
- title="2nd degree — the people they know"
376
- body={
377
- <>
378
- This is what fills <b>Degrees</b> and <b>Outlink</b>: it opens each of your
379
- connections in turn and reads who <i>they</i> know. Most people hide their
380
- connections — those are noted and never tried again.
381
- <br /><br />
382
- It is slow on purpose, about two minutes between each person, because
383
- this is the part LinkedIn notices. During development a real account was
384
- temporarily restricted after roughly <b>19 people in one sitting</b>.
385
- Run a batch, leave it for a day, run another — and stop the moment
386
- LinkedIn mentions unusual activity. It always picks up where it left
387
- off: anyone still without a mapped circle, in the order you choose below —
388
- your newest connections first, or highest tier first. Each person&rsquo;s
389
- list is read to the end, saving every 10 pages, and a read that is stopped
390
- carries on from the same page next time.
391
- </>
392
- }
393
- action={
394
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
395
- <CooldownBanner
396
- cooldown={li?.cooldown}
397
- disabled={busy}
398
- onLift={() => run('lift-cooldown')}
384
+ )} />
385
+ )}
386
+ {shown === 4 && (
387
+ <Mission step={4} title="Map who they know" time="A small batch a day, slowly"
388
+ body={<>It opens your connections one at a time and reads who <i>they</i> know: that fills Degrees, Separation and Outlink. Every round picks up where the last one stopped.</>}>
389
+ <CooldownBanner
390
+ cooldown={li?.cooldown}
391
+ disabled={busy}
392
+ onLift={() => run('lift-cooldown')}
393
+ />
394
+ {/* The radar: Scan in the middle, today's budget round it, the speed beside it */}
395
+ <div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'center', marginTop: 4 }}>
396
+ <ScanRadar
397
+ li={li}
398
+ running={Boolean(running)}
399
+ scanning={Boolean(running && s?.action?.startsWith('auto-bridge'))}
400
+ disabled={!canSearch || (order === 'score' && !tiers.length)}
401
+ label={running && s?.action === 'auto-bridge' ? 'Mapping…' : 'Map 2nd degree'}
402
+ sublabel={batch ? `${batch} people` : 'everyone'}
403
+ onScan={() => run('auto-bridge', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish, experimental })}
404
+ onPace={busy ? undefined : (pace) => run('set-limits', { ...(li?.limits || {}), pace })}
399
405
  />
400
- <BudgetBox li={li} disabled={busy} onSetLimits={(l) => run('set-limits', l)} />
406
+ <div style={{ flex: '1 1 230px', display: 'flex', flexDirection: 'column', gap: 12 }}>
407
+ <label style={{ display: 'flex', gap: 10, alignItems: 'center', fontSize: 13, color: '#cfd8d8' }}>
408
+ This round
409
+ <select value={batch} onChange={(e) => setBatch(Number(e.target.value))} disabled={running} style={selectStyle}>
410
+ <option value={5}>5 people</option>
411
+ <option value={10}>10 people</option>
412
+ <option value={25}>25 people</option>
413
+ <option value={0}>everyone — not advised</option>
414
+ </select>
415
+ </label>
416
+ <label style={{ display: 'flex', gap: 10, alignItems: 'center', fontSize: 13, color: '#cfd8d8' }}>
417
+ Start with
418
+ <select value={order} onChange={(e) => setOrder(e.target.value)} disabled={running} style={selectStyle}>
419
+ <option value="newest">Newest connections</option>
420
+ <option value="score">Highest tier</option>
421
+ </select>
422
+ </label>
401
423
  {order === 'score' && <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
402
424
  <span style={{ fontSize: 12, color: '#8b9a9a' }}>Work through</span>
403
425
  {['S', 'A', 'B', 'C', 'D'].map((t) => {
@@ -426,97 +448,22 @@ function SetupInner() {
426
448
  {' '}<b>Check for new</b> first so your latest connections are in the list.
427
449
  </div>
428
450
  )}
429
- <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
430
- <span style={{ fontSize: 12, color: '#8b9a9a' }}>Start with</span>
431
- <select value={order} onChange={(e) => setOrder(e.target.value)} disabled={running} style={selectStyle}>
432
- <option value="newest">Newest connections first</option>
433
- <option value="score">Highest tier first</option>
434
- </select>
435
- <span style={{ fontSize: 12, color: '#8b9a9a' }}>Read up to</span>
436
- <select value={pages} onChange={(e) => setPages(Number(e.target.value))} disabled={running} style={selectStyle}>
437
- <option value={100}>every page, to the end of their list</option>
438
- <option value={50}>50 pages each</option>
439
- <option value={25}>25 pages each</option>
440
- <option value={10}>10 pages (~100 people) each</option>
441
- </select>
451
+ <WhySlow />
442
452
  </div>
443
- <label style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 12.5, color: '#cfd8d8', cursor: running ? 'not-allowed' : 'pointer' }}>
444
- <input type="checkbox" checked={finish} onChange={(e) => setFinish(e.target.checked)} disabled={running} />
445
- Also finish people already mapped, from the page each one stopped at
446
- </label>
447
- <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12.5, color: '#b8c4c4', cursor: running ? 'default' : 'pointer' }}>
448
- <input type="checkbox" checked={experimental} disabled={running} style={{ marginTop: 3 }}
449
- onChange={(e) => { setExperimental(e.target.checked); window.dispatchEvent(new Event('six-degrees:experimental')); }} />
450
- <span>
451
- <b style={{ color: '#FFD700' }}>Experimental:</b> all-day pacing. Up to 8 pages in a sitting,
452
- then an hour&rsquo;s rest; searches only from 9:00 to 18:00; never more than 40 searches in a day
453
- or 200 in a week, however high your budget; two days&rsquo; rest after any check from LinkedIn; at
454
- the budget it waits instead of stopping; and it saves every page. It also reads LinkedIn&rsquo;s own data beside
455
- the page text, to fill gaps and measure how the two compare. It keeps running while the app is open.
456
- </span>
457
- </label>
458
- <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12.5, color: '#b8c4c4', cursor: running ? 'default' : 'pointer' }}>
459
- <input type="checkbox" checked={hideChrome} disabled={running} style={{ marginTop: 3 }}
460
- onChange={(e) => setHideChrome(e.target.checked)} />
461
- <span>
462
- <b style={{ color: '#FFD700' }}>Hide the Chrome window while scanning.</b> Every scan runs with no
463
- window popping up; the status bar still shows what it&rsquo;s doing, and Stop still works. Signing
464
- in always opens the window.
465
- <span style={{ display: 'block', marginTop: 4, color: '#e0a080' }}>
466
- The risks: a hidden Chrome is easier for LinkedIn to tell apart from a person, so it may make a
467
- warning or restriction more likely. If LinkedIn asks you to check it&rsquo;s you (a code, a
468
- puzzle, signing in again) you won&rsquo;t see it, and the scan will stop instead of waiting for
469
- you. Untick this and scan again to see what LinkedIn wants. It changes nothing about pacing or
470
- your daily budget.
471
- </span>
472
- </span>
473
- </label>
474
- <div style={{ fontSize: 12, color: '#FFD700', lineHeight: 1.6 }}>
475
- Every page is a LinkedIn search, so it rests 20 seconds before each one and a
476
- minute after every 10, and a long list can take
477
- {' '}about {circleScanCost(pages).minutes} minutes a person.
478
- LinkedIn shows 100 pages of anyone&rsquo;s connections at most. Free accounts
479
- have a monthly search limit: if LinkedIn says it has been reached, the scan saves
480
- what it read and stops, and carries on from that page next time. Keep batches small.
481
- </div>
482
- <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
483
- <Btn onClick={() => run('auto-bridge', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish, experimental })} disabled={!canSearch || (order === 'score' && !tiers.length)} primary>
484
- {running && s.action === 'auto-bridge' ? 'Mapping…' : 'Map 2nd degree'}
485
- </Btn>
486
- <select
487
- value={batch}
488
- onChange={(e) => setBatch(Number(e.target.value))}
489
- disabled={running}
490
- style={selectStyle}
491
- >
492
- <option value={5}>5 people</option>
493
- <option value={10}>10 people</option>
494
- <option value={25}>25 people</option>
495
- <option value={0}>everyone — not advised</option>
496
- </select>
497
- <Btn onClick={() => run('auto-bridge-retry', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish })} disabled={!canSearch || (order === 'score' && !tiers.length)}>
498
- Retry hidden ones
499
- </Btn>
500
453
  </div>
501
- <PausedList
502
- paused={s?.paused || []}
503
- disabled={!canSearch}
504
- onResume={(p) => run('resume', { id: p.id })}
505
- onResumeAll={() => run('resume-all', { maxBridges: batch })}
506
- />
507
- </div>
508
- }
509
- />
454
+ </Mission>
455
+ )}
510
456
 
511
- {/* ---- stop: one control, always where the log is ---- */}
457
+ {/* ---- the scanner at work: what it's doing, and Stop, right under the step ---- */}
512
458
  {running && (
513
459
  <div style={{
514
460
  display: 'flex', alignItems: 'center', gap: 12, marginTop: 20,
515
461
  padding: '14px 16px', borderRadius: 8,
516
- background: 'rgba(255,255,255,0.05)', border: LINE,
462
+ background: 'rgba(0,255,136,0.06)', border: '1px solid rgba(0,255,136,0.3)',
517
463
  }}>
518
464
  <Spinner />
519
465
  <div style={{ flex: 1, fontSize: 13.5 }}>
466
+ <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: 1.2, color: '#00ff88', marginBottom: 2 }}>YOUR SCANNER IS WORKING</div>
520
467
  <b>{ACTION_LABELS[s.action] || 'Working'}</b>
521
468
  {s.progress && <Progress p={s.progress} action={s.action} />}
522
469
  <div style={{ color: '#8b9a9a', fontSize: 12.5, marginTop: 2 }}>
@@ -527,18 +474,90 @@ function SetupInner() {
527
474
  </div>
528
475
  )}
529
476
 
477
+ {/* Everything you might want to change, out of the way until you do */}
478
+ <details style={{ margin: '18px 0 4px', borderRadius: 10, border: LINE, background: 'rgba(255,255,255,0.02)' }}>
479
+ <summary style={{ padding: '12px 16px', cursor: 'pointer', fontSize: 13.5, fontWeight: 650, color: '#cfd8d8' }}>
480
+ Fine-tune the scanner
481
+ <span style={{ fontWeight: 500, color: '#778', marginLeft: 8 }}>daily budget, how much of each list, the hidden window, paused lists</span>
482
+ </summary>
483
+ <div style={{ padding: '4px 16px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>
484
+ <BudgetBox li={li} disabled={busy} onSetLimits={(l) => run('set-limits', l)} />
485
+ <label style={{ display: 'flex', gap: 10, alignItems: 'center', fontSize: 12.5, color: '#cfd8d8' }}>
486
+ Read up to
487
+ <select value={pages} onChange={(e) => setPages(Number(e.target.value))} disabled={running} style={selectStyle}>
488
+ <option value={100}>every page, to the end of their list</option>
489
+ <option value={50}>50 pages each</option>
490
+ <option value={25}>25 pages each</option>
491
+ <option value={10}>10 pages (~100 people) each</option>
492
+ </select>
493
+ </label>
494
+ <label style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 12.5, color: '#cfd8d8', cursor: running ? 'not-allowed' : 'pointer' }}>
495
+ <input type="checkbox" checked={finish} onChange={(e) => setFinish(e.target.checked)} disabled={running} />
496
+ Also finish people already mapped, from the page each one stopped at
497
+ </label>
498
+ <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12.5, color: '#b8c4c4', cursor: running ? 'default' : 'pointer' }}>
499
+ <input type="checkbox" checked={experimental} disabled={running} style={{ marginTop: 3 }}
500
+ onChange={(e) => { setExperimental(e.target.checked); window.dispatchEvent(new Event('six-degrees:experimental')); }} />
501
+ <span>
502
+ <b style={{ color: '#FFD700' }}>Experimental:</b> all-day pacing. Up to 8 pages in a sitting,
503
+ then an hour&rsquo;s rest; searches only from 9:00 to 18:00; never more than 40 searches in a day
504
+ or 200 in a week, however high your budget; two days&rsquo; rest after any check from LinkedIn; at
505
+ the budget it waits instead of stopping; and it saves every page. It also reads LinkedIn&rsquo;s own data beside
506
+ the page text, to fill gaps and measure how the two compare. It keeps running while the app is open.
507
+ </span>
508
+ </label>
509
+ <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12.5, color: '#b8c4c4', cursor: running ? 'default' : 'pointer' }}>
510
+ <input type="checkbox" checked={hideChrome} disabled={running} style={{ marginTop: 3 }}
511
+ onChange={(e) => setHideChrome(e.target.checked)} />
512
+ <span>
513
+ <b style={{ color: '#FFD700' }}>Hide the Chrome window while scanning.</b> Every scan runs with no
514
+ window popping up; the status bar still shows what it&rsquo;s doing, and Stop still works. Signing
515
+ in always opens the window.
516
+ <span style={{ display: 'block', marginTop: 4, color: '#e0a080' }}>
517
+ The risks: a hidden Chrome is easier for LinkedIn to tell apart from a person, so it may make a
518
+ warning or restriction more likely. If LinkedIn asks you to check it&rsquo;s you (a code, a
519
+ puzzle, signing in again) you won&rsquo;t see it, and the scan will stop instead of waiting for
520
+ you. Untick this and scan again to see what LinkedIn wants. It changes nothing about pacing or
521
+ your daily budget.
522
+ </span>
523
+ </span>
524
+ </label>
525
+ <div style={{ fontSize: 12, color: '#FFD700', lineHeight: 1.6 }}>
526
+ Every page is a LinkedIn search, so at {paceOf(li?.limits?.pace).label} it rests{' '}
527
+ {paceOf(li?.limits?.pace).pagePause} seconds before each one and{' '}
528
+ {paceOf(li?.limits?.pace).chunkCooldown / 60 === 1 ? 'a minute' : `${paceOf(li?.limits?.pace).chunkCooldown / 60} minutes`} after every 10, and a long list can take
529
+ {' '}about {circleScanCost(pages, li?.limits?.pace).minutes} minutes a person.
530
+ LinkedIn shows 100 pages of anyone&rsquo;s connections at most. Free accounts
531
+ have a monthly search limit: if LinkedIn says it has been reached, the scan saves
532
+ what it read and stops, and carries on from that page next time. Keep batches small.
533
+ </div>
534
+ <div>
535
+ <Btn onClick={() => run('auto-bridge-retry', { maxBridges: batch, tiers: order === 'score' ? tiers : [], order, maxPages: pages, deeper: finish })} disabled={!canSearch || (order === 'score' && !tiers.length)}>
536
+ Retry hidden ones
537
+ </Btn>
538
+ <span style={{ fontSize: 12, color: '#8b9a9a', marginLeft: 10 }}>People whose list was hidden last time, in case they've opened it.</span>
539
+ </div>
540
+ <PausedList
541
+ paused={s?.paused || []}
542
+ disabled={!canSearch}
543
+ onResume={(p) => run('resume', { id: p.id })}
544
+ onResumeAll={() => run('resume-all', { maxBridges: batch })}
545
+ />
546
+ </div>
547
+ </details>
548
+
530
549
  {error && <Box tone="bad">{error}</Box>}
531
550
 
551
+ {/* The scanner's own words, for when you want them: open by itself when a run stopped badly. */}
532
552
  {(s?.log?.length > 0) && (
533
- <>
534
- <div style={{
535
- display: 'flex', alignItems: 'center', gap: 10,
536
- margin: '28px 0 8px', fontSize: 12, color: '#788',
537
- textTransform: 'uppercase', letterSpacing: 0.6,
553
+ <details open={failed} style={{ marginTop: 20 }}>
554
+ <summary style={{
555
+ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer',
556
+ margin: '8px 0', fontSize: 12, color: '#788', textTransform: 'uppercase', letterSpacing: 0.6,
538
557
  }}>
539
558
  {running && <Spinner />}
540
- {running ? 'Working' : 'Last run'}
541
- </div>
559
+ {running ? 'What it’s doing (the log)' : 'The last run’s log'}
560
+ </summary>
542
561
  <pre ref={logRef} style={{
543
562
  background: 'rgba(0,0,0,0.45)', border: LINE, borderRadius: 8,
544
563
  padding: 14, maxHeight: 280, overflow: 'auto', margin: 0,
@@ -546,7 +565,7 @@ function SetupInner() {
546
565
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
547
566
  whiteSpace: 'pre-wrap', wordBreak: 'break-word',
548
567
  }}>{s.log.join('\n')}</pre>
549
- </>
568
+ </details>
550
569
  )}
551
570
  </>}
552
571
 
@@ -575,7 +594,7 @@ function SetupInner() {
575
594
  function ScanOne({ pick, pages, setPages, li, running, s, canSearch, busy, onStart, onUnpick }) {
576
595
  const { person, circle } = pick;
577
596
  const first = String(person.name || '').trim().split(/\s+/)[0] || 'them';
578
- const cost = circleScanCost(pages);
597
+ const cost = circleScanCost(pages, li?.limits?.pace);
579
598
  const theirs = running && s?.target?.id === person.id;
580
599
  const short = li?.leftToday != null && li.leftToday < cost.searches;
581
600
  return (
@@ -665,26 +684,100 @@ const selectStyle = {
665
684
  background: 'rgba(255,255,255,0.08)', color: '#fff', border: LINE,
666
685
  };
667
686
 
668
- function Step({ n, done, title, body, action }) {
687
+ const JOURNEY_CSS = `
688
+ @keyframes stepPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(0,255,136,.45); } 50% { box-shadow: 0 0 0 7px rgba(0,255,136,0); } }
689
+ .journey-now { animation: stepPulse 2.2s ease-in-out infinite; }
690
+ .launch-go { transition: transform .15s ease, box-shadow .2s ease, filter .2s ease; }
691
+ .launch-go:not(:disabled):hover { transform: translateY(-1px); box-shadow: 0 8px 30px rgba(0,255,136,.35); filter: brightness(1.08); }
692
+ .launch-go:not(:disabled):active { transform: translateY(0) scale(.98); }
693
+ @media (prefers-reduced-motion: reduce) { .journey-now { animation: none; } }
694
+ `;
695
+
696
+ /** The four steps on a rail: done ones ticked, the one you're on pulsing, any of them opens on a tap. */
697
+ function Journey({ steps, current, view, onView }) {
698
+ return (
699
+ <div role="tablist" aria-label="Scanning, step by step" style={{ display: 'flex', alignItems: 'flex-start', margin: '4px 0 14px' }}>
700
+ <style>{JOURNEY_CSS}</style>
701
+ {steps.map((st, i) => {
702
+ const now = st.key === current && !st.done;
703
+ const open = st.key === view;
704
+ const color = st.done ? '#00ff88' : now ? '#fff' : '#667';
705
+ return (
706
+ <div key={st.key} style={{ flex: 1, display: 'flex', alignItems: 'flex-start', minWidth: 0 }}>
707
+ <button type="button" role="tab" aria-selected={open} onClick={() => onView(st.key === current ? null : st.key)}
708
+ style={{ flex: '0 0 auto', width: 76, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 7, background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}>
709
+ <span className={now ? 'journey-now' : undefined} style={{
710
+ width: 38, height: 38, borderRadius: '50%', display: 'grid', placeItems: 'center', fontSize: 15, fontWeight: 800,
711
+ background: st.done ? 'rgba(0,255,136,0.16)' : now ? 'linear-gradient(135deg, #00ff88, #1abc9c)' : 'rgba(255,255,255,0.05)',
712
+ color: st.done ? '#00ff88' : now ? '#04140c' : '#778',
713
+ border: `2px solid ${open ? '#fff' : st.done ? 'rgba(0,255,136,0.55)' : now ? 'transparent' : 'rgba(255,255,255,0.12)'}`,
714
+ }}>{st.done ? '✓' : st.key}</span>
715
+ {/* Wraps to two lines rather than running into the next on a phone */}
716
+ <span style={{ fontSize: 12, fontWeight: open ? 800 : 650, color, textAlign: 'center', lineHeight: 1.25, maxWidth: 76, overflowWrap: 'break-word' }}>{st.label}</span>
717
+ </button>
718
+ {i < steps.length - 1 && (
719
+ <span aria-hidden="true" style={{
720
+ flex: 1, height: 2, marginTop: 19, borderRadius: 1, minWidth: 8,
721
+ background: st.done ? 'linear-gradient(90deg, rgba(0,255,136,0.7), rgba(0,255,136,0.25))' : 'rgba(255,255,255,0.1)',
722
+ }} />
723
+ )}
724
+ </div>
725
+ );
726
+ })}
727
+ </div>
728
+ );
729
+ }
730
+
731
+ /** The step that's open: what it does, how long, and the one button that does it. */
732
+ function Mission({ step, title, time, body, launch, more, children }) {
669
733
  return (
670
734
  <div style={{
671
- display: 'flex', gap: 16, padding: '20px 0', borderBottom: LINE, alignItems: 'flex-start',
735
+ padding: '20px 22px', borderRadius: 14, marginBottom: 8,
736
+ background: 'rgba(255,255,255,0.035)', border: '1px solid rgba(255,255,255,0.1)',
672
737
  }}>
673
- <div style={{
674
- flexShrink: 0, width: 28, height: 28, borderRadius: '50%',
675
- display: 'grid', placeItems: 'center', fontSize: 13, fontWeight: 700,
676
- background: done ? 'rgba(0,255,136,0.15)' : 'rgba(255,255,255,0.08)',
677
- color: done ? '#00ff88' : '#889', border: `1px solid ${done ? 'rgba(0,255,136,0.4)' : 'rgba(255,255,255,0.14)'}`,
678
- }}>{done ? '✓' : n}</div>
679
- <div style={{ flex: 1, minWidth: 0 }}>
680
- <div style={{ fontSize: 15.5, fontWeight: 650, marginBottom: 4 }}>{title}</div>
681
- <div style={{ fontSize: 13.5, color: '#8b9a9a', lineHeight: 1.6, marginBottom: action ? 12 : 0 }}>{body}</div>
682
- {action}
738
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
739
+ <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: 1.2, color: '#778' }}>STEP {step}</span>
740
+ <span style={{ fontSize: 19, fontWeight: 800 }}>{title}</span>
741
+ {time && <span style={{ fontSize: 12, color: '#8fd9b6', marginLeft: 'auto' }}>⏱ {time}</span>}
683
742
  </div>
743
+ <div style={{ fontSize: 14, color: '#9fb0bb', lineHeight: 1.6, margin: '8px 0 14px', maxWidth: 600 }}>{body}</div>
744
+ {children}
745
+ {(launch || more) && <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>{launch}{more}</div>}
684
746
  </div>
685
747
  );
686
748
  }
687
749
 
750
+ /** The big go button. `quiet` once that step is done, so the next thing to do stands out. */
751
+ function Launch({ children, onClick, disabled, quiet = false }) {
752
+ return (
753
+ <button type="button" className="launch-go" onClick={onClick} disabled={disabled} style={{
754
+ display: 'inline-flex', alignItems: 'center', gap: 10, padding: quiet ? '10px 18px' : '13px 26px', borderRadius: 12,
755
+ fontSize: quiet ? 14 : 16, fontWeight: 800, cursor: disabled ? 'not-allowed' : 'pointer',
756
+ color: disabled ? '#667' : quiet ? '#cfe8dc' : '#04140c', border: quiet ? '1px solid rgba(0,255,136,0.35)' : 'none',
757
+ background: disabled ? 'rgba(255,255,255,0.06)' : quiet ? 'rgba(0,255,136,0.08)' : 'linear-gradient(135deg, #00ff88, #1abc9c)',
758
+ boxShadow: disabled || quiet ? 'none' : '0 4px 22px rgba(0,255,136,0.25)',
759
+ }}>
760
+ <span aria-hidden="true" style={{ fontSize: quiet ? 11 : 13 }}>▶</span>{children}
761
+ </button>
762
+ );
763
+ }
764
+
765
+ /** Why the second degree goes slowly: one tap away, not in the way. */
766
+ function WhySlow() {
767
+ return (
768
+ <details style={{ fontSize: 12.5, color: '#8b9a9a', lineHeight: 1.6 }}>
769
+ <summary style={{ cursor: 'pointer', color: '#aab7c4', fontWeight: 600 }}>Why so slow?</summary>
770
+ <div style={{ marginTop: 6 }}>
771
+ This is the part LinkedIn notices. While this app was being built, a real account was
772
+ restricted for a while after roughly <b>19 people in one sitting</b>. So: a small batch, a day&rsquo;s
773
+ rest, another batch, and stop the moment LinkedIn mentions unusual activity. Most people hide
774
+ their connections; those are noted and never tried again. A list is read to the end, saving every
775
+ 10 pages, and a stopped read carries on from the same page.
776
+ </div>
777
+ </details>
778
+ );
779
+ }
780
+
688
781
  function Btn({ children, onClick, disabled, primary, tone }) {
689
782
  const bg = tone === 'bad' ? 'rgba(255,80,80,0.15)'
690
783
  : primary ? 'linear-gradient(135deg, #9B59B6, #3498DB)'